Spring Authorization Server,Shiro 整合SpringMVC 并實現權限管理,登錄和注銷

 2023-11-07 阅读 25 评论 0

摘要:Shiro 整合SpringMVC 并且實現權限管理,登錄和注銷 Apache Shiro是Java的一個安全框架。目前,使用Apache Shiro的人越來越多,因為它相當簡單,對比Spring Security,可能沒有Spring Security做的功能強大,但是在實際工作時可能并不需

Shiro 整合SpringMVC 并且實現權限管理,登錄和注銷

Apache Shiro是Java的一個安全框架。目前,使用Apache Shiro的人越來越多,因為它相當簡單,對比Spring Security,可能沒有Spring Security做的功能強大,但是在實際工作時可能并不需要那么復雜的東西,所以使用小而簡單的Shiro就足夠了。

  因為我總結的是使用SpringMVC和Apache Shiro整合,注重的是整合和使用,至于基礎,我這里就不細說了。我使用的是maven進行項目的構建,對于非maven的項目只要把這些JAR包下載下來放到相應的位置即可。因為這個項目是整合Spring的,所以除了Apache shiro的JAR之外,我們還需要shiro-web和shiro-spring的的JAR,下面是所需要的所有shiro架包,至于其他的架包,像緩存的架包,Spring和SpringMVC的架包等等還是平時那些通用JAR,沒有多余的。

<dependency>  <groupId>org.apache.shiro</groupId>  <artifactId>shiro-core</artifactId>  <version>1.2.3</version>  </dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-ehcache</artifactId><version>1.2.3</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-web</artifactId><version>1.2.3</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.2.3</version></dependency>

Spring Authorization Server?將JAR都準備好了之后,我們就可以開始正式搭建了。下面就分步驟來創建

1.

一:首先創建spring的配置文件,位置都在resource中(非maven的項目可以放到classpath或者是WEB-INF下面,只要保證最后編譯之后能在classpath下即可),配置文件為spring-context.xml.

二:創建Apache Shiro的配置文件,名字是spring-context-shiro.xml,我們只需要和spring的配置文件放在同一級就可以了。

三:還有一個配置文件是springmvc的,配置文件是spring-mvc。前面兩個文件都是以spring-context*開頭是有原因的,因為這樣我們就可以在web.xml中設置配置文件的時候,直接使用通配符掃描前兩個但是又可以不掃描springmvc的配置文件

這是在web.xml里面配置:

<!-- 配置spring容器的路徑 --><context-param><param-name>contextConfigLocation</param-name><param-value>classpath*:/spring-context-*.xml</param-value></context-param><!-- 對spring開始監聽 --><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener>

除了spring的配置,還有一個配置是非常重要的:shiroFilter。對于初次配置shiro的同學經常遇到一個問題:問題大概講的是shiroFilter找不到,但是我們明明在web.xml和spring-context-shiro配置文件里面配置了呀,怎么回事?這是因為這個shiroFilter名字兩邊需要一致!!!(是不是很坑,但是其實是可以配置的,只是一般人不知道,這個后面講)

  <filter><filter-name>shiroFilter</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class></filter><filter-mapping><filter-name>shiroFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>

3.

除了在web.xml中設置spring和spring-shiro配置文件位置之外,我們還需要在web.xml中設置spring-mvc的位置:

<!-- MVC Servlet設置springmvc的Servlet--><servlet><servlet-name>springServlet</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:springmvc.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>springServlet</servlet-name><url-pattern>/</url-pattern></servlet-mapping>

4

在spring-context配置文件中,還有一個是需要配置-cacheManager,因為shiro的session是自己實現的,所以我們還需要一個緩存框架,所以在spring的配置文件一定要注意配置哦,用的是ehcache

   <!-- 緩存 --><bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"><property name="configLocation" value="classpath:${ehcache.file}"></property></bean>

Ehcache的maven地址:

 <dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache-core</artifactId><version>2.6.9</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-ehcache</artifactId><version>1.2.3</version></dependency>

5

