Java 权限框架 Shiro 实战二:与spring集成、filter机制
2015-07-04 18:23
701 查看
Shiro和Spring的集成,涉及到很多相关的配置,涉及到shiro的filer机制以及它拥有的各种默认filter,涉及到shiro的权限判断标签,权限注解,涉及到session管理等等方面。
1. 配置
首先需要在web.xml中专门负责接入shiro的filter:
并且需要放在所有filter中靠前的位置,比如需要放在siteMesh的过滤器之前。
DelegatingFilterProxy 表示这是一个代理filter,它会将实际的工作,交给spring配置文件中 id="shiroFilter" 的bean来处理:
// Let subclasses do whatever initialization they like.
initFilterBean();
Filter 接口的 init 方法调用 initFilterBean(), 而该方法在子类中进行实现,它先获得 this.targetBeanName = getFilterName(); bean的名称,也就是id,然后对其进行初始化:this.delegate = initDelegate(wac); 其实就是从bean工厂中根据bean的名称找到bean.
而 shiroFilter在spring中的配置如下:
上面的shiroFilter的配置又引出了 securityManager 和 shiro 的filter机制和他自带的一些filter.
2. securityManager 级相关配置
在上一篇文章 Java 权限框架 Shiro 实战一:理论基础 中我们知道securityManager是shiro的顶层对象,它管理和调用其它所有子系统,负责系统的安全。我们知道shiro有两个类型的securityManager:一个是JavaSE环境,默认是DefaultSecurityManager;一个是web环境,默认是DefaultWebSecurityManager。所以我们web环境肯定应该使用后者。我们从顶层对象一层一层向下配置。先看securityManager如何配置:
上面的配置相当于调用SecurityUtils.setSecurityManager(securityManager) ,来注入了下面配置的 securityManager(DefaultWebSecurityManager) :
它默认使用的session管理器是 ServletContainerSessionManager,所以上面没有配置,所以就使用默认值。配置了就会覆盖下面的默认值:
显然 securityManager 最重要的工作就是用户登录认证和获得用户的权限等相关信息,所以 realm 是其最重要的依赖:
要理解上面userRealm的配置,就的先理解 UserRealm 的继承体系:
View Code
效果是根据用户拥有的角色,来显示左侧有哪些菜单项。
5. shiro 权限注解的使用
shiro对权限的控制,除了前面给出的在 shiroFilter这个bean中配置的过滤器:
之外,最重要的就是使用注解的方式来进行访问控制的实现了。shiro权限注解可以达到方法级别的细腻控制,可以控制具有某些权限或者某些角色的用户才能访问某个方法(某个url)。先要开启shiro权限注解功能,开启方法参见文档:http://shiro.apache.org/spring.html
Here is how to enable these annotations. Just add these two bean definitions to applicationContext.xml:
开启shiro权限注解的方法二:
<aop:config /> 表示开启spring注解,而 DefaultAdvisorAutoProxyCreator 表示会自动创建代理。但是二者最好不要同时使用。
AuthorizationAttributeSourceAdvisor 通过其依赖的 securityManager 来获取用户的角色和权限信息,进而可以进行权限判断。
支持的shiro注解有:
RequiresPermissions, RequiresRoles, RequiresUser, RequiresGuest, RequiresAuthentication
主要是通过: AopAllianceAnnotationsAuthorizingMethodInterceptor 类来实现的:
上面注入了注解的拦截器实现。具体的拦截判断权限过程实现如下:
主要是上面的方法 assertAuthorized(Annotation a) 中来实现对用户是否拥有某些角色进行判断的。其实还是很简单的。
shiro权限注解使用方法如下所示:
@RequiresPermissions(value={"user:update", "user:select"}, logical= Logical.AND)
表示必须有 对 user 表的同时拥有 查询和更新权限,才能修改密码。
1. 配置
首先需要在web.xml中专门负责接入shiro的filter:
<!-- shiro 安全过滤器 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <async-supported>true</async-supported> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
并且需要放在所有filter中靠前的位置,比如需要放在siteMesh的过滤器之前。
DelegatingFilterProxy 表示这是一个代理filter,它会将实际的工作,交给spring配置文件中 id="shiroFilter" 的bean来处理:
public class DelegatingFilterProxy extends GenericFilterBean { private String contextAttribute; private WebApplicationContext webApplicationContext; private String targetBeanName; private boolean targetFilterLifecycle = false; private volatile Filter delegate; private final Object delegateMonitor = new Object(); @Override protected void initFilterBean() throws ServletException { synchronized (this.delegateMonitor) { if (this.delegate == null) { // If no target bean name specified, use filter name. if (this.targetBeanName == null) { this.targetBeanName = getFilterName(); } // Fetch Spring root application context and initialize the delegate early, // if possible. If the root application context will be started after this // filter proxy, we'll have to resort to lazy initialization. WebApplicationContext wac = findWebApplicationContext(); if (wac != null) { this.delegate = initDelegate(wac); } } } }
public abstract class GenericFilterBean implements Filter, BeanNameAware, EnvironmentAware, ServletContextAware, InitializingBean, DisposableBean { @Override public final void init(FilterConfig filterConfig) throws ServletException { Assert.notNull(filterConfig, "FilterConfig must not be null"); if (logger.isDebugEnabled()) { logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'"); } this.filterConfig = filterConfig; // Set bean properties from init parameters. try { PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties); BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment)); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException ex) { String msg = "Failed to set bean properties on filter '" + filterConfig.getFilterName() + "': " + ex.getMessage(); logger.error(msg, ex); throw new NestedServletException(msg, ex); } // Let subclasses do whatever initialization they like. initFilterBean(); if (logger.isDebugEnabled()) { logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully"); } }
// Let subclasses do whatever initialization they like.
initFilterBean();
Filter 接口的 init 方法调用 initFilterBean(), 而该方法在子类中进行实现,它先获得 this.targetBeanName = getFilterName(); bean的名称,也就是id,然后对其进行初始化:this.delegate = initDelegate(wac); 其实就是从bean工厂中根据bean的名称找到bean.
protected Filter initDelegate(WebApplicationContext wac) throws ServletException { Filter delegate = wac.getBean(getTargetBeanName(), Filter.class); if (isTargetFilterLifecycle()) { delegate.init(getFilterConfig()); } return delegate; }
而 shiroFilter在spring中的配置如下:
<!-- Shiro的Web过滤器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login"/> <property name="successUrl" value="/"/> <property name="unauthorizedUrl" value="/unauthorized"/> <property name="filters"> <util:map> <entry key="authc" value-ref="passThruAuthenticationFilter"/> </util:map> </property> <property name="filterChainDefinitions"> <value> /reg/** = anon <!-- 注册相关 --> /login = authc /logout = logout /authenticated = authc /loginController = anon /js/** = anon /css/** = anon /img/** = anon /html/** = anon /font-awesome/** = anon <!-- /** = anon /user/modifyPassword = perms["user:update", "user:select"] --> /** = user </value> </property> </bean>
上面的shiroFilter的配置又引出了 securityManager 和 shiro 的filter机制和他自带的一些filter.
2. securityManager 级相关配置
在上一篇文章 Java 权限框架 Shiro 实战一:理论基础 中我们知道securityManager是shiro的顶层对象,它管理和调用其它所有子系统,负责系统的安全。我们知道shiro有两个类型的securityManager:一个是JavaSE环境,默认是DefaultSecurityManager;一个是web环境,默认是DefaultWebSecurityManager。所以我们web环境肯定应该使用后者。我们从顶层对象一层一层向下配置。先看securityManager如何配置:
<!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) --> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/> <property name="arguments" ref="securityManager"/> </bean>
上面的配置相当于调用SecurityUtils.setSecurityManager(securityManager) ,来注入了下面配置的 securityManager(DefaultWebSecurityManager) :
<!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="userRealm"/> <property name="cacheManager" ref="cacheManager"/> <property name="rememberMeManager" ref="rememberMeManager"/> </bean>
它默认使用的session管理器是 ServletContainerSessionManager,所以上面没有配置,所以就使用默认值。配置了就会覆盖下面的默认值:
public DefaultWebSecurityManager() { super(); ((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator()); this.sessionMode = HTTP_SESSION_MODE; setSubjectFactory(new DefaultWebSubjectFactory()); setRememberMeManager(new CookieRememberMeManager()); setSessionManager(new ServletContainerSessionManager()); }
显然 securityManager 最重要的工作就是用户登录认证和获得用户的权限等相关信息,所以 realm 是其最重要的依赖:
<!-- Realm实现 --> <bean id="userRealm" class="com.ems.shiro.UserRealm"> <property name="credentialsMatcher" ref="credentialsMatcher"/> <property name="cachingEnabled" value="false"/> </bean>
要理解上面userRealm的配置,就的先理解 UserRealm 的继承体系:
<!--sidebar-menu--> <div id="sidebar"><a href="javascript:;" class="visible-phone"><i class="icon icon-home"></i> Dashboard</a> <ul> <shiro:hasAnyRoles name="student,teacher"> <li id="li_queryScore"><a href="${ctx}/user/queryScore"><i class="icon icon-home"></i><span>查询成绩</span></a></li> </shiro:hasAnyRoles> <shiro:hasAnyRoles name="teacher,admin"> <li id="li_showStudentInfo"><a href="${ctx}/student/showStudentInfo"><i class="icon icon-home"></i><span>查询学生信息</span></a></li> </shiro:hasAnyRoles> <shiro:hasAnyRoles name="admin"> <li id="li_showTeacherInfo"><a href="${ctx}/teacher/showTeacherInfo"><i class="icon icon-home"></i><span>查询教师信息</span></a></li> </shiro:hasAnyRoles> <shiro:hasAnyRoles name="admin"> <li id="li_getStatistic"><a href="${ctx}/statistics/getStatistic"><i class="icon icon-th"></i><span>统计</span></a></li> </shiro:hasAnyRoles> <shiro:hasAnyRoles name="student,teacher,admin"> <li id="li_password"><a href="${ctx}/user/password"><i class="icon icon-inbox"></i><span>密码修改</span></a></li> </shiro:hasAnyRoles> <shiro:hasRole name="admin"> <li id="li_showPrivilege"><a href="${ctx}/priv/showPrivilege"><i class="icon icon-fullscreen"></i><span>权限设置</span></a></li> </shiro:hasRole> <shiro:hasAnyRoles name="teacher"> <li id="li_scoreRatio"><a href="${ctx}/set/scoreRatio"><i class="icon icon-tint"></i><span>成绩比例设置</span></a></li> </shiro:hasAnyRoles> <shiro:hasAnyRoles name="admin"> <li id="li_getSetting"><a href="${ctx}/set/getSetting"><i class="icon icon-tint"></i><span>成绩录入时间设置</span></a></li> </shiro:hasAnyRoles> <shiro:hasAnyRoles name="student,teacher"> <li id="li_queryReExam"><a href="${ctx}/user/queryReExam"><i class="icon icon-pencil"></i><span>补考名单</span></a></li> <li id="li_queryReLearn"><a href="${ctx}/user/queryReLearn"><i class="icon icon-pencil"></i><span>重修名单</span></a></li> </shiro:hasAnyRoles> </ul> </div> <!--sidebar-menu-->
View Code
效果是根据用户拥有的角色,来显示左侧有哪些菜单项。
5. shiro 权限注解的使用
shiro对权限的控制,除了前面给出的在 shiroFilter这个bean中配置的过滤器:
<property name="filterChainDefinitions"> <value> /reg/** = anon <!-- 注册相关 --> /login = authc /logout = logout /loginController = anon /js/** = anon /css/** = anon /img/** = anon /html/** = anon /font-awesome/** = anon /** = user </value> </property>
之外,最重要的就是使用注解的方式来进行访问控制的实现了。shiro权限注解可以达到方法级别的细腻控制,可以控制具有某些权限或者某些角色的用户才能访问某个方法(某个url)。先要开启shiro权限注解功能,开启方法参见文档:http://shiro.apache.org/spring.html
Here is how to enable these annotations. Just add these two bean definitions to applicationContext.xml:
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- 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>
开启shiro权限注解的方法二:
<aop:config /> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean>
<aop:config /> 表示开启spring注解,而 DefaultAdvisorAutoProxyCreator 表示会自动创建代理。但是二者最好不要同时使用。
AuthorizationAttributeSourceAdvisor 通过其依赖的 securityManager 来获取用户的角色和权限信息,进而可以进行权限判断。
支持的shiro注解有:
@SuppressWarnings({"unchecked"}) 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()); }
RequiresPermissions, RequiresRoles, RequiresUser, RequiresGuest, RequiresAuthentication
主要是通过: AopAllianceAnnotationsAuthorizingMethodInterceptor 类来实现的:
public class AopAllianceAnnotationsAuthorizingMethodInterceptor extends AnnotationsAuthorizingMethodInterceptor implements MethodInterceptor{ 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); }
上面注入了注解的拦截器实现。具体的拦截判断权限过程实现如下:
public class RoleAnnotationHandler extends AuthorizingAnnotationHandler { public RoleAnnotationHandler() { super(RequiresRoles.class); } public void assertAuthorized(Annotation a) throws AuthorizationException { if (!(a instanceof RequiresRoles)) return; RequiresRoles rrAnnotation = (RequiresRoles) a; String[] roles = rrAnnotation.value(); if (roles.length == 1) { getSubject().checkRole(roles[0]); return; } if (Logical.AND.equals(rrAnnotation.logical())) { getSubject().checkRoles(Arrays.asList(roles)); return; } if (Logical.OR.equals(rrAnnotation.logical())) { // Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first boolean hasAtLeastOneRole = false; for (String role : roles) if (getSubject().hasRole(role)) hasAtLeastOneRole = true; // Cause the exception if none of the role match, note that the exception message will be a bit misleading if (!hasAtLeastOneRole) getSubject().checkRole(roles[0]); } } }
主要是上面的方法 assertAuthorized(Annotation a) 中来实现对用户是否拥有某些角色进行判断的。其实还是很简单的。
shiro权限注解使用方法如下所示:
@RequiresPermissions(value={"user:update", "user:select"}, logical= Logical.AND) @RequestMapping(value="/modifyPassword", method=RequestMethod.POST) @ResponseBody public Map<String, String> modifyPassword(String oldPassword, String newPassword, HttpSession session) { Map<String, String> map = new HashMap<>(); if(oldPassword == null || newPassword == null || newPassword.length() < 8 || newPassword.length() > 32){ map.put("result", "error"); map.put("msg", "密码必须在8到20位之间"); return map; } User user = (User)session.getAttribute(ConstantConfig.LONGIN_USER); if(user != null){ PasswordHelper ph = new PasswordHelper(); if(!ph.checkPasswordAndEncryptPassword(oldPassword, user)){ // 判断输入的 oldPassword是否正确 map.put("result", "error"); map.put("msg", "密码错误"); return map; }else{ user.setPassword(newPassword); ph.encryptPassword(user); int result = this.userService.updateUserById(user); if(result > 0){ map.put("result", "ok"); map.put("msg", "密码修改成功,请重新登录"); }else{ map.put("result", "error"); map.put("msg", "密码修改失败"); } return map; } } return map; }
@RequiresPermissions(value={"user:update", "user:select"}, logical= Logical.AND)
表示必须有 对 user 表的同时拥有 查询和更新权限,才能修改密码。
相关文章推荐
- JavaWeb学习笔记——开发动态WEB资源(五)servlet身份验证
- 2015070408 - EffactiveJava笔记 - 第60条 优先使用标准异常
- java策略模式
- java 保留2位小数
- Java命名规则
- Java String,Unicode,byte[],16进制的字符串互转
- 数组中的逆序对 (java实现)
- java web之Filter详解
- 音乐播放器之myeclipse项目
- Spring MVC讲解
- Java之单例模式
- Think In java 笔记一
- 叫号系统排队系统挂号系统实现
- [JavaEE] JBoss主要版本下载链接一览
- 开始玩hadoop7--hadoop 2.6.0 在eclipse里安装(第一个map程序)
- Spring 系列: Spring 框架简介
- Eclipse Push出现rejected - non-fast-forward错误
- Java中static、final用法小结
- 关于Struts2
- JAVA中的继承和覆盖