shiro + mybatis+ spring (只用shiro的密碼校驗和併發剔除)——不用許可權之類
阿新 • • 發佈:2019-02-13
</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>