在項目中重點還是配置spring-context-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"xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttp://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context-4.0.xsd"default-lazy-init="true"><description>Shiro Configuration</description><!-- 加載配置屬性文件 --><context:property-placeholder ignore-unresolvable="true" location="classpath:yonyou.properties" /><!-- Shiro權限過濾過濾器定義 --><bean name="shiroFilterChainDefinitions" class="java.lang.String"><constructor-arg><value>/static/** = anon/userfiles/** = anon${adminPath}/login = authc${adminPath}/logout = logout${adminPath}/** = user</value></constructor-arg></bean><!-- 安全認證過濾器 --><bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><property name="securityManager" ref="securityManager" /><!--<property name="loginUrl" value="${adminPath}/login" /><property name="successUrl" value="${adminPath}?login" /><property name="filters"><map><entry key="cas" value-ref="casFilter"/><entry key="authc" value-ref="formAuthenticationFilter"/></map></property><property name="filterChainDefinitions"><ref bean="shiroFilterChainDefinitions"/></property></bean><!-- 定義Shiro安全管理配置 --><bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><property name="realm" ref="systemAuthorizingRealm" /><property name="sessionManager" ref="sessionManager" /><property name="cacheManager" ref="shiroCacheManager" /></bean><!-- 自定義會話管理配置 --><bean id="sessionManager" class="com.yonyou.hotusm.common.security.session.SessionManager"> <property name="sessionDAO" ref="sessionDAO"/><!-- 會話超時時間,單位:毫秒  --><property name="globalSessionTimeout" value="${session.sessionTimeout}"/><!-- 定時清理失效會話, 清理用戶直接關閉瀏覽器造成的孤立會話   --><property name="sessionValidationInterval" value="${session.sessionTimeoutClean}"/>
<!--          <property name="sessionValidationSchedulerEnabled" value="false"/> --><property name="sessionValidationSchedulerEnabled" value="true"/><property name="sessionIdCookie" ref="sessionIdCookie"/><property name="sessionIdCookieEnabled" value="true"/></bean><!-- 指定本系統SESSIONID, 默認為: JSESSIONID 問題: 與SERVLET容器名沖突, 如JETTY, TOMCAT 等默認JSESSIONID,當跳出SHIRO SERVLET時如ERROR-PAGE容器會為JSESSIONID重新分配值導致登錄會話丟失! --><bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie"><constructor-arg name="name" value="hotusm.session.id"/></bean><bean id="sessionDAO" class="com.yonyou.hotusm.common.security.session.CacheSessionDAO"><property name="sessionIdGenerator" ref="idGen" /><property name="activeSessionsCacheName" value="activeSessionsCache" /><property name="cacheManager" ref="shiroCacheManager" /></bean><!-- 定義授權緩存管理器 -->
<!--     <bean id="shiroCacheManager" class="com.thinkgem.jeesite.common.security.shiro.cache.SessionCacheManager" /> --><bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"><property name="cacheManager" ref="cacheManager"/></bean><!-- 保證實現了Shiro內部lifecycle函數的bean執行 --><bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/><!-- AOP式方法級權限檢查  --><bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"><property name="proxyTargetClass" value="true" /></bean><bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"><property name="securityManager" ref="securityManager"/></bean></beans>

這里從上往下進行解釋:
1.shiroFilterChainDefinitions

可以看到類型是String,String內部的各個字符串是使用"\n\t"進行換行。這里的每一行代表了一個路由,而后面的anno,user等等,也就是相對應的Filter(這塊我們是可以自己定義的,后面會講,${adminPath} 是我在配置文件里面配置的路徑而已,完全可以根據自己的路由進行設置。shiroFilterChainDefinitions最主要是在shiroFilter中作為一個參數注入。

===============權限過濾器及配置釋義=======================

?

anon   org.apache.shiro.web.filter.authc.AnonymousFilterauthc  org.apache.shiro.web.filter.authc.FormAuthenticationFilterauthcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilterperms  org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilterport   org.apache.shiro.web.filter.authz.PortFilterrest   org.apache.shiro.web.filter.authz.HttpMethodPermissionFilterroles  org.apache.shiro.web.filter.authz.RolesAuthorizationFilterssl    org.apache.shiro.web.filter.authz.SslFilteruser   org.apache.shiro.web.filter.authc.UserFilterlogout org.apache.shiro.web.filter.authc.LogoutFilter

anon:例子/admins/**=anon?沒有參數,表示可以匿名使用。

authc:例如/admins/user/**=authc表示需要認證(登錄)才能使用,沒有參數

roles:例子/admins/user/**=roles[admin],參數可以寫多個,多個時必須加上引號,并且參數之間用逗號分割,當有多個參數時,例如admins/user/**=roles["admin,guest"],每個參數通過才算通過,相當于hasAllRoles()方法。

perms:例子/admins/user/**=perms[user:add:*],參數可以寫多個,多個時必須加上引號,并且參數之間用逗號分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],當有多個參數時必須每個參數都通過才通過,想當于isPermitedAll()方法。

rest:例子/admins/user/**=rest[user],根據請求的方法,相當于/admins/user/**=perms[user:method]?,其中method為post,get,delete等。

port:例子/admins/user/**=port[8081],當請求的url的端口不是8081是跳轉到schemal://serverName:8081?queryString,其中schmal是協議http或https等,serverName是你訪問的host,8081是url配置里port的端口,queryString

是你訪問的url里的?后面的參數。

authcBasic:例如/admins/user/**=authcBasic沒有參數表示httpBasic認證

ssl:例子/admins/user/**=ssl沒有參數,表示安全的url請求,協議為https

user:例如/admins/user/**=user沒有參數表示必須存在用戶,當登入操作時不做檢查

?

2.重點來了:shiroFilter(ShiroFilterFactoryBean),這里要非常小心!! 這里的bean的名字一定要和web.xml里面的那個Filter名字相同,具體可以見下面的源碼:

DelegatingFilterProxy.java:@Overrideprotected void initFilterBean() throws ServletException {synchronized (this.delegateMonitor) {if (this.delegate == null) {// If no target bean name specified, use filter name.if (this.targetBeanName == null) {this.targetBeanName = getFilterName();}// Fetch Spring root application context and initialize the delegate early,// if possible. If the root application context will be started after this// filter proxy, we'll have to resort to lazy initialization.WebApplicationContext wac = findWebApplicationContext();if (wac != null) {this.delegate = initDelegate(wac);}}}}

還記得我們web.xml里面配置的那個Filter嗎, 其實我們配置的Filter只不過是起到一個代理的作用,那么它代理誰呢? 它也不能知道,它所能做的就是根據targetBeanName去容器中獲取bean(這個bean是實現了Filter接口的),其中的targetBeanName就是bean的名稱,如果沒有設置的話,那么就默認使用的Filter名稱。所以說前面說過的必須相同是不正確的,你只需要在Filter中設置targetBeanName和spring-context-shiro配置文件中ShiroFilterFactoryBean的bean名稱一樣即可。

除了上面需要注意的幾個點之外,ShiroFilterFactoryBean還有一些屬性:unauthorizedUrl,系統未認證時跳轉的頁面,loginUrl登錄頁面,successUrl登錄成功的頁面,filter屬性就是和前面的shiroFilterChainDefinitions對應的。同時支持自定義,并且配置路由:像<entry key="outdate" value-ref="sessionOutDateFilter"/>這樣的。最底層是過濾器,下面是我實現的一個filter:

package com.yonyou.kms.common.security.shiro.session;import java.io.PrintWriter;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.apache.shiro.web.servlet.AdviceFilter;import com.yonyou.kms.modules.sys.security.SystemAuthorizingRealm.Principal;
import com.yonyou.kms.modules.sys.utils.UserUtils;/*** * 自定義filter* @author Hotusm**/
public class SessionOutDateFilter extends AdviceFilter{private String redirectUrl="http://url/portal";//session 失效之后需要跳轉的頁面private String platformUrl="http://url/kms/a/login";//排除這個鏈接 其他的鏈接都會進行攔截private String loginUrl="/kms/a/login";private String frontUrl="cms/f";private String uploadUrl="cms/article/plupload";private String appUrl="a/app";protected boolean preHandle(ServletRequest request, ServletResponse response){Principal principal = UserUtils.getPrincipal();HttpServletRequest req=(HttpServletRequest) request;String uri=req.getRequestURI();if(checkUrl(uri, loginUrl,frontUrl,uploadUrl,appUrl)|(principal!=null&&!principal.isMobileLogin())){return true;}try {issueRedirect(request,response,redirectUrl);} catch (Exception e) {e.printStackTrace();}return false;}protected void issueRedirect(ServletRequest request, ServletResponse response, String redirectUrl)throws Exception{      String url="<a href="+redirectUrl+" target=\"_blank\" οnclick=\"custom_close()\">重新登錄<a/> ";String platform="<a href="+platformUrl+" target=\"_blank\" οnclick=\"custom_close()\">直接登錄<a/> ";HttpServletResponse resp=(HttpServletResponse) response;HttpServletRequest req=(HttpServletRequest) request;response.setContentType("text/html;charset=UTF-8");PrintWriter out=resp.getWriter();out.print("<script language='javascript'>");out.print("function custom_close(){" +"self.opener=null;" +"self.close();}");out.print("</script>");out.print("沒有權限或者驗證信息過期,請點擊"+url+"登錄portal<br/>");out.print("直接登錄"+platform);}public String getRedirectUrl() {return redirectUrl;}public void setRedirectUrl(String redirectUrl) {this.redirectUrl = redirectUrl;}public String getLoginUrl() {return loginUrl;}public void setLoginUrl(String loginUrl) {this.loginUrl = loginUrl;}/*** 排除一些url不進行攔截* @param targetUrl* @param urls* @return*/private boolean checkUrl(String targetUrl,String ...urls){for(int i=0;i<urls.length;i++){if(targetUrl.contains(urls[i])){return true;}}return false;}
}

