Shiro权限注解原理
概述
前不久刚学会使用权限注解(),开始思索了一番。最开始猜测实现方式是注解@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的理解。
Shiro权限注解原理的更多相关文章
- Shiro 权限注解
Shiro 权限注解: Shiro 提供了相应的注解用于权限控制,如果使用这些注解就需要使用AOP 的功能来进行 判断,如Spring AOP:Shiro 提供了Spring AOP 集成用于 ...
- 2017.2.13 开涛shiro教程-第十二章-与Spring集成(二)shiro权限注解
原博客地址:http://jinnianshilongnian.iteye.com/blog/2018398 根据下载的pdf学习. 第十二章-与Spring集成(二)shiro权限注解 shiro注 ...
- SpringBoot Shiro 权限注解不起作用
最近在学习springboot结合shiro做权限管理时碰到一个问题. 问题如下: 我在userRealm中的doGetAuthorizationInfo方法中给用户添加了权限,然后在Controll ...
- Shiro 权限校验不通过时,区分GET和POST请求正确响应对应的方式
引入:https://blog.csdn.net/catoop/article/details/69210140 本文基于Shiro权限注解方式来控制Controller方法是否能够访问. 例如使用到 ...
- Shiro权限管理框架(三):Shiro中权限过滤器的初始化流程和实现原理
本篇是Shiro系列第三篇,Shiro中的过滤器初始化流程和实现原理.Shiro基于URL的权限控制是通过Filter实现的,本篇从我们注入的ShiroFilterFactoryBean开始入手,翻看 ...
- SpringMVC整合Shiro权限框架
尊重原创:http://blog.csdn.net/donggua3694857/article/details/52157313 最近在学习Shiro,首先非常感谢开涛大神的<跟我学Shiro ...
- 类Shiro权限校验框架的设计和实现(2)--对复杂权限表达式的支持
前言: 我看了下shiro好像默认不支持复杂表达式的权限校验, 它需要开发者自己去做些功能扩展的工作. 针对这个问题, 同时也会为了弥补上一篇文章提到的支持复杂表示需求, 特地尝试写一下解决方法. 本 ...
- 4.SSM配置shiro权限管理
作者QQ:1095737364 QQ群:123300273 欢迎加入! 1.搭建SSM项目: http://www.cnblogs.com/yysbolg/p/6909021.html ...
- Shiro框架 (原理分析与简单实现)
Shiro框架(原理分析与简单实现) 有兴趣的同学也可以阅读我之前分享的:Java权限管理(授权与认证)CRM权限管理 (PS : 这篇博客里面的实现方式没有使用框架,完全是手写的授权与认证,可以 ...
随机推荐
- android_sdcard读写(三)
这次来个稍微复杂点的. package cn.com.sxp;import android.app.Activity;import android.app.ProgressDialog;import ...
- 组件--button详解
一.wxss尺寸单位rpx rpx(responsive pixel): 可以根据屏幕宽度进行自适应.规定屏幕宽为750rpx. 严格按照XML语法. 二.icon 图标组件 <!--index ...
- 网页缓存相关的HTTP头部信息详解
前言 之前看完了李智慧老师著的<大型网站技术架构-核心原理与案例分析>这本书,书中多次提起浏览器缓存的话题,恰是这几天生产又遇到了一个与缓存的问题,发现自己书是没少看,正经走心的内容却不多 ...
- 【CodeForces - 1167C 】News Distribution(并查集)
News Distribution 题意 大概就是分成几个小团体,给每个人用1 - n编号,当对某个人传播消息的时候,整个小团体就知道这个消息,输出 分别对1 - n编号的某个人传递消息时,有多少人知 ...
- swift对象存储
swift对象存储 简介 OpenStack Object Storage(Swift)是OpenStack开源云计算项目的子项目之一,被称为对象存储,提供了强大的扩展性.冗余和持久性.对象存储,用于 ...
- python面向对象-封装-property-接口-抽象-鸭子类型-03
封装 什么是封装: # 将复杂的丑陋的隐私的细节隐藏到内部,对外提供简单的使用接口 或 # 对外隐藏内部实现细节,并提供访问的接口 为什么需要封装 1.为了保证关键数据的安全性 2.对外部隐藏内部的实 ...
- 最全的Vue组件通信方式总结
1.一图认清组件关系名词 父子关系:A与B.A与C.B与D.C与E 兄弟关系:B与C 隔代关系:A与D.A与E 非直系亲属:D与E 总结为三大类: 父子组件之间通信 兄弟组件之间通信 跨级通信 2.8 ...
- PHP与ECMAScript_6_常用运算符
优先级从上到下 PHP ECMAScript 特殊运算符 [ ] ,( ) [ ] ,( ) 自增减/类型 ++ -- ! int float string array object @ (错误抑 ...
- 【iOS】iOS viewDidLoad 方法名问题
这两天在调试一个项目,跳转到一个页面的时候总是不显示标题栏(当然也没有标题栏的返回按钮),搞了好久,今天总算找到了问题:之前的开发人员竟然把 viewDidLoad 这个基本的方法名写成了 views ...
- poj 1068 模拟
题目链接 大概题意就是告诉你有个n个小括号,每一个")"左边有多少个"("都告诉你了,然后让你求出每一对括号之间有多少对括号(包含自己本身). 思路: 我先计算 ...