@wxf
2017-11-07T01:12:29.000000Z
字数 10204
阅读 1227
项目实战
在上面搭建好的框架基础上整合权限模块。首先添加shiro-alljar包,jar包地址如下:
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-all</artifactId><version>1.3.2</version></dependency>
在web系统中,shiro是通过filter进行拦截。所以需要在web.xml中添加shiroFilter过滤器,配置信息如下:
<!-- 配置shiro的filter --><filter><filter-name>shiroFilter</filter-name><!-- shiro过滤器,DelegatingFilterProxy通过代理模式将spring容器汇中的bean和filter关联起来 --><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class><!-- 设置true,表示由servlet容器控制filter的生命周期 --><init-param><param-name>targetFilterLifecycle</param-name><param-value>true</param-value></init-param><!--<init-param>--><!--<param-name>transformWsdlLocations</param-name>--><!--<param-value>true</param-value>--><!--</init-param>--><!-- 设置spring容器filter的bean id,如果不设置则找与filter-name一致的bean --><init-param><param-name>targetBeanName</param-name><param-value>shiroFilter</param-value></init-param></filter><filter-mapping><filter-name>shiroFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>
DelegatingFilterProxy 实际上是 Filter 的一个代理对象. 默认情况下, Spring 会到 IOC 容器中查找和
<filter-name>对应的 filter Bean。也可以通过targetBeanName的初始化参数来配置 filter Bean 的 id。其实简单来说就是:filter拦截后将操作权交给了在spring中配置的filterChain(过滤链)
创建applicationContext-shiro.xml
配置文件的头信息如下:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"></beans>
在application-shiro.xml中需要配置的信息主要包括:shiro的web过滤器①、web.xml中shiroFilter对应的bean、SecurityManager、realm等
shiro的web过滤器
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><property name="securityManager" ref="securityManager" /><!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 --><property name="loginUrl" value="/login.action" /><!-- 认证成功统一跳转到first.action,建议不配置,shiro认证成功自动到上一个请求路径 --><!-- <property name="successUrl" value="/first.action"/>--><!-- 通过unauthorizedUrl指定没有权限操作时跳转页面--><property name="unauthorizedUrl" value="/refuse.jsp" /><!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 --><property name="filterChainDefinitions"><value><!-- 静态资源匿名访问 -->/images/** = anon/js/** = anon/css/** = anon/** = anon</value></property></bean>
配置 SecurityManager安全管理器
SecurityManager的配置内容如下:
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><property name="realm" ref="customRealm" /></bean>
配置自定义的Realm
该自定义的Realm继承自org.apache.shiro.realm.AuthorizingRealm。
<bean id="customRealm" class="com.wxf.mmall.shiro.CustomRealm" />
-----以上配置就是shiro与spring整合时最基础的配置-----
使用FormAuthenticationFilter过滤器实现,参看过滤器源码得知原理如下:
1.认证时请求数据是如何被捕获的?
当用户没有认证时,请求会跳转到loginurl进行认证,然后FormAuthenticationFilter过滤器拦截并取出request请求中的username和password(两个参数名称是可以配置的)。
2.获得参数后过滤器是如何进行认证的?
FormAuthenticationFilter拦截器调用realm(CustomRealm)的doGetAuthenticationInfo并传入一个token(username和password),然后realm认证时根据username查询用户信息,如果查询不到realm返回null,FormAuthenticationFilter向request域中填充一个参数(记录异常信息)。
登录认证的实现
public class CustomRealm extends AuthorizingRealm{// 设置realm的名称@Overridepublic void setName(String name) {super.setName("customRealm");}// 用于认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// token是用户输入的username和password// 1. 从token中取出身份信息String userCode = (String) token.getPrincipal();// 2.根据用户输入的userCode从数据库查询m,如果查询不到返回null// 3.模拟从数据库查询到密码String password = "111111";// 如果查询到返回认证信息AuthenticationInfoSimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userCode, password, this.getName());return simpleAuthenticationInfo;}// 用于授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {return null;}}
/*1.登录提交地址和applicationContext-shiro.xml中配置的loginurl一致2.此方法不处理登录成功(认证成功),shiro认证成功会自动跳转到上一个请求路径*/@RequestMapping(value = "/login")public ModelAndView login(HttpServletRequest request) throws Exception {ModelAndView mv = new ModelAndView("login");//如果登录失败从request中获取认证异常信息,shiroLoginFailure就是shiro异常类的全限定名String exceptionClassName = request.getParameter("shiroLoginFailure");//根据shiro返回的异常类路径判断,抛出指定异常信息if(exceptionClassName != null){if(UnknownAccountException.class.getName().equals(exceptionClassName)){//最终会抛给异常处理器throw new CustomException("账号不存在");} else if(IncorrectCredentialsException.class.getName().equals(exceptionClassName)){throw new CustomException("用户名/密码错误");} else {throw new Exception();}}return mv;}
登录方法写好之后,接下来需要处理的问题就是怎样拦截请求。所以还要配置一个认证拦截器。
<!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 --><property name="filterChainDefinitions"><value><!-- 静态资源匿名访问 -->/images/** = anon/js/** = anon/css/** = anon<!-- 所有url都需要认证 -->/** = authc</value></property>
使用LogoutFilter退出登录
使用LogoutFilter退出时,不需要我们去实现退出,只要去访问一个退出的url(该url可以不存在)即可。LogoutFilter会拦截住退出请求,然后清除session。
<!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 --><property name="filterChainDefinitions"><value><!-- 静态资源匿名访问 -->/images/** = anon/js/** = anon/css/** = anon/logout = logout/** = authc</value></property>
实现方式
doGetAuthenticationInfo,设置认证信息 realm从数据库查询用户信息,将用户菜单、usercode、username等设置在SimpleAuthenticationInfo中。
// 用于认证protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String username = (String) token.getPrincipal();User user = userBusiness.findUserByUsername(username);if(user == null){return null;}String password = user.getPassword();String salt = user.getSalt();//用户身份信息ActiveUser activeUser = new ActiveUser();activeUser.setUserid(user.getId());activeUser.setUsername(user.getUsername());activeUser.setRealname(user.getRealname());List<Permission> menus = permissionBusiness.findMenusByUserId(user.getId());activeUser.setMenus(menus);return new SimpleAuthenticationInfo(activeUser, password, ByteSource.Util.bytes(salt), this.getName());}
从shiro的session中取出用户信息(ActiveUser对象)
Subject subject = SecurityUtils.getSubject();//获取身份信息ActiveUser activeUser = (ActiveUser) subject.getPrincipal();request.setAttribute("activeUser", activeUser);
<!-- 配置自定义的Realm --><bean id="customRealm" class="com.wxf.mmall.shiro.CustomRealm"><property name="credentialsMatcher" ref="credentialsMatcher" /></bean><!-- 设置凭证匹配器 --><bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"><property name="hashAlgorithmName" value="md5" /><property name="hashIterations" value="1" /></bean>
流程:
1.在applicationContext-shiro.xml中配置filter规则/items/queryItems = perms[item:query]
2.用户认证通过后,请求/items/queryItems
3.被PermissionAuthorizationFilter拦截,发现需要“item:query”权限
4.PermissionAuthorizationFilter拦截器会调用CustomRealm的doGetAuthorizationInfo获取数据库中正确的权限
5.PermissionAuthorizationFilter拦截器对“item:query”和从realm中获取的权限进行对比,如果“item:query”在realm返回的权限列表中,授权通过。
实现方式
doGetAuthorizationInfo,设置权限信息 realm从数据库查询用户的权限信息
// 用于授权protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();List<String> permissions = new ArrayList<String>();List<Permission> permissionList = permissionBusiness.findPermissionsByUserId(activeUser.getUserid());for(Permission permission : permissionList){permissions.add(permission.getPercode());}SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();simpleAuthorizationInfo.addStringPermissions(permissions);return simpleAuthorizationInfo;}
<!-- 商品查询需要商品查询权限 -->/items/queryItems = perms[item:query]
问题总结
实现方式
在springmvc.xml中配置shiro注解支持,可以在controller方法中使用shiro注解配置权限:
<!-- 开启aop,对类实现代理 --><aop:config proxy-target-class="true"></aop:config><!-- 开启shiro注解支持 --><bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"><property name="securityManager" ref="securityManager" /></bean>
@RequiresPermissions("login:login") //执行此方法时需要“login:login”权限
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>
| 标签名称 | 标签条件 |
|---|---|
<shiro:authenticated> |
登录之后 |
<shiro:notAuthenticated> |
不在登录状态时 |
<shiro:guest> |
用户在没有RememberMe时 |
<shiro:user> |
用户在RememberMe时 |
<shiro:hasAnyRoles name="abc,123" > |
在有abc或者123角色时 |
<shiro:hasRole name="abc"> |
拥有角色abc |
<shiro:lacksRole name="abc"> |
没有角色abc |
<shiro:hasPermission name="abc"> |
拥有权限资源abc |
<shiro:lacksPermission name="abc"> |
没有abc权限资源 |
<shiro:principal property="username"/> |
显示用户身份中的属性值 |
shiro中提供了对认证信息和授权信息的缓存。shiro默认是关闭认证信息缓存的。对于授权信息的缓存默认是开启的。授权信息的缓存作为主要研 究对象。在这里我们使用ehcache进行数据缓存。
添加ehcache的jar包
maven坐标
配置缓存管理器
<!-- 配置 SecurityManager 安全管理器 --><bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><property name="realm" ref="customRealm" /><!-- 注入缓存管理器 --><property name="cacheManager" ref="cacheManager" /></bean><!-- 缓存管理器 --><bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"><property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml" /></bean>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"><!--diskStore:缓存数据持久化的目录 地址 --><diskStore path="F:\develop\ehcache" /><defaultCachemaxElementsInMemory="1000"maxElementsOnDisk="10000000"eternal="false"overflowToDisk="false"diskPersistent="false"timeToIdleSeconds="120"timeToLiveSeconds="120"diskExpiryThreadIntervalSeconds="120"memoryStoreEvictionPolicy="LRU"></defaultCache></ehcache>
1.如果用户正常退出,缓存自动清除。
2.如果用户非正常退出,缓存自动清空。
3.如果修改了用户的权限,而用户不退出系统,修改的权限无法立即生效。若要实现立即生效功能,需要手动进行编程实现。方法如下:
在权限修改后调用realm的clearCache方法清除缓存。(下边的代码正常开发时要放在service中调用)
//清除缓存(该方法需要在realm中进行定义)public void clearCached() {PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();super.clearCache(principals);}
和shiro整合后,使用shiro的session管理,shiro提供sessionDao操作会话数据。
<!-- 配置 SecurityManager 安全管理器 --><bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><property name="realm" ref="customRealm" /><property name="cacheManager" ref="cacheManager" /><!-- 注入会话管理器 --><property name="sessionManager" ref="sessionManager"/></bean><!-- 会话管理器 --><bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"><property name="globalSessionTimeout" value="600000"/><property name="deleteInvalidSessions" value="true"/></bean>