spring对shiro注解支持的原理
来源:互联网 发布:mac qq接受文件没反应 编辑:程序博客网 时间:2024/06/07 09:35
背景介绍:
笔者最近要开发一个开放的管理后台,既然是给大量的用户做的,就必须要考虑到用户的权限问题,做到安全的管理,笔者以前用过的是spring security(简称ss),
但是根据网上的资料和同事的推荐,发现使用shiro来做权限控制比ss更好一些,尤其是spring组织的项目也是用的shiro,没有用自家的ss,所以笔者准备入shiro坑
本篇文章主要讲的是spring对shiro注解的支持和扩展,至于spring集成shiro注解的部分就不做详细说明。
环境介绍:
spring:4.1.6
shiro:1.2.3
shiro-spring:1.2.3
示例代码
@Controller@RequestMapping(value="demo")@RequiresRoles("ADMIN")public class DemoController extends BaseController{/*----------------角色校验demo--------------------*//** * 角色测试一:代码方式验证角色 * @return */@RequestMapping(value="role/demo01")@ResponseBodypublic String demo01(){//判断有没有admin的角色SecurityUtils.getSubject().checkRole("admin");System.out.println("测试代码方式验证角色");return "demo01";}/** * 角色测试二:注解方式验证单个角色 * @return */@RequiresRoles("USER")@RequestMapping(value="role/demo02")@ResponseBodypublic String demo02(){System.out.println("测试注解方式验证单个角色");return "demo02";}/** * 角色测试三:注解方式验证多个角色 * @return */@RequiresRoles(logical=Logical.OR,value={"USER","ADMIN"})@RequestMapping(value="role/demo03")@ResponseBodypublic String demo03(){System.out.println("测试注解方式验证多个角色");return "demo03";}/** * 角色测试四:controller类上的注解方式验证角色(结果:不支持,只支持方法级别的***最后自定义实现了) * @return */@RequestMapping(value="role/demo04")@ResponseBodypublic String demo04(){System.out.println("controller类上的注解方式验证角色");return "demo04";}}
*spring对shiro注解的支持扩展支持类级别
这个是什么意思呢?说的是shiro-spring包支持的@RequiresPermissions, @RequiresRoles,@RequiresUser, @RequiresGuest, @RequiresAuthentication这5个注解,
只有在方法上使用的时候才会生效,但是查看注解的代码,以@RequiresRoles为例,注解应该在方法上,类上都应该有效的:
@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 roles are specified. AND is the default * @since 1.1.0 */ Logical logical() default Logical.AND; }
笔者的目的:让@RequiresRoles在方法上和类上都可以生效
应用场景:@RequiresRoles在方法上的效果自然就是控制访问这个路径的权限,@RequiresRoles在类上的作用什么呢?@RequiresRoles在类上的作用就是起到默认的权限控制,
也就是说像demo04()这样没有指定@RequiresRoles的方法,默认需要的权限是DemoController类上指定的@RequiresRoles权限,但是向demo01()已经指定权限的方法,就会覆盖类上的权限设置
方法(细粒度)权限 会覆盖 类(粗粒度)权限
有人会想,这样会有什么用呢,我举个例子,比如说一个小说的模块所有的url访问都需要USER权限,如果只是一个个的方法都设置成一样的@RequiresRoles权限,是不是很麻烦,但是如果支持了类上@RequiresRoles(默认权限)
就会让所有没有指定@RequiresRoles权限的方法默认是需要类上的@RequiresRoles指定的权限
@RequiresRoles现在已经解释清除了,那么现在shiro-spring是不支持类上@RequiresRoles的,我们应该怎么让它支持,满足我们的需求呢?
笔者查看了一下shiro-spring扫描@RequiresRoles权限的源码:
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; /** * Create a new AuthorizationAttributeSourceAdvisor. */ 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; } //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()); if ( isAuthzAnnotationPresent(m) ) { return true; } } 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(Method method) { for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) { Annotation a = AnnotationUtils.findAnnotation(method, annClass); if ( a != null ) { return true; } } return false; }}
这个扫描shiro注解类的源码最重要的方法时matches方法,会通过返回true的方式来判断某个方法是否带有shiro权限注解,是的,只会判断方法,那么我们现在需要支持类上的权限注解,怎么办呢?
笔者把源码给修改了一下:
/** * 自定义的注解权限AOP扫描 * @author zhihua * */public class OpenCmsAuthorizationAdvisor extends AuthorizationAttributeSourceAdvisor{//权限注解private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES = new Class[] { RequiresPermissions.class, RequiresRoles.class, RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class };//web注解private static final Class<? extends Annotation>[] WEB_ANNOTATION_CLASSES = new Class[] { RequestMapping.class };/** * Create a new AuthorizationAttributeSourceAdvisor. */ public OpenCmsAuthorizationAdvisor() { setAdvice(new OpenCmsAnnotationsAuthorizingMethodInterceptor()); }/** * 匹配带有注解的方法 */@Overridepublic boolean matches(Method method, Class targetClass) {boolean flag = super.matches(method, targetClass);//如果方法上没有权限注解,尝试获取类上的默认权限注解if(!flag && isAuthzAnnotationPresent(targetClass) && isWebAnnotationPresent(method)){flag = true;}return flag;}private boolean isAuthzAnnotationPresent(Class<BaseController> clazz) { for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) { Annotation a = AnnotationUtils.findAnnotation(clazz, 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; }private boolean isWebAnnotationPresent(Method method) { for( Class<? extends Annotation> annClass : WEB_ANNOTATION_CLASSES ) { Annotation a = AnnotationUtils.findAnnotation(method, annClass); if ( a != null ) { return true; } } return false; }}
添加了一个逻辑,就是如果方法上没有发现shiro权限注解的话,会先判断方法是否有requestmapping注解,判断是否是一个接口,这样是因为要屏蔽equals()等方法,如果是一个接口,就会扫描类上的@RequiresRoles作为方法
的指定访问权限,这样就实现了让类上的shiro注解生效,作为默认的权限注解。但是有的读者可能会问,这里只是扫描,那对类上的@RequiresRoles注解进行处理的地方是怎样的原理呢?在这里,我要跟大家说,对类上的
@RequiresRoles注解进行的相关处理shiro-spring.jar是支持的,只是在扫描的时候不支持,所以笔者在这里怀疑这是shiro-spring.jar的一个bug.然后关于对类上的@RequiresRoles注解进行处理的地方的原理讲解在下面会详细说明,
这里暂时跳过
对了,做了扩展以后,spring的配置文件也要做修改
<!-- Enable Shiro Annotations for Spring-configured beans. Only run after --><!-- the lifecycleBeanProcessor has run: --><bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/><!-- 这个是原生的,因为不满足需要,所以修改为自定义的了 --><!-- <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/></bean> --><bean class="com.boluofan.opencms.auth.OpenCmsAuthorizationAdvisor"> <property name="securityManager" ref="securityManager"/></bean>
*对类上的@RequiresRoles注解进行处理的原理
其实这个很简单,我简单的贴出源码给大家看一下就知道了,还是以@requireRoles为例子
DefaultAnnotationResolver:
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); return annotation == null ? mi.getThis().getClass().getAnnotation(clazz) : annotation; }
Annotation annotation = m.getAnnotation(clazz);
这行代码是查找方法上的注解,
annotation == null ? mi.getThis().getClass().getAnnotation(clazz) : annotation
这行代码是如果在方法上找不到权限注解,就从类上获取权限注解
然后会跳转到annotation的对应处理方法,
reqireRoles --> RoleAnnotationHandler
会在RoleAnnotationHandler的assertAuthorized方法里进行权限认证,assertAuthorized方法的权限认证实际上是在AuthorizingRealm.java里边通过
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
进行校验的,相信大家都这个方法都很熟悉了。这就是对注解的运行原理,如果大家对这部分还是不是很清楚的话,接下来,我会带大家实现一个自定义的shiro-spring权限注解,
shiro-spring.jar只支持@RequiresPermissions, @RequiresRoles,@RequiresUser, @RequiresGuest, @RequiresAuthentication这5个注解,我会带大家实现一个基于jsr的@RolesAllowed注解,同样也是用于角色的认证
*实现自定义shiro权限注解
这个部分笔者会带大家实现一个自定义的shiro注解,用于角色校验,和@RequiresRoles的作用是一样的
首先说一下,spring-shiro注解实现认证的内部原理:
#tomcat在启动的时候shiro-spring中AopAllianceAnnotationsAuthorizingMethodInterceptor注册注解拦截器
#tomcat启动的时候AuthorizationAttributeSourceAdvisor扫描权限注解
#用户的请求经过被注册的注解拦截器拦截
AopAllianceAnnotationsAuthorizingMethodInterceptor.invoke(MethodInvocation methodInvocation)
AuthorizingMethodInterceptor.invoke
AnnotationsAuthorizingMethodInterceptor.assertAuthorized(MethodInvocation methodInvocation)
AuthorizingAnnotationMethodInterceptor.assertAuthorized(MethodInvocation mi)
RoleAnnotationHandler.assertAuthorized()
AuthorizingRealm.checkRole()
返回验证结果
#注解拦截器经过AuthorizingRealm(可以自定义实现)获取权限
接下来,我们按照上述的机制步骤来实现自定义注解@rolesallowed
#实现自定义shiro权限注解
@Documented@Retention (RUNTIME)@Target({TYPE, METHOD})public @interface RolesAllowed { String[] value();}
#注册注解拦截器
public class OpenCmsAnnotationsAuthorizingMethodInterceptor extends AopAllianceAnnotationsAuthorizingMethodInterceptor {public OpenCmsAnnotationsAuthorizingMethodInterceptor() {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)); //自定义 interceptors.add(new RoleAllowsAnnotationMethodInterceptor()); setMethodInterceptors(interceptors);}}
rolesallowed注解拦截器
public class RoleAllowsAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {public RoleAllowsAnnotationMethodInterceptor() {super(new RolesAllowedAnnotationHandler());}public RoleAllowsAnnotationMethodInterceptor(AnnotationResolver resolver) {super(new RolesAllowedAnnotationHandler(),resolver);}}
注解处理器
public class RolesAllowedAnnotationHandler extends AuthorizingAnnotationHandler {/** * 构造函数 * @param annotationClass */public RolesAllowedAnnotationHandler() {super(RolesAllowed.class);}@Overridepublic void assertAuthorized(Annotation a) throws AuthorizationException {RolesAllowed rrAnnotation = (RolesAllowed) a; String[] roles = rrAnnotation.value();getSubject().checkRoles(Arrays.asList(roles)); return;}}
#添加扫描权限注解@rolesallowed
public class OpenCmsAuthorizationAdvisor extends AuthorizationAttributeSourceAdvisor{//权限注解private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES = new Class[] {RolesAllowed.class, RequiresPermissions.class, RequiresRoles.class, RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class };//web注解private static final Class<? extends Annotation>[] WEB_ANNOTATION_CLASSES = new Class[] { RequestMapping.class };/** * Create a new AuthorizationAttributeSourceAdvisor. */ public OpenCmsAuthorizationAdvisor() { setAdvice(new OpenCmsAnnotationsAuthorizingMethodInterceptor()); }/** * 匹配带有注解的方法 */@Overridepublic boolean matches(Method method, Class targetClass) {boolean flag = super.matches(method, targetClass);//如果方法上没有权限注解,尝试获取类上的默认权限注解if(!flag && isAuthzAnnotationPresent(targetClass) && isWebAnnotationPresent(method)){flag = true;}return flag;}/** * 查看BaseController的子类是否有权限注解 * @param clazz * @return */private boolean isAuthzAnnotationPresent(Class<BaseController> clazz) { for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) { Annotation a = AnnotationUtils.findAnnotation(clazz, annClass); if ( a != null ) { return true; } } return false; }/** * 查看方法上是否有权限注解 * @param method * @return */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; }/** * 查看方法是否有web注解,是否是一个rest接口 * @param method * @return */private boolean isWebAnnotationPresent(Method method) { for( Class<? extends Annotation> annClass : WEB_ANNOTATION_CLASSES ) { Annotation a = AnnotationUtils.findAnnotation(method, annClass); if ( a != null ) { return true; } } return false; }}
#用户的请求经过被注册的注解拦截器拦截
---
#注解拦截器经过DefaultRealm获取权限
public class DefaultRealm extends AuthorizingRealm{/** * 用于角色权限校验 */@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.fromRealm(getName()).iterator().next(); // 查询用户授权信息 伪代码 SimpleAuthorizationInfo info = 。。。; return info; }}/** * 用于登录校验 */@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {UsernamePasswordToken token = (UsernamePasswordToken) authcToken; //伪代码 //第二个参数是从数据库中获取到的用户密码(或者密码的MD5),交给shiro去进行校验 return new SimpleAuthenticationInfo(username, user.getPassword(),getName()); } return null; }}
关于shiro和spring扫描和注解方式认证权限的过程就到这里了,当然我只是说明了部分原理,大家如果有我没有讲到的地方或者是更好的资料,希望能够分享给我,一起学习进步
- spring对shiro注解支持的原理
- spring对shiro注解支持的原理
- Spring Cache注解及对ehcache的支持
- 升级shiro对quartz 2的支持
- 升级shiro对quartz 2的支持
- 升级shiro对quartz 2的支持
- 【Spring进阶】spring对AOP的支持-注解方式和配置方式
- [Spring]支持注解的Spring调度器
- [Spring]支持注解的Spring调度器
- Spring注解的实现原理
- spring中基于java的容器注解,对JSR的支持(13)
- 对Spring的原理
- shiro直接对类进行注解,类似于@Controller的形式
- shiro与spring整合详解与spring项目中shiro注解不生效的解决办法
- spring集成shiro注解授权
- Servlet3.0对注解的支持
- struts2 对annotation(注解)的支持
- spring对Junit的支持、spring对Aop的支持
- ShadowSocks 代理设置,超有用!
- 虚拟桌面
- [Elasticsearch] 向已存在的索引中添加自定义filter/analyzer
- 正则表达式去除粘贴代码行号
- 【思路题】【多校第一场】【1001.OO’s Sequence】
- spring对shiro注解支持的原理
- STM32CUBE——7 DS18B20
- C语言之函数调用08—暴力法求4个数的最大公约数和最小公倍数
- 黑马程序员—IOS基础之OC—block和protocol
- 黑马程序员—IOS基础之C语言—数组与指针
- 初始化列表和构造函数
- hadoop
- windows服务器因多用户登陆文件夹权限更改后不能正常删除
- Swift的一些特点,重要概念和应用