@wxf
2017-11-07T09:12:29.000000Z
字数 10204
阅读 1015
项目实战
在上面搭建好的框架基础上整合权限模块。首先添加shiro-all
jar包,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的名称
@Override
public void setName(String name) {
super.setName("customRealm");
}
// 用于认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
// token是用户输入的username和password
// 1. 从token中取出身份信息
String userCode = (String) token.getPrincipal();
// 2.根据用户输入的userCode从数据库查询m,如果查询不到返回null
// 3.模拟从数据库查询到密码
String password = "111111";
// 如果查询到返回认证信息AuthenticationInfo
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
userCode, password, this.getName());
return simpleAuthenticationInfo;
}
// 用于授权
@Override
protected 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" />
<defaultCache
maxElementsInMemory="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>