這個和springmvc的攔截器是相同的用法,返回true則表示驗證通過(后面的邏輯繼續執行),返回false就表示驗證不通過。

最后在shiroFilter的filters進行配置我們自定義的bean:

<property name="filters"><map><entry key="outdate" value-ref="sessionOutDateFilter"/></map></property>
這個sessionOutDateFilter我們需要注入(這里省略)。最后我們就將可以將這些東西加到shiroFilterChainDefinitions中去:
   <bean name="shiroFilterChainDefinitions" class="java.lang.String"><constructor-arg><value>......${adminPath}/** = outdate.....</value></constructor-arg></bean>        

這樣我們自己定義的叫做outdata的路由會攔截${adminPath}下的所以路徑,并且進行驗證。

3.

SecurityManager

它和我們前面講的ShiroFilterFactoryBean的關系形象的將就是ShiroFilterFactoryBean是一個路由規則配置倉庫和代理類,其實真正的邏輯都是在SecurityManager中進行的,下面來進行詳講SecurityManager的依賴類。

一:realm:域,Shiro從從Realm獲取安全數據(如用戶、角色、權限),就是說SecurityManager要驗證用戶身份,那么它需要從Realm獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應的角色/權限進行驗證用戶是否能進行操作;可以把Realm看成DataSource,即安全數據源,下面是我重寫的realm:

package com.yonyou.hotusm.module.sys.security;import com.yonyou.hotusm.common.utils.Encodes;
import com.yonyou.hotusm.module.sys.dao.UserDao;
import com.yonyou.hotusm.module.sys.entity.User;
import com.yonyou.hotusm.module.sys.service.UserService;
import com.yonyou.hotusm.module.sys.util.UserUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.io.Serializable;@Service("systemAuthorizingRealm")
public class SystemAuthorizingRealm extends AuthorizingRealm implements InitializingBean{@Autowiredprivate UserDao userDao;@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();info.addStringPermission("sys:manager");info.addStringPermission("user");System.out.println("開始授權");return info;}@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {UsernamePasswordToken upToken=(UsernamePasswordToken) token; String username=upToken.getUsername();User user=new User();user.setLoginName(username);user=userDao.get(user);if(user!=null){byte[] salt = Encodes.decodeHex(user.getPassword().substring(0,16));return new SimpleAuthenticationInfo(username, user.getPassword().substring(16), ByteSource.Util.bytes(salt), getName());}else{throw new UnauthenticatedException();}}public static class Principal implements Serializable {private static final long serialVersionUID = 1L;private String id; // 編號private String loginName; // 登錄名private String name; // 姓名public Principal(User user) {this.id = user.getId();this.loginName = user.getLoginName();this.name = user.getName();}public String getId() {return id;}public String getLoginName() {return loginName;}public String getName() {return name;}/*** 獲取SESSIONID*/public String getSessionid() {try{return (String) UserUtils.getSession().getId();}catch (Exception e) {return "";}}@Overridepublic String toString() {return id;}}//在bean初始化完成以后  設置校驗的規則public void afterPropertiesSet() throws Exception {HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(UserService.HASH_ALGORITHM);matcher.setHashIterations(UserService.HASH_INTERATIONS);setCredentialsMatcher(matcher);}}

