概述
前不久剛學會使用權限注解(),開始思索了一番。最開始猜測實現方式是注解@Aspect,具體實現方式類似如下所示(切面記錄審計日志)。后來發現並非如此,所以特地分析一下源碼。
@Component
@Aspect
public class AuditLogAspectConfig {
@Pointcut("@annotation(com.ygsoft.ecp.mapp.basic.audit.annotation.AuditLog) || @annotation(com.ygsoft.ecp.mapp.basic.audit.annotation.AuditLogs)")
public void pointcut() {
}
@After(value="pointcut()")
public void after(JoinPoint joinPoint) {
//執行的邏輯
}
...
}
權限注解的源碼分析
DefaultAdvisorAutoProxyCreator
這個類實現了BeanProcessor
接口,當ApplicationContext
讀取所有的Bean配置信息后,這個類將掃描上下文,尋找所有的Advistor
(一個Advisor
是一個切入點和一個通知的組成),將這些Advisor
應用到所有符合切入點的Bean中。
@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);
}
}
AuthorizationAttributeSourceAdvisor
繼承了StaticMethodMatcherPointcutAdvisor
,如下代碼所示,只匹配五個注解,也就是說只對這五個注解標注的類或者方法增強。StaticMethodMatcherPointcutAdvisor
是靜態方法切點的抽象基類,默認情況下它匹配所有的類。StaticMethodMatcherPointcut
包括兩個主要的子類分別是NameMatchMethodPointcut
和AbstractRegexpMethodPointcut
,前者提供簡單字符串匹配方法前面,而后者使用正則表達式匹配方法前面。動態方法切點:DynamicMethodMatcerPointcut
是動態方法切點的抽象基類,默認情況下它匹配所有的類,而且也已經過時,建議使用DefaultPointcutAdvisor
和DynamicMethodMatcherPointcut
動態方法代替。另外還需關注構造器中的傳入的AopAllianceAnnotationsAuthorizingMethodInterceptor
。
public class AuthorizationAttributeSourceAdvisor extends StaticMethodMatcherPointcutAdvisor {
private static final Logger log = LoggerFactory.getLogger(AuthorizationAttributeSourceAdvisor.class);
private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES =
new Class[] {
RequiresPermissions.class, RequiresRoles.class,
RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class
};
protected SecurityManager securityManager = null;
public AuthorizationAttributeSourceAdvisor() {
setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor());
}
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;
}
}
AopAllianceAnnotationsAuthorizingMethodInterceptor
在初始化時,interceptors
添加了5個方法攔截器(都繼承自AuthorizingAnnotationMethodInterceptor
),這5個攔截器分別對5種權限驗證的方法進行攔截,執行invoke方法。
public class AopAllianceAnnotationsAuthorizingMethodInterceptor
extends AnnotationsAuthorizingMethodInterceptor implements MethodInterceptor {
public AopAllianceAnnotationsAuthorizingMethodInterceptor() {
List<AuthorizingAnnotationMethodInterceptor> interceptors =
new ArrayList<AuthorizingAnnotationMethodInterceptor>(5);
AnnotationResolver resolver = new SpringAnnotationResolver();
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);
}
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
org.apache.shiro.aop.MethodInvocation mi = createMethodInvocation(methodInvocation);
return super.invoke(mi);
}
...
}
AopAllianceAnnotationsAuthorizingMethodInterceptor
的invoke方法,又會調用超類AuthorizingMethodInterceptor
的invoke方法,在該方法中先執行assertAuthorized方法,進行權限校驗,校驗不通過,拋出AuthorizationException
異常,中斷方法;校驗通過,則執行methodInvocation.proceed()
,該方法也就是被攔截並且需要權限校驗的方法。
public abstract class AuthorizingMethodInterceptor extends MethodInterceptorSupport {
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
assertAuthorized(methodInvocation);
return methodInvocation.proceed();
}
protected abstract void assertAuthorized(MethodInvocation methodInvocation) throws AuthorizationException;
}
assertAuthorized方法最終執行的還是AuthorizingAnnotationMethodInterceptor.assertAuthorized
,而AuthorizingAnnotationMethodInterceptor
有5中的具體的實現類(RoleAnnotationMethodInterceptor
, PermissionAnnotationMethodInterceptor
, AuthenticatedAnnotationMethodInterceptor
, UserAnnotationMethodInterceptor
, GuestAnnotationMethodInterceptor
)。
public abstract class AnnotationsAuthorizingMethodInterceptor extends AuthorizingMethodInterceptor {
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);
}
}
}
}
...
}
AuthorizingAnnotationMethodInterceptor
的assertAuthorized,首先從子類獲取AuthorizingAnnotationHandler
,再調用該實現類的assertAuthorized
方法。
public abstract class AuthorizingAnnotationMethodInterceptor extends AnnotationMethodInterceptor
{
public AuthorizingAnnotationMethodInterceptor( AuthorizingAnnotationHandler handler ) {
super(handler);
}
public AuthorizingAnnotationMethodInterceptor( AuthorizingAnnotationHandler handler,
AnnotationResolver resolver) {
super(handler, resolver);
}
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) {
if (ae.getCause() == null) ae.initCause(new AuthorizationException("Not authorized to invoke method: " + mi.getMethod()));
throw ae;
}
}
}
現在分析其中一種實現類PermissionAnnotationMethodInterceptor
,也是用的最多的,但是這個類的實際代碼很少,很明顯上述分析的getHandler在PermissionAnnotationMethodInterceptor
中返回值為PermissionAnnotationHandler
。
public class PermissionAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
public PermissionAnnotationMethodInterceptor() {
super( new PermissionAnnotationHandler() );
}
public PermissionAnnotationMethodInterceptor(AnnotationResolver resolver) {
super( new PermissionAnnotationHandler(), resolver);
}
}
在PermissionAnnotationHandler
類中,終於發現實際的檢驗邏輯,還是調用的Subject.checkPermission()
進行校驗。
public class PermissionAnnotationHandler extends AuthorizingAnnotationHandler {
public PermissionAnnotationHandler() {
super(RequiresPermissions.class);
}
protected String[] getAnnotationValue(Annotation a) {
RequiresPermissions rpAnnotation = (RequiresPermissions) a;
return rpAnnotation.value();
}
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())) {
boolean hasAtLeastOnePermission = false;
for (String permission : perms) if (getSubject().isPermitted(permission)) hasAtLeastOnePermission = true;
if (!hasAtLeastOnePermission) getSubject().checkPermission(perms[0]);
}
}
}
實現類似編程式AOP
定義一個注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
}
繼承StaticMethodMatcherPointcutAdvisor
類,並實現相關的方法。
@SuppressWarnings("serial")
@Component
public class HelloAdvisor extends StaticMethodMatcherPointcutAdvisor{
public HelloAdvisor() {
setAdvice(new LogMethodInterceptor());
}
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());
return isAuthzAnnotationPresent(m);
} catch (NoSuchMethodException ignored) {
}
}
return false;
}
private boolean isAuthzAnnotationPresent(Method method) {
Annotation a = AnnotationUtils.findAnnotation(method, Log.class);
return a!= null;
}
}
實現MethodInterceptor
接口,定義切面處理的邏輯
public class LogMethodInterceptor implements MethodInterceptor{
public Object invoke(MethodInvocation invocation) throws Throwable {
Log log = invocation.getMethod().getAnnotation(Log.class);
System.out.println("log: "+log.value());
return invocation.proceed();
}
}
定義一個測試類,並添加Log注解
@Component
public class TestHello {
@Log("test log")
public String say() {
return "ss";
}
}
編寫啟動類,並且配置DefaultAdvisorAutoProxyCreator
@Configuration
public class TestBoot {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext("com.fzsyw.test");
TestHello th = ctx.getBean(TestHello.class);
System.out.println(th.say());
}
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator da = new DefaultAdvisorAutoProxyCreator();
da.setProxyTargetClass(true);
return da;
}
}
最終打印的結果如下,證明編程式的AOP生效。
log: test log
ss
總結與思考
Shiro的注解式權限,使用確實方便,通過源碼也分析了它的實現原理,比較核心的是配置DefaultAdvisorAutoProxyCreator
和繼承StaticMethodMatcherPointcutAdvisor
。其中的5中權限注解,使用了統一一套代碼架構,用到了的模板模式,方便擴展。最后自己也簡單做了一個小例子,加深對編程式AOP的理解。