shiro原始碼分析之自定義註解RequiredPermission(可代替RequiresPermissions)
阿新 • • 發佈:2018-12-10
1.現象
shiro使用RequiresPermissions等註解必須新增如下切面,否則不生效 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; }
2.原始碼分析
分析AuthorizationAttributeSourceAdvisor(繼承自StaticMethodMatcherPointcutAdvisor)原始碼發現,所有授權註解校驗都在AUTHZ_ANNOTATION_CLASSES中 private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES = new Class[] { RequiresPermissions.class, RequiresRoles.class, RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class }; 並且其構造方法中加了AopAllianceAnnotationsAuthorizingMethodInterceptor(繼承自AnnotationsAuthorizingMethodInterceptor)攔截器 public AuthorizationAttributeSourceAdvisor() { setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor()); } 而AopAllianceAnnotationsAuthorizingMethodInterceptor攔截器構造方法中加了一堆的對應註解攔截器 public AopAllianceAnnotationsAuthorizingMethodInterceptor() { List<AuthorizingAnnotationMethodInterceptor> interceptors = new ArrayList<AuthorizingAnnotationMethodInterceptor>(5); //use a Spring-specific Annotation resolver - Spring's AnnotationUtils is nicer than the //raw JDK resolution process. AnnotationResolver resolver = new SpringAnnotationResolver(); //we can re-use the same resolver instance - it does not retain state: interceptors.add(new RoleAnnotationMethodInterceptor(resolver)); interceptors.add(new PermissionAnnotationMethodInterceptor(resolver)); interceptors.add(new AuthenticatedAnnotationMethodInterceptor(resolver)); interceptors.add(new UserAnnotationMethodInterceptor(resolver)); interceptors.add(new GuestAnnotationMethodInterceptor(resolver)); setMethodInterceptors(interceptors); } 分析註解對應攔截器(都繼承自AuthorizingAnnotationMethodInterceptor)發現,在其構造方法中新增校驗許可權的handler(PermissionAnnotationMethodInterceptor為例) public PermissionAnnotationMethodInterceptor(AnnotationResolver resolver) { super( new PermissionAnnotationHandler(), resolver); } 而他們的handler都是簡單的呼叫許可權校驗方法,原始碼如下 public void assertAuthorized(Annotation a) throws AuthorizationException { if (!(a instanceof RequiresPermissions)) return; RequiresPermissions rpAnnotation = (RequiresPermissions) a; String[] perms = getAnnotationValue(a); Subject subject = getSubject(); if (perms.length == 1) { subject.checkPermission(perms[0]); return; } if (Logical.AND.equals(rpAnnotation.logical())) { getSubject().checkPermissions(perms); return; } if (Logical.OR.equals(rpAnnotation.logical())) { // Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first boolean hasAtLeastOnePermission = false; for (String permission : perms) if (getSubject().isPermitted(permission)) hasAtLeastOnePermission = true; // Cause the exception if none of the role match, note that the exception message will be a bit misleading if (!hasAtLeastOnePermission) getSubject().checkPermission(perms[0]); } }
3.自定義註解RequiredPermission, 用於授權校驗
這裡只是簡單的加了des, 可根據自己需求去改
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiredPermission {
String[] value();
Logical logical() default Logical.AND;
String des() default "";
}
4.自定義MineAuthorizationAttributeSourceAdvisor繼承自StaticMethodMatcherPointcutAdvisor
其實和AuthorizationAttributeSourceAdvisor的區別是AUTHZ_ANNOTATION_CLASSES中加入自己的註解, 然後使用自己的攔截器MineAopAllianceAnnotationsAuthorizingMethodInterceptor
public class MineAuthorizationAttributeSourceAdvisor extends StaticMethodMatcherPointcutAdvisor {
private static final Logger log = LoggerFactory.getLogger(org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor.class);
private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES =
new Class[] {
RequiresPermissions.class, RequiresRoles.class,
RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class,
RequiredPermission.class
};
protected SecurityManager securityManager = null;
public MineAuthorizationAttributeSourceAdvisor() {
setAdvice(new MineAopAllianceAnnotationsAuthorizingMethodInterceptor());
}
public SecurityManager getSecurityManager() {
return securityManager;
}
public void setSecurityManager(org.apache.shiro.mgt.SecurityManager securityManager) {
this.securityManager = securityManager;
}
public boolean matches(Method method, Class targetClass) {
Method m = method;
if ( isAuthzAnnotationPresent(m) ) {
return true;
}
if ( targetClass != null) {
try {
m = targetClass.getMethod(m.getName(), m.getParameterTypes());
if ( isAuthzAnnotationPresent(m) ) {
return true;
}
} catch (NoSuchMethodException ignored) {
}
}
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;
}
}
5.自定義攔截器MineAopAllianceAnnotationsAuthorizingMethodInterceptor (繼承自AopAllianceAnnotationsAuthorizingMethodInterceptor)
就是把自己的RequiredPermissionAnnotationMethodInterceptor攔截器新增到advice中
public class MineAopAllianceAnnotationsAuthorizingMethodInterceptor extends AopAllianceAnnotationsAuthorizingMethodInterceptor {
public MineAopAllianceAnnotationsAuthorizingMethodInterceptor() {
super();
this.methodInterceptors.add(new RequiredPermissionAnnotationMethodInterceptor(new SpringAnnotationResolver()));
}
}
6.自定義RequiredPermissionAnnotationMethodInterceptor攔截器 (繼承自AuthorizingAnnotationMethodInterceptor)
定義自己的註解攔截器,並使用自己的RequiredPermissionAnnotationHandler
public class RequiredPermissionAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
public RequiredPermissionAnnotationMethodInterceptor(){
super(new RequiredPermissionAnnotationHandler());
}
public RequiredPermissionAnnotationMethodInterceptor(AnnotationResolver resolver){
super(new RequiredPermissionAnnotationHandler(), resolver);
}
}
7.RequiredPermissionAnnotationHandler (繼承自AuthorizingAnnotationHandler)
在assertAuthorized中新增自己的校驗邏輯
public class RequiredPermissionAnnotationHandler extends AuthorizingAnnotationHandler {
public RequiredPermissionAnnotationHandler() {
super(RequiredPermission.class);
}
protected String[] getAnnotationValue(Annotation a) {
RequiredPermission rpAnnotation = (RequiredPermission) a;
return rpAnnotation.value();
}
@Override
public void assertAuthorized(Annotation a) throws AuthorizationException {
if (!(a instanceof RequiredPermission)) {
return;
}
RequiredPermission rpAnnotation = (RequiredPermission) a;
String[] perms = getAnnotationValue(a);
Subject subject = getSubject();
if (perms.length == 1) {
subject.checkPermission(perms[0]);
return;
}
if (Logical.AND.equals(rpAnnotation.logical())) {
getSubject().checkPermissions(perms);
return;
}
if (Logical.OR.equals(rpAnnotation.logical())) {
boolean hasAtLeastOnePermission = false;
for (String permission : perms) {
if (getSubject().isPermitted(permission)) {
hasAtLeastOnePermission = true;
}
}
if (!hasAtLeastOnePermission) {
getSubject().checkPermission(perms[0]);
}
}
}
}
8.替換MineAuthorizationAttributeSourceAdvisor替換AuthorizationAttributeSourceAdvisor
@Bean
public MineAuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
MineAuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new MineAuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
9.測試
@RequestMapping(value="/addTeacher.do", method =RequestMethod.GET)
@RequiredPermission(value = {"teacher:add"}, des = "新增老師")
public ResultMessage addTeacher(){
return ResultMessage.success("permission success.");
}
@RequestMapping(value="/updateTeacher.do", method =RequestMethod.GET)
@RequiredPermission(value = {"teacher:update"}, des = "新增老師")
public ResultMessage updateTeacher(){
return ResultMessage.success("permission success.");
}
原始碼 https://gitee.com/jsjack_wang/springboot-demo dev-shiro分支