其他的幾個類在這里不是重點,重要的是看這里面的邏輯。其中最重要的是doGetAuthorizationInfo和doGetAuthenticationInfo以及afterPropertiesSet這三個方法,doGetAuthorizationInfo是對當前的用戶進行授權的,至于授權的時期,就是當用戶需要驗證的時候(框架進行回調),我這里只是簡單的寫死了,但是在實際項目開發中,我們一般會將權限存放在數據表中,所以真實情況是先到數據庫中查出一個集合,然后迭代授權。

? ? doGetAuthenticationInfo對于的是對用戶驗證,主要的一個點在于我們最后返回的那個SimpleAuthenticationInfo,這個是加密的策略,這里的密碼是密文的(根據loginName數據中取得),下面是密碼的加密策略:

//為明文密碼加密public String encryptionPassword(String plainPassword){byte[] salt = Digests.generateSalt(SALT_SIZE);  //SALT_SIZE=8byte[] hashPassword = Digests.sha1(plainPassword.getBytes(), salt, HASH_INTERATIONS);  //HASH_INTERATIONS=1024
return Encodes.encodeHex(salt)+Encodes.encodeHex(hashPassword); }

我這里是生成了了16位的salt,然后用來加密明文,最后兩個加起來存入到數據中。根據上面說的,所以看到我們doGetAuthenticationInfo返回的是分開的兩部分。這里也需要注意,這個密碼最后的校驗我們做的,而是框架!我們只是提供了校驗類供它回調(下面我們使用的是默認的校驗類,我們也可以自定義):

  //在bean初始化完成以后  設置校驗的規則public void afterPropertiesSet() throws Exception {HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(UserService.HASH_ALGORITHM);matcher.setHashIterations(UserService.HASH_INTERATIONS);setCredentialsMatcher(matcher);

