本篇主要以@RequiresPermissions
注解为例,讲解shiro中如何设计与实现
首先定义注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermissions {
/**
* The permission string which will be passed to {@link org.apache.shiro.subject.Subject#isPermitted(String)}
* to determine if the user is allowed to invoke the code protected by this annotation.
*/
String[] value();
/**
* The logical operation for the permission checks in case multiple permissions are specified. AND is the default
* @since 1.1.0
*/
Logical logical() default Logical.AND;
}
可以看出value可以有多个值,logical枚举有or或者and两种,默认and,只有所有value都满足才行
抽象 注解处理类父类,可以看出只是简单的对注解类型的read,write,获取subject操作
接下来定义了关于权限相关的抽象类父类AuthorizingAnnotationHandler,里面定义了处理注解的抽象方法assertAuthorized方法,子类实现。
permission注解实现类
可以看到实现了assertAuthorized方法,获取注解中的信息,利用subject的isPermitted方法判断是否有权限,否则抛出异常
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]);
}
}
注解处理器好了,下面来看看注解解析器
接口 AnnotationResolver,判断在一个方法上是否存在该注解
默认实现类为:
public Annotation getAnnotation(MethodInvocation mi, Class<? extends Annotation> clazz) {
if (mi == null) {
throw new IllegalArgumentException("method argument cannot be null");
}
Method m = mi.getMethod();
if (m == null) {
String msg = MethodInvocation.class.getName() + " parameter incorrectly constructed. getMethod() returned null";
throw new IllegalArgumentException(msg);
}
Annotation annotation = m.getAnnotation(clazz);
if (annotation == null ) {
Object miThis = mi.getThis();
//SHIRO-473 - miThis could be null for static methods, just return null
annotation = miThis != null ? miThis.getClass().getAnnotation(clazz) : null;
}
return annotation;
}
下面该定义拦截器接口,在执行注解标注的方法之前先执行MethodInvocation利用Subject验证是否有权限执行该方法
抽象子类MethodInterceptorSupport增加了获取subject的方法
抽象子类AnnotationMethodInterceptor 有两个实例字段,一个注解处理器,一个注解解析器。利用注解处理器获取(MethodInvocation中的注解和注解处理器中的注解作为参数)获取注解,能获取到说明支持该注解,否则该拦截器不支持
该注解。
抽象类 AuthorizingAnnotationMethodInterceptor 继承AnnotationMethodInterceptor
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
assertAuthorized(methodInvocation);
return methodInvocation.proceed();
}
在执行注解方法是确保有权限执行,否则抛出异常。这里调用注解处理器执行验证。
public void assertAuthorized(MethodInvocation mi) throws AuthorizationException {
try {
((AuthorizingAnnotationHandler)getHandler()).assertAuthorized(getAnnotation(mi));
}
catch(AuthorizationException ae) {
// Annotation handler doesn't know why it was called, so add the information here if possible.
// Don't wrap the exception here since we don't want to mask the specific exception, such as
// UnauthenticatedException etc.
if (ae.getCause() == null) ae.initCause(new AuthorizationException("Not authorized to invoke method: " + mi.getMethod()));
throw ae;
}
}
那么这些关于权限的注解拦截器都是怎么被调用的那?shiro又专门有类继承自拦截器支持类:
抽象类 AnnotationsAuthorizingMethodInterceptor 里面包含了所有关于权限的注解,循环遍历执行各个拦截器
public AnnotationsAuthorizingMethodInterceptor() {
methodInterceptors = new ArrayList<AuthorizingAnnotationMethodInterceptor>(5);
methodInterceptors.add(new RoleAnnotationMethodInterceptor());
methodInterceptors.add(new PermissionAnnotationMethodInterceptor());
methodInterceptors.add(new AuthenticatedAnnotationMethodInterceptor());
methodInterceptors.add(new UserAnnotationMethodInterceptor());
methodInterceptors.add(new GuestAnnotationMethodInterceptor());
}
protected void assertAuthorized(MethodInvocation methodInvocation) throws AuthorizationException {
//default implementation just ensures no deny votes are cast:
Collection<AuthorizingAnnotationMethodInterceptor> aamis = getMethodInterceptors();
if (aamis != null && !aamis.isEmpty()) {
for (AuthorizingAnnotationMethodInterceptor aami : aamis) {
if (aami.supports(methodInvocation)) {
aami.assertAuthorized(methodInvocation);
}
}
}
}
那么如何调用那?
1:这里首先介绍aop的实现者aspectj的支持
/**
* Performs the method interception of the before advice at the specified joint point.
*
* @param aJoinPoint The joint point to intercept.
* @throws Throwable If an error occurs berforming the method invocation.
*/
protected void performBeforeInterception(JoinPoint aJoinPoint) throws Throwable {
// 1. Adapt the join point into a method invocation
BeforeAdviceMethodInvocationAdapter mi = BeforeAdviceMethodInvocationAdapter.createFrom(aJoinPoint);
// 2. Delegate the authorization of the method call to the super class
super.invoke(mi);
}
可以看出在这里调用了父类的invoke方法,BeforeAdviceMethodInvocationAdapter 就是接口MethodInvocation的实现类
那又是在哪里调用的AspectjAnnotationsAuthorizingMethodInterceptor那?就是下面这个切面类:
@Aspect()
public class ShiroAnnotationAuthorizingAspect {
private static final String pointCupExpression =
"execution(@org.apache.shiro.authz.annotation.RequiresAuthentication * *(..)) || " +
"execution(@org.apache.shiro.authz.annotation.RequiresGuest * *(..)) || " +
"execution(@org.apache.shiro.authz.annotation.RequiresPermissions * *(..)) || " +
"execution(@org.apache.shiro.authz.annotation.RequiresRoles * *(..)) || " +
"execution(@org.apache.shiro.authz.annotation.RequiresUser * *(..))";
@Pointcut(pointCupExpression)
public void anyShiroAnnotatedMethod(){}
@Pointcut(pointCupExpression)
void anyShiroAnnotatedMethodCall(JoinPoint thisJoinPoint) {
}
private AspectjAnnotationsAuthorizingMethodInterceptor interceptor =
new AspectjAnnotationsAuthorizingMethodInterceptor();
@Before("anyShiroAnnotatedMethodCall(thisJoinPoint)")
public void executeAnnotatedMethod(JoinPoint thisJoinPoint) throws Throwable {
interceptor.performBeforeInterception(thisJoinPoint);
}
}
所以说,凡是遇到带有上面定义的权限注解的方法都会执行一次performBeforeInterception方法,而此方法又调用了父类的invoke,invoke里面又遍历调用了所有的权限注解处理类,去判断是否权限执行该方法。
2:spring支持(其实不止spring,shiro注解能在按照aop联盟制定的规则实现的任何环境下执行)
首先看类:AopAllianceAnnotationsAuthorizingMethodInterceptor
看构造方法中把所有的注解处理器放在父类的methodInterceptors中
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);
}
然后实现invoke方法:
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
//把aop联盟中的MethodInvocation转变成shiro中的MethodInvocation
org.apache.shiro.aop.MethodInvocation mi = createMethodInvocation(methodInvocation);
return super.invoke(mi);
}
接着调用父类invoke,过程和上面aspectj一样遍历所有注解处理器
接着切面类 AuthorizationAttributeSourceAdvisor 整合advice(通知或者增强)和pointcut(切点,按某些条件从jointpoint连接点中查出来的符合条件的切点)
构造方法中设置advice
public AuthorizationAttributeSourceAdvisor() {
setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor());
}
现在接口设置切点筛选条件:
private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES =
new Class[] {
RequiresPermissions.class, RequiresRoles.class,
RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class
};
public boolean matches(Method method, Class targetClass) {
Method m = method;
if ( isAuthzAnnotationPresent(m) ) {
return true;
}
//The 'method' parameter could be from an interface that doesn't have the annotation.
//Check to see if the implementation has it.
if ( targetClass != null) {
try {
m = targetClass.getMethod(m.getName(), m.getParameterTypes());
return isAuthzAnnotationPresent(m) || isAuthzAnnotationPresent(targetClass);
} catch (NoSuchMethodException ignored) {
//default return value is false. If we can't find the method, then obviously
//there is no annotation, so just use the default return value.
}
}
return false;
}
private boolean isAuthzAnnotationPresent(Class<?> targetClazz) {
for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) {
Annotation a = AnnotationUtils.findAnnotation(targetClazz, annClass);
if ( a != null ) {
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;
}
最后由配置类:ShiroAnnotationProcessorConfiguration注入该切面
@Configuration
public class ShiroAnnotationProcessorConfiguration extends AbstractShiroAnnotationProcessorConfiguration{
@Bean
@DependsOn("lifecycleBeanPostProcessor")
protected DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
return super.defaultAdvisorAutoProxyCreator();
}
@Bean
protected AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
return super.authorizationAttributeSourceAdvisor(securityManager);
}
}