http://blog.csdn.net/changliangwl/article/details/51455833
shiro + mybatis+ spring (只用shiro的密码校验和并发剔除)——不用权限之类
分类:
版权声明:本文为博主原创文章,未经博主允许不得转载。
- </pre> shiro 很强大,但往往项目不可能大改造,往往只需要部分功能,比如用到验证码,加密,还有就是同一个账户在两个地方登录,剔除第一个登录者,本文只提供思路和部 分代码,<p></p><p></p><p>自定义实现ream,< /p><p></p><pre name="code" class="java">package com.shiro.shiro.realm;
- import java.util.HashSet;
- import java.util.Set;
- import org.apache.shiro.authc.*;
- import org.apache.shiro.authz.AuthorizationInfo;
- import org.apache.shiro.authz.SimpleAuthorizationInfo;
- import org.apache.shiro.realm.AuthorizingRealm;
- import org.apache.shiro.subject.PrincipalCollection;
- import org.apache.shiro.util.ByteSource;
- import org.springframework.beans.factory.annotation.Autowired;
- import com.shiro.model.User;
- import com.shiro.service.UserService;
- public class UserRealm extends AuthorizingRealm {
- @Autowired
- private UserService userService;
- //获取授权信息
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
- SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
- <span style="font-size:18px;"> <span style="color:#ff0000;"><strong>Set<String> set =new HashSet<String>();
- set.add("*:*:*");</strong></span>
- <span style="color:#ff0000;"><strong>authorizationInfo.setRoles(set);
- authorizationInfo.setStringPermissions(set);
- return authorizationInfo;</strong></span></span>
- }
- //获取身份验证信息
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
- String username = (String)token.getPrincipal();
- User user = userService.findByUsername(username);
- if(user == null) {
- throw new UnknownAccountException();//没找到帐号
- }
- if(Boolean.TRUE.equals(user.getLocked())) {
- throw new LockedAccountException(); //帐号锁定
- }
- //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现
- SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
- user.getUsername(), //用户名
- user.getPassword(), //密码
- ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt
- getName() //realm name
- );
- return authenticationInfo;
- }
- @Override
- public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
- super.clearCachedAuthorizationInfo(principals);
- }
- @Override
- public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
- super.clearCachedAuthenticationInfo(principals);
- }
- @Override
- public void clearCache(PrincipalCollection principals) {
- super.clearCache(principals);
- }
- public void clearAllCachedAuthorizationInfo() {
- getAuthorizationCache().clear();
- }
- public void clearAllCachedAuthenticationInfo() {
- getAuthenticationCache().clear();
- }
- public void clearAllCache() {
- clearAllCachedAuthenticationInfo();
- clearAllCachedAuthorizationInfo();
- }
- }
- 只要在<pre name="code" class="java">doGetAuthorizationInfo方法体里授权所有资源所有角色就好了
- </pre><pre name="code" class="java">
- 剔除功能
- </pre><pre name="code" class="java"><pre name="code" class="java">package com.shiro.shiro.filter;
- import org.apache.shiro.cache.Cache;
- import org.apache.shiro.cache.CacheManager;
- import org.apache.shiro.session.Session;
- import org.apache.shiro.session.mgt.DefaultSessionKey;
- import org.apache.shiro.session.mgt.SessionManager;
- import org.apache.shiro.subject.Subject;
- import org.apache.shiro.web.filter.AccessControlFilter;
- import org.apache.shiro.web.util.WebUtils;
- import javax.servlet.ServletRequest;
- import javax.servlet.ServletResponse;
- import java.io.Serializable;
- import java.util.Deque;
- import java.util.LinkedList;
- /**
- * 并发剔除
- */
- public class KickoutSessionControlFilter extends AccessControlFilter {
- private String kickoutUrl; //踢出后到的地址
- private boolean kickoutAfter = false; //踢出之前登录的/之后登录的用户 默认踢出之前登录的用户
- private int maxSession = 1; //同一个帐号最大会话数 默认1
- private SessionManager sessionManager;
- private Cache<String, Deque<Serializable>> cache;
- public void setKickoutUrl(String kickoutUrl) {
- this.kickoutUrl = kickoutUrl;
- }
- public void setKickoutAfter(boolean kickoutAfter) {
- this.kickoutAfter = kickoutAfter;
- }
- public void setMaxSession(int maxSession) {
- this.maxSession = maxSession;
- }
- public void setSessionManager(SessionManager sessionManager) {
- this.sessionManager = sessionManager;
- }
- public void setCacheManager(CacheManager cacheManager) {
- this.cache = cacheManager.getCache("shiro-kickout-session");
- }
- @Override
- protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
- return false;
- }
- @Override
- protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
- Subject subject = getSubject(request, response);
- if(!subject.isAuthenticated() && !subject.isRemembered()) {//未授权且非remember
- //如果没有登录,直接进行之后的流程
- return true;
- }
- Session session = subject.getSession();
- String username = (String) subject.getPrincipal();
- Serializable sessionId = session.getId();
- //TODO 同步控制
- Deque<Serializable> deque = cache.get(username);
- if(deque == null) {
- deque = new LinkedList<Serializable>();
- cache.put(username, deque);
- }
- //如果队列里没有此sessionId,且用户没有被踢出;放入队列
- if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) {
- deque.push(sessionId);
- }
- //如果队列里的sessionId数超出最大会话数,开始踢人
- while(deque.size() > maxSession) {
- Serializable kickoutSessionId = null;
- if(kickoutAfter) { //如果踢出后者
- kickoutSessionId = deque.removeFirst();
- } else { //否则踢出前者
- kickoutSessionId = deque.removeLast();
- }
- try {
- Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));
- if(kickoutSession != null) {
- //设置会话的kickout属性表示踢出了
- kickoutSession.setAttribute("kickout", true);
- }
- } catch (Exception e) {//ignore exception
- }
- }
- //如果被踢出了,直接退出,重定向到踢出后的地址
- if (session.getAttribute("kickout") != null) {
- //会话被踢出了
- try {
- subject.logout();//这里走的shiro的退出
- } catch (Exception e) { //ignore
- }
- saveRequest(request);
- WebUtils.issueRedirect(request, response, kickoutUrl);
- return false;
- }
- return true;
- }
- }
此处用到了缓存,当然可以自己实现,把session存到数据库中然后判断操作
- </pre><pre name="code" class="java">再看下部分配置文件
- </pre><pre name="code" class="java">
- <pre name="code" class="html"><?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:util="http://www.springframework.org/schema/util"
- xmlns:aop="http://www.springframework.org/schema/aop"
- 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
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
- <!-- 缓存管理器 -->
- <bean id="cacheManager" class="com.shiro.spring.cache.SpringCacheManagerWrapper">
- <property name="cacheManager" ref="springCacheManager"/>
- </bean>
- <!-- 凭证匹配器 -->
- <bean id="credentialsMatcher" class="com.shiro.shiro.credentials.CredentialsMatcher">
- <property name="hashAlgorithmName" value="md5"/>
- <property name="hashIterations" value="2"/>
- <property name="storedCredentialsHexEncoded" value="true"/>
- </bean>
- <!-- Realm实现 -->
- <bean id="userRealm" class="com.shiro.shiro.realm.UserRealm">
- <property name="credentialsMatcher" ref="credentialsMatcher"/>
- <property name="cachingEnabled" value="false"/>
- </bean>
- <!-- 会话ID生成器 -->
- <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>
- <!-- 会话Cookie模板 maxAge=-1表示浏览器关闭时失效此Cookie-->
- <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
- <constructor-arg value="sid"/>
- <property name="httpOnly" value="true"/>
- <property name="maxAge" value="-1"/>
- </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"/>
- <property name="cacheManager" ref="cacheManager"/>
- </bean>
- <!-- 相当于调用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>
- <!-- 基于Form表单的身份验证过滤器 -->
- <strong> <bean id="authcFilter" class="com.shiro.shiro.filter.MyFormAuthenticationFilter">
- <property name="usernameParam" value="username"/>
- <property name="passwordParam" value="password"/>
- <property name="failureKeyAttribute" value="shiroLoginFailure"/>
- </bean>
- <bean id="validateFilter" class="com.shiro.shiro.filter.ValidateFilter">
- <property name="verificationAbled" value="true"/>
- <property name="verificationParam" value="verificationParam"/>
- <property name="failureKeyAttribute" value="shiroLoginFailure"/>
- </bean></strong>
- <!-- Shiro的Web过滤器 -->
- <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
- <property name="securityManager" ref="securityManager"/>
- <property name="loginUrl" value="/login"/>
- <property name="filters">
- <util:map>
- <entry key="authc" value-ref="<strong>authcFilter</strong>"/>
- <entry key="sysUser" value-ref="sysUserFilter"/>
- <entry key="validateFilter" value-ref="<strong>validateFilter</strong>"/>
- <entry key="kickout" value-ref="<strong>kickoutSessionControlFilter</strong>"/>
- </util:map>
- </property>
- <property name="filterChainDefinitions">
- <value>
- /login = <strong>validateFilter</strong>,authc
- /logout = logout
- /authenticated = authc
- /** = <strong>kickout</strong>,user,sysUser
- </value>
- </property>
- </bean>
- <!-- currenuser -->
- <bean id="sysUserFilter" class="com.shiro.shiro.filter.SysUserFilter"/>
- <!-- 并发踢出
- kickoutAfter:是否踢出后来登录的,默认是false
- kickoutUrl:被踢出后重定向到的地址
- maxSession:同一个用户最大的会话数,默认1
- -->
- <bean id="kickoutSessionControlFilter" class="com.shiro.shiro.filter.KickoutSessionControlFilter">
- <property name="cacheManager" ref="cacheManager"/>
- <property name="sessionManager" ref="sessionManager"/>
- <property name="kickoutAfter" value="false"/>
- <property name="maxSession" value="2"/>
- <property name="kickoutUrl" value="/login?kickout=1"/>
- </bean>
- <!-- Shiro生命周期处理器-->
- <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
- </beans>
- </pre><pre name="code" class="java">自定义的
- </pre><pre name="code" class="java"><pre name="code" class="java">package com.shiro.shiro.filter;
- import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
- import javax.servlet.ServletRequest;
- import javax.servlet.ServletResponse;
- /**
- * 自定义shiro FormAuthenticationFilter 表单过滤器
- * @author changliang
- *
- */
- public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
- /**
- * 授权是否失败
- */
- @Override
- protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
- if(request.getAttribute(getFailureKeyAttribute()) != null) {
- return true;
- }
- return super.onAccessDenied(request, response, mappedValue);
- }
- }
- </pre>密码校验<pre>
- <pre name="code" class="java">package com.shiro.shiro.filter;
- import org.apache.shiro.web.filter.AccessControlFilter;
- import org.apache.shiro.web.util.WebUtils;
- import org.springframework.beans.factory.annotation.Autowired;
- import com.shiro.service.UserService;
- import javax.servlet.ServletRequest;
- import javax.servlet.ServletResponse;
- import javax.servlet.http.HttpServletRequest;
- /**
- * 验证码过滤器
- */
- public class ValidateFilter extends AccessControlFilter {
- @SuppressWarnings("unused")
- @Autowired
- private UserService userService;
- private boolean verificationAbled = true; //是否开启验证码支持
- @SuppressWarnings("unused")
- private String verificationParam = "verificationParam"; //前台提交的验证码参数名
- private String failureKeyAttribute = "shiroLoginFailure"; //验证码验证失败后存储到的属性名
- public void setVerificationAbled(boolean verificationAbled) {
- this.verificationAbled = verificationAbled;
- }
- public void setVerificationParam(String verificationParam) {
- this.verificationParam = verificationParam;
- }
- public void setFailureKeyAttribute(String failureKeyAttribute) {
- this.failureKeyAttribute = failureKeyAttribute;
- }
- @Override
- protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
- //1、设置验证码是否开启属性,页面可以根据该属性来决定是否显示验证码
- request.setAttribute("verificationAbled", verificationAbled);
- HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
- //2、判断验证码是否禁用 或不是表单提交(允许访问)
- if (verificationAbled == false || !"post".equalsIgnoreCase(httpServletRequest.getMethod())) {
- return true;
- }
- <span style="color:#ff0000;"><strong> //3、此时是表单提交,验证验证码是否正确
- //TODO 增加自己的验证码校验
- //return userService.verification(verificationParam);
- return true;</strong></span>
- }
- @Override
- protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
- //如果验证码失败了,存储失败key属性
- //在LoginController里过滤用到
- request.setAttribute(failureKeyAttribute, "<span style="color:#ff0000;">verification.error</span>");
- return true;
- }
- }
处理器
- </pre><pre name="code" class="java"><pre name="code" class="java">package com.shiro.controller;
- import org.apache.shiro.authc.IncorrectCredentialsException;
- import org.apache.shiro.authc.UnknownAccountException;
- import org.springframework.stereotype.Controller;
- import org.springframework.ui.Model;
- import org.springframework.web.bind.annotation.RequestMapping;
- import javax.servlet.http.HttpServletRequest;
- /**
- * 常见错误:
- * subject.login()
- * DisabledAccountException(禁用的帐号)、
- * LockedAccountException(锁定的帐号)、
- * UnknownAccountException(错误的帐号)、
- * ExcessiveAttemptsException(登录失败次数过多)、
- * IncorrectCredentialsException (错误的凭证)、
- * ExpiredCredentialsException(过期的凭证)
- * @author changliang
- *
- */
- @Controller
- public class LoginController {
- @RequestMapping(value = "/login" )
- public String showLoginForm(HttpServletRequest req, Model model) {
- String exceptionClassName = (String)req.getAttribute("shiroLoginFailure");
- String error = null;
- if(UnknownAccountException.class.getName().equals(exceptionClassName)) {
- error = "用户名/密码错误";
- } else if(IncorrectCredentialsException.class.getName().equals(exceptionClassName)) {
- error = "用户名/密码错误";//错误的凭证
- }else if("<span style="color:#ff0000;">verification.error</span>".equals(exceptionClassName)) {
- error = "验证码错误";
- } else if(exceptionClassName != null) {
- error = "其他错误:" + exceptionClassName;
- }
- model.addAttribute("error", error);
- return "login";
- }
- }
参考 :http://jinnianshilongnian.iteye.com/blog/2049092
- </pre><pre name="code" class="java">
- </pre><pre name="code" class="java">
- </pre><pre name="code" class="java">
- </pre><pre name="code" class="java">
- </pre>