序文
実際のシステムアプリケーションは、そのような一般的なビジネスシナリオ、動的にパーミッションチェックするリソースへのユーザーアクセスを達成するために必要としています。
例えば、業務システムのプラットフォームでは、ビジネス、ブランド、製品およびその他の経営資源があります。それらの間の関係は、商人が複数のアイテムを持つことができます1つのブランドの下で複数のブランドを持つことができます。
ビジネス・ユーザーが複数のアカウントを持つことができ、各アカウントは、権限の異なるレベルを持っています。
例えば、王はAの下で事業のすべてのリソースの作業を操作するための責任がある、シャオチャンは、ブランドAとブランドA.下のすべての商品の仕事を操作するための責任があります そして、マイクは、ブランドBのために責任があります
自身がRequiresAuthenticationを提供史郎、RequiresPermissionsとRequiresRolesや他の注釈は、静的認証局を達成するために使用されている
ではなく、このようなきめ細かな動的なリソースを検証するための認証局のため。上記の説明に基づいて、記事は、動的なリソースへのきめ細かなアクセスのためのチェックを追加することです。
おそらくアイデアを設計
- 1.著作権史郎を表す文字列に、リソースの変換のために、カスタム注釈Permitableを追加(SPEL式をサポート)
- 2.新しいタグのカスタムメソッドを関連付けるために使用AOPセクション、および注釈史郎権限チェックを追加します。
- 現在のユーザーが保護されたリソースにアクセスするための十分な権限を持っているかどうか3.チェック
コーディング
- 1、新しいのPermissionResolverインタフェース
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import static java.util.stream.Collectors.toList;
/**
* 资源权限解析器
*
* @author wuyue
* @since 1.0, 2019-09-07
*/
public interface PermissionResolver {
/**
* 解析资源
*
* @return 资源的权限表示字符串
*/
String resolve();
/**
* 批量解析资源
*/
static List<String> resolve(List<PermissionResolver> list) {
return Optional.ofNullable(list).map(obj -> obj.stream().map(PermissionResolver::resolve).collect(toList()))
.orElse(Collections.emptyList());
}
}
- 2、新しいエンティティクラスの経営資源、および、そのような新Product.javaとして例えばのPermissionResolverインタフェース、貿易のリソースを、実装
import com.wuyue.shiro.shiro.PermissionResolver;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.Date;
@Getter
@Setter
@ToString
@Entity
@Table(name = "product")
public class Product implements PermissionResolver {
@Override
public String resolve() {
return merchantId + ":" + brandId + ":" + id;
}
@Id
@GenericGenerator(name = "idGen", strategy = "uuid")
@GeneratedValue(generator = "idGen")
private String id;
@Column(name = "merchant_id")
private String merchantId;
@Column(name = "brand_id")
private String brandId;
@Column(name = "name")
private String name;
@Column(name = "create_time")
private Date createTime;
@Column(name = "update_time")
private Date updateTime;
}
- 3、新しいカスタム注釈Permitable
import java.lang.annotation.*;
/**
* 自定义细粒度权限校验注解,配合SpEL表达式使用
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Permitable {
/**
* 前置校验资源权限表达式
*
* @return 资源的权限字符串表示(如“字节跳动”下的“抖音”可以表达为BYTE_DANCE:TIK_TOK)
*/
String pre() default "";
/**
* 后置校验资源权限表达式
*
* @return
*/
String post() default "";
}
- 4、新しい権限検査セクション
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;
/**
* 静态自定义权限认证切面
*/
@Slf4j
public class PermitAdvisor extends StaticMethodMatcherPointcutAdvisor {
private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES =
new Class[] {
Permitable.class
};
public PermitAdvisor(SpelExpressionParser parser) {
// 构造一个通知,当方法上有加入Permitable注解时,会触发此通知执行权限校验
MethodInterceptor advice = mi -> {
Method method = mi.getMethod();
Object targetObject = mi.getThis();
Object[] args = mi.getArguments();
Permitable permitable = method.getAnnotation(Permitable.class);
// 前置权限认证
checkPermission(parser, permitable.pre(), method, args, targetObject, null);
Object proceed = mi.proceed();
// 后置权限认证
checkPermission(parser, permitable.post(), method, args, targetObject, proceed);
return proceed;
};
setAdvice(advice);
}
/**
* 匹配加了Permitable注解的方法,用于通知权限校验
*/
@Override
public boolean matches(Method method, Class<?> targetClass) {
Method m = method;
if (isAuthzAnnotationPresent(m)) {
return true;
}
return false;
}
private boolean isAuthzAnnotationPresent(Method method) {
for (Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES) {
Annotation a = AnnotationUtils.findAnnotation(method, annClass);
if ( a != null ) {
return true;
}
}
return false;
}
/**
* 动态权限认证
*/
private void checkPermission(SpelExpressionParser parser, String expr,
Method method, Object[] args, Object target, Object result){
if (StringUtils.isBlank(expr)){
return;
}
// 解析SpEL表达式,获得资源的权限表示字符串
Object resources = parser.parseExpression(expr)
.getValue(createEvaluationContext(method, args, target, result), Object.class);
// 调用Shiro进行权限校验
if (resources instanceof String) {
SecurityUtils.getSubject().checkPermission((String) resources);
} else if (resources instanceof List){
List<Object> list = (List) resources;
list.stream().map(obj -> (String) obj).forEach(SecurityUtils.getSubject()::checkPermission);
}
}
/**
* 构造SpEL表达式上下文
*/
private EvaluationContext createEvaluationContext(Method method, Object[] args, Object target, Object result) {
MethodBasedEvaluationContext evaluationContext = new MethodBasedEvaluationContext(
target, method, args, new DefaultParameterNameDiscoverer());
evaluationContext.setVariable("result", result);
try {
evaluationContext.registerFunction("resolve", PermissionResolver.class.getMethod("resolve", List.class));
} catch (NoSuchMethodException e) {
log.error("Get method error:", e);
}
return evaluationContext;
}
}
- 5、許可されたユーザを実現
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
Map<String, Object> principal = (Map<String, Object>) principals.getPrimaryPrincipal();
String accountId = (String) principal.get("accountId");
// 拥有的商家资源权限
List<AccountMerchantLink> merchantLinks = accountService.findMerchantLinks(accountId);
Set<String> merchantPermissions = merchantLinks.stream().map(AccountMerchantLink::getMerchantId).collect(toSet());
SimpleAuthorizationInfo authzInfo = new SimpleAuthorizationInfo();
authzInfo.addStringPermissions(merchantPermissions);
// 拥有的品牌资源权限
List<AccountBrandLink> brandLinks = accountService.findBrandLinks(accountId);
Set<String> brandPermissions = brandLinks.stream().map(link -> link.getMerchantId() + ":" + link.getBrandId()).collect(toSet());
authzInfo.addStringPermissions(brandPermissions);
return authzInfo;
}
6、カスタム注釈アプリケーション
6.1、IDに基づいて、ビジネス情報へのアクセス
@Permitable(pre = "#id") @Override public Optional<Merchant> findById(String id) { if (StringUtils.isBlank(id)) { return Optional.empty(); } return merchantDao.findById(id); }
6.2、IDに基づいて商品情報を取得します
@Permitable(post = "#result?.get().resolve()") @Override public Optional<Product> findById(String id) { if (StringUtils.isBlank(id)) { return Optional.empty(); } return productDao.findById(id); }
6.3、ブランド品のリストを見つけます
@Permitable(post = "#resolve(#result)") @Override public List<Product> findByBrandId(String brandId) { if (StringUtils.isBlank(brandId)) { return Collections.emptyList(); } return productDao.findByBrandId(brandId); }
7、テスト
7.1、上述したサービスシナリオによれば、ユーザデータ3を調製しました
7.2、王のテストを使用してログオン
7.2.1、ビジネス情報を取得する(権限を持っています)
7.2.2、製品情報を取得する(権限を持っています)
7.3、李のテストを使用してログオン
7.3.1、ビジネス情報(不十分な権限)を取得
7.3.2、製品情報(不十分な権限)を得ます
7.3.3、製品情報を取得する(権限を持っています)
7.4まとめ
それはインタフェースのテストの上のスクリーンショットからわかるように、このプログラムに沿って、私たちは、達成すべきビジネスシナリオの最初に設計されています。