该demo整合Shiro的相关配置参考开涛的博客
数据库表格相关设计
表格设计得比较简单,导航栏直接由角色表auth_role的角色描述vRoleDesc(父结点)和角色相关权限中的权限描述(标记为导航结点)vPermissionDesc(展开子项)组成。
Shiro相关设置
密码输入错误次数限制
//密码重试5次次数限制
public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher{
private Cache<String,AtomicInteger> passwordRetryCache;
public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager){
passwordRetryCache=cacheManager.getCache("passwordRetryCache");
}
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
String username=(String)token.getPrincipal();
AtomicInteger retryCount=passwordRetryCache.get(username);
if(retryCount==null){
retryCount=new AtomicInteger(0);
passwordRetryCache.put(username, retryCount);
}
if(retryCount.incrementAndGet()>5){
throw new ExcessiveAttemptsException();
}
boolean matches= super.doCredentialsMatch(token, info);//匹配密码
if(matches){
passwordRetryCache.remove(username);
}
return matches;
}
}
相关配置:
<!-- 凭证(密码)匹配器 -->
<bean id="credentialsMatcher" class="com.test.shiro.credentials.RetryLimitHashedCredentialsMatcher">
<constructor-arg ref="cacheManager"/>
<property name="hashAlgorithmName" value="md5"/><!--md5加密-->
<property name="hashIterations" value="2"/><!--加密迭代两次-->
<property name="storedCredentialsHexEncoded" value="true"/><!--为true时使用Hex编码(默认);false时使用Base64编码-->
</bean>
使用缓存实现为ehcache,相关配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="shiroCache" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd">
<diskStore path="java.io.tmpdir"/>
<cache name="passwordRetryCache"
maxEntriesLocalHeap="2000"
eternal="false"<!--非永久有效-->
timeToIdleSeconds="3600"<!-- 对象空闲时间,即60min后失效-->
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<!--...-->
</ehcache>
扩展AuthorizingRealm:用于从数据库抓取密码等相关验证信息和相关权限信息
public class UserRealm extends AuthorizingRealm{
@Autowired
private UserService userService;
//获取相关授权信息
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName=(String)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
authorizationInfo.setRoles(userService.findPermissionsByUserName(userName));//获取角色信息
authorizationInfo.setStringPermissions(userService.findPermissionsByUserName(userName));//获取权限信息
return authorizationInfo;
}
//获取身份验证信息
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName=(String)token.getPrincipal();
User user=userService.getUserByUserName(userName);//获取身份信息(密码和密码盐)
if(user==null){
throw new UnknownAccountException();
}
SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(
user.getUserName(),
user.getPassword(),
ByteSource.Util.bytes(user.getUserName()+user.getPasswordSalt()),
getName());
return authenticationInfo;
}
}
登录相关
扩展FormAuthenticationFilter:用于登录后获取用户导航栏,并将其存入session范围
public class WithNavibarFormAuthenticationFilter extends FormAuthenticationFilter {
@Autowired
private UserService userService;
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
ServletResponse response) throws Exception {
HttpServletRequest httpReq=(HttpServletRequest)request;
String userName=(String)SecurityUtils.getSubject().getPrincipal();
List navigationBar=userService.getNavigationBar(userName);
httpReq.getSession().setAttribute("navibar", navigationBar);
return super.onLoginSuccess(token, subject, request, response);
}
}
登录Controller实现(用户密码不匹配情况下执行)
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
//...
@RequestMapping("/login")
public ModelAndView login(HttpServletRequest req){
String error=null;
String exceptionClassName = (String)req.getAttribute("shiroLoginFailure");
if(UnknownAccountException.class.getName().equals(exceptionClassName)) {
error = "用户名/密码错误";
} else if(IncorrectCredentialsException.class.getName().equals(exceptionClassName)) {
error = "用户名/密码错误";
} else if(exceptionClassName != null) {
error = "其他错误:" + exceptionClassName;
}
ModelAndView mav=new ModelAndView("login");
mav.addObject("error", error);
return mav;
}
//...
}
登录表单代码:注意提交action=”“,以及输入控件name值须与form表单过滤器中的设置对应
<form class="form-signin" method="post" action="">
<h3 class="form-signin-heading">请登录</h3>
<label for="inputEmail" class="sr-only">用户名</label>
<input type="text" id="inputEmail" class="form-control" placeholder="用户名" name="username" required autofocus>
<label for="inputPassword" class="sr-only">密码</label>
<input type="password" id="inputPassword" class="form-control" placeholder="密码" name="password" required>
<div class="checkbox">
<label>
<input type="checkbox" name="rememberMe"> 记住我
</label>
</div>
<p class="bg-warning">${error}</p>
<button class="btn btn-lg btn-primary btn-block" type="submit">登录</button>
</form>
最后通过Spring注解,控制访问
@RequiresPermissions("user:list")
@RequestMapping("/list")
public ModelAndView showUserList(){
//
}
完整shiro配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
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
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!-- 缓存管理器 使用Ehcache实现 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:config/ehcache.xml"/>
</bean>
<!-- 凭证(密码)匹配器 -->
<bean id="credentialsMatcher" class="com.test.shiro.credentials.RetryLimitHashedCredentialsMatcher">
<constructor-arg ref="cacheManager"/>
<property name="hashAlgorithmName" value="md5"/>
<property name="hashIterations" value="2"/>
<property name="storedCredentialsHexEncoded" value="true"/>
</bean>
<!-- Realm实现 -->
<bean id="userRealm" class="com.test.shiro.realm.UserRealm">
<property name="credentialsMatcher" ref="credentialsMatcher"/>
<property name="cachingEnabled" value="true"/>
<property name="authenticationCachingEnabled" value="true"/>
<property name="authenticationCacheName" value="authenticationCache"/>
<property name="authorizationCachingEnabled" value="true"/>
<property name="authorizationCacheName" value="authorizationCache"/>
</bean>
<!-- 会话ID生成器 -->
<bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>
<!-- 会话Cookie模板 -->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="sid"/>
<property name="httpOnly" value="true"/>
<property name="maxAge" value="180000"/>
</bean>
<!-- 会话DAO -->
<bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
<property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>
<property name="sessionIdGenerator" ref="sessionIdGenerator"/>
</bean>
<!-- 会话验证调度器 -->
<bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">
<property name="sessionValidationInterval" value="1800000"/>
<property name="sessionManager" ref="sessionManager"/>
</bean>
<!-- 会话管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="globalSessionTimeout" value="1800000"/>
<property name="deleteInvalidSessions" value="true"/>
<property name="sessionValidationSchedulerEnabled" value="true"/>
<property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
<property name="sessionDAO" ref="sessionDAO"/>
<property name="sessionIdCookieEnabled" value="true"/>
<property name="sessionIdCookie" ref="sessionIdCookie"/>
</bean>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm"/>
<property name="sessionManager" ref="sessionManager"/>