,那么在密碼進行驗證的時候,就會調用HashedCredentialsMatcher的

@Overridepublic boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {Object tokenHashedCredentials = hashProvidedCredentials(token, info);Object accountCredentials = getCredentials(info);return equals(tokenHashedCredentials, accountCredentials);}

方法,這個Info就是我們前面方法doGetAuthenticationInfo提供的,至于另外的一個Token,后面會講(也是一個方法提供的)。

4

下面就是講解SessionManager,因為Shiro有自己的一套session體系,有sessionManager就不奇怪了,sessionManager主要職責是管理session的創建和刪除,特別提一下,sessionManager對session的操作,其實只是調用了sessionDAO,然再加上自己的一些操作。

看源碼:

 
public class DefaultSessionManager extends AbstractValidatingSessionManager implements CacheManagerAware {//TODO - complete JavaDocprivate static final Logger log = LoggerFactory.getLogger(DefaultSessionManager.class);private SessionFactory sessionFactory;protected SessionDAO sessionDAO;  //todo - move SessionDAO up to AbstractValidatingSessionManager?private CacheManager cacheManager;private boolean deleteInvalidSessions;public DefaultSessionManager() {this.deleteInvalidSessions = true;this.sessionFactory = new SimpleSessionFactory();this.sessionDAO = new MemorySessionDAO();}
.......protected void create(Session session) {if (log.isDebugEnabled()) {log.debug("Creating new EIS record for new session instance [" + session + "]");}sessionDAO.create(session);}
 

這上面的源碼中就知道SessionManager就是對SessionDAO進行了代理的作用。

我們就明白了sessionManager依賴sessionDAO(后面實現自己的SessionDAO需要注入到SessionManager中),下面是自己實現的sessionManager:

 
package com.yonyou.hotusm.common.security.session;import org.apache.shiro.session.InvalidSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SessionContext;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.session.mgt.SimpleSession;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.util.Collection;
import java.util.Date;/**** * @author Hotusm*    v2015-11-04*/
public class SessionManager extends DefaultWebSessionManager{/**DefaultWebSessionManager 實現了DefaultSessionManager的功能 并在其上實現了web的功能* 也就是在上面實現了將SessionId 存到了Cookie中 * */@Overrideprotected Serializable getSessionId(ServletRequest request,ServletResponse response) {String sid=request.getParameter("_sid");if(org.apache.commons.lang.StringUtils.isNotBlank(sid)){if(WebUtils.isTrue(request, "_cookie")){HttpServletRequest req=(HttpServletRequest) request;HttpServletResponse resp=(HttpServletResponse) response;Cookie template=getSessionIdCookie();Cookie cookie=new SimpleCookie(template);cookie.setValue(sid);cookie.saveTo(req, resp);}request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sid);request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);return sid;}return super.getSessionId(request, response);}@Overrideprotected Session doCreateSession(SessionContext context) {try {return super.doCreateSession(context);} catch (Exception e) {return null;}}@Overrideprotected Session newSessionInstance(SessionContext context) {Session session=super.newSessionInstance(context);session.setTimeout(getGlobalSessionTimeout());return session;}@Overrideprotected Session retrieveSession(SessionKey sessionKey)throws UnknownSessionException {try {return super.retrieveSession(sessionKey);} catch (Exception e) {//獲取不到SESSION不報錯return null;}}@Overridepublic void validateSessions() {super.validateSessions();}@Overridepublic Session start(SessionContext context) {try {return super.start(context);} catch (Exception e) {SimpleSession session=new SimpleSession();session.setId(0);return session;}}@Overridepublic Date getStartTimestamp(SessionKey key) {try {return super.getStartTimestamp(key);} catch (Exception e) {return null;}}@Overridepublic Date getLastAccessTime(SessionKey key) {try {return super.getLastAccessTime(key);} catch (Exception e) {return null;}}@Overridepublic long getTimeout(SessionKey key) throws InvalidSessionException {try {return super.getTimeout(key);} catch (Exception e) {return 0;}}@Overridepublic void setTimeout(SessionKey key, long maxIdleTimeInMillis)throws InvalidSessionException {try {super.setTimeout(key, maxIdleTimeInMillis);} catch (Exception e) {}}@Overridepublic void touch(SessionKey key) throws InvalidSessionException {try {super.touch(key);} catch (Exception e) {}}@Overridepublic String getHost(SessionKey key) {try {return super.getHost(key);} catch (Exception e) {return null;}}@Overridepublic Collection<Object> getAttributeKeys(SessionKey key) {try {return super.getAttributeKeys(key);} catch (Exception e) {return null;}}@Overridepublic Object getAttribute(SessionKey sessionKey, Object attributeKey)throws InvalidSessionException {try {return super.getAttribute(sessionKey, attributeKey);} catch (Exception e) {return null;}}@Overridepublic Object removeAttribute(SessionKey sessionKey, Object attributeKey)throws InvalidSessionException {try {return super.removeAttribute(sessionKey, attributeKey);} catch (Exception e) {return null;}
//        
    }@Overridepublic void stop(SessionKey key) throws InvalidSessionException {try {super.stop(key);} catch (Exception e) {}}@Overridepublic void checkValid(SessionKey key) throws InvalidSessionException {try {super.checkValid(key);} catch (Exception e) {}} 
}
 

上面就是對session的操作.

5

還有就是sessionDAO了,這個sessionDAO才是真正對session操作的bean:

 
package com.yonyou.hotusm.common.security.session;import com.google.common.collect.Sets;
import com.yonyou.hotusm.common.config.Global;
import com.yonyou.hotusm.common.web.Servlets;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.Collection;
import java.util.Set;/*** @author Hotusm*         v-2015-10-28*/
public class CacheSessionDAO extends EnterpriseCacheSessionDAO implements SessionDAO {@Overrideprotected Serializable doCreate(Session session) {HttpServletRequest request = Servlets.getRequest();if (request != null) {String uri = request.getRequestURI();if (Servlets.isStaticFile(uri)) {return null;}}super.doCreate(session);//System.out.println("doCreate:"+" sessionId"+session.getId());return session.getId();}@Overridepublic Session readSession(Serializable sessionId) throws UnknownSessionException {//System.out.println("readSession:"+" sessionId"+sessionId);//System.out.println();try {Session s = null;HttpServletRequest request = Servlets.getRequest();if (request != null) {String uri = request.getRequestURI();if (Servlets.isStaticFile(uri)) {return null;}s = (Session) request.getAttribute("session_" + sessionId);}if (s != null) {return s;}Session session = super.readSession(sessionId);if (request != null && session != null) {request.setAttribute("session_" + sessionId, session);}return session;} catch (Exception e) {return null;}}@Overrideprotected Session doReadSession(Serializable sessionId) {//System.out.println("doReadSession:"+" sessionId"+sessionId);return super.doReadSession(sessionId);}@Overrideprotected void doUpdate(Session session) {
//        System.out.println("doUpdate"+" sessionId"+session.getId());if (session == null || session.getId() == null) {return;}HttpServletRequest request = Servlets.getRequest();if (request != null) {String uri = request.getRequestURI();if (Servlets.isStaticFile(uri)) {return;}if (org.apache.commons.lang.StringUtils.startsWith(uri, Global.getConfig("web.view.prefix"))&& org.apache.commons.lang.StringUtils.endsWith(uri, Global.getConfig("web.view.suffix"))) {return;}//手動控制不更新sessionString updateSession = request.getParameter("updateSession");if (Global.FALSE.equals(updateSession) || Global.NO.equals(updateSession)) {return;}}super.doUpdate(session);}@Overrideprotected void doDelete(Session session) {//System.out.println("doDelete");if (session == null || session.getId() == null) {return;}super.doUpdate(session);}public Collection<Session> getActiveSessions(boolean includeLeave) {return null;}public Collection<Session> getActiveSessions(boolean includeLeave,Object principal, Session filterSession) {if (includeLeave && principal == null) {return this.getActiveSessions();}Set<Session> sessions = Sets.newHashSet();for (Session session : getActiveSessions()) {boolean isActiveSession = true;}return null;}}
 

6.

看sessionDAO還有一個idGen依賴bean,指的是sessionId值的生成策略,這個bean也是自己定義的,但是需要繼承SessionIdGenerator:

public class IdGen implements SessionIdGenerator{private static SecureRandom secureRandom;/*** 封裝JDK自帶的UUID,通過random生成*/public static String uuid(){return UUID.randomUUID().toString().replace("-", "");}public static long randomLong(){return Math.abs(secureRandom.nextLong());}public Serializable generateId(Session session) {return IdGen.uuid();}}

返回的就是session的值,至于shiroCacheManager就是session緩存的儲存位置(它依賴的是我們在spring-context定義的cacheManager)。

3.需要注意一點是formAuthenticationFilter是登陸以后,身份驗證的入口,但是只攔截POST方式的loginUrl,就是我們前面配置的那個url,成功以后會跳到我們配置的那個成功頁面,一般我們都是設置一個虛擬路徑,然后在controller跳轉頁面:

/*** 登錄成功,進入管理首頁*/@RequiresPermissions("user")@RequestMapping(value = "${adminPath}")public String index(HttpServletRequest request, HttpServletResponse response) {Principal principal = UserUtils.getPrincipal();List<String> str=commentService.commentList(null);//System.out.println(JsonMapper.toJsonString(str));// 登錄成功后,驗證碼計算器清零isValidateCodeLogin(principal.getLoginName(), false, true);if (logger.isDebugEnabled()){logger.debug("show index, active session size: {}", sessionDAO.getActiveSessions(false).size());}// 如果已登錄,再次訪問主頁,則退出原賬號。if (Global.TRUE.equals(Global.getConfig("notAllowRefreshIndex"))){String logined = CookieUtils.getCookie(request, "LOGINED");if (org.apache.commons.lang3.StringUtils.isBlank(logined) || "false".equals(logined)){CookieUtils.setCookie(response, "LOGINED", "true");}else if (org.apache.commons.lang3.StringUtils.equals(logined, "true")){UserUtils.getSubject().logout();return "redirect:" + adminPath + "/login";}}
/return "modules/sys/sysIndex";}

下面是authc對應的那個filter的代碼,

 
@Service
public class FormAuthenticationFilter extends org.apache.shiro.web.filter.authc.FormAuthenticationFilter {public static final String DEFAULT_CAPTCHA_PARAM = "validateCode";public static final String DEFAULT_MOBILE_PARAM = "mobileLogin";public static final String DEFAULT_MESSAGE_PARAM = "message";private String captchaParam = DEFAULT_CAPTCHA_PARAM;private String mobileLoginParam = DEFAULT_MOBILE_PARAM;private String messageParam = DEFAULT_MESSAGE_PARAM;@Overrideprotected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {String username = getUsername(request);String password = getPassword(request);boolean rememberMe = isRememberMe(request);String host = StringUtils.getRemoteAddr((HttpServletRequest)request);boolean mobile = isMobileLogin(request);return new UsernamePasswordToken(username, password.toCharArray(), rememberMe, host, mobile);//end
    }public String getCaptchaParam() {return captchaParam;}protected String getCaptcha(ServletRequest request) {return WebUtils.getCleanParam(request, getCaptchaParam());}public String getMobileLoginParam() {return mobileLoginParam;}protected boolean isMobileLogin(ServletRequest request) {return WebUtils.isTrue(request, getMobileLoginParam());}public String getMessageParam() {return messageParam;}/*** 登錄成功之后跳轉URL*/@Overridepublic String getSuccessUrl() {return super.getSuccessUrl();}@Overrideprotected void issueSuccessRedirect(ServletRequest request,ServletResponse response) throws Exception {
//        Principal p = UserUtils.getPrincipal();
//        if (p != null && !p.isMobileLogin()){WebUtils.issueRedirect(request, response, getSuccessUrl(), null, true);
//        }else{
//            super.issueSuccessRedirect(request, response);
//        }
    }/*** 登錄失敗調用事件*/@Overrideprotected boolean onLoginFailure(AuthenticationToken token,AuthenticationException e, ServletRequest request, ServletResponse response) {String className = e.getClass().getName(), message = "";if (IncorrectCredentialsException.class.getName().equals(className)|| UnknownAccountException.class.getName().equals(className)){message = "用戶或密碼錯誤, 請重試.";}else if (e.getMessage() != null && org.apache.commons.lang3.StringUtils.startsWith(e.getMessage(), "msg:")){message = org.apache.commons.lang3.StringUtils.replace(e.getMessage(), "msg:", "");}else{message = "系統出現點問題,請稍后再試!";e.printStackTrace(); // 輸出到控制臺
        }request.setAttribute(getFailureKeyAttribute(), className);request.setAttribute(getMessageParam(), message);return true;}}
 

這里的Token就是我們前面所講的Info一起來做明文和密文進行校驗的。

?

經過上面的一些操作,shiro登錄和授權就可以做好了,對于退出,我們只要設置退出按鈕的鏈接地址是我們前面filterChainDefinitions配置的路徑就可以了,我的是:?${adminPath}/logout = logout;

具體的代碼在github:https://github.com/Housum/blog.git 有?

?

轉載于:https://www.cnblogs.com/cnblog-long/p/7646989.html

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/2/166907.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息