單點登錄(Single Sign On),簡稱為 SSO,是目前比較流行的企業業務整合的解決方案之一。SSO的定義是在多個應用系統中,用戶只需要登錄一次就可以訪問所有相互信任的應用系統。目前市面上有很多實現單點登錄的方案,例如CAS,Token頒發校驗,Cookie+域名+路徑配置,在這里主要是想介紹一下第三種方案的實現方式。
過程:
很早期的公司,一家公司可能只有一個Server,慢慢的Server開始變多了。每個Server都要進行注冊登錄,退出的時候又要一個個退出。用戶體驗很不好!你可以想象一下,上豆瓣 要登錄豆瓣FM、豆瓣讀書、豆瓣電影、豆瓣日記......真的會讓人崩潰的。我們想要另一種登錄體驗:一家企業下的服務只要一次注冊,登錄的時候只要一次登錄,退出的時候只要一次退出。怎么做?
筆者之前所在的某公司項目是一個單體垂直架構,當初為了節省時間也沒做拆分處理,只是集成了了Spring-Session和Redis。有一天突然發現在本地調試的時候關掉Tomcat再啟動發現竟然用戶的緩存還在。因為之前剛接觸Servlet的時候做過一些小demo,知道用戶登錄后會為該用戶分配一個session對象 當服務被關掉的時候會釋放內存。
項目拆分:
過了一段時間,由於公司內部發展需要,要額外開發一個新的平台;於是討論后打算要做成真正的單點登錄,因為用戶可能在原先平台登錄后要去到另外一個新的平台,其中新的平台域名是:www.xxx.xx.com,舊平台域名是 www.xx.com,按照傳統的開發,用戶去到新的平台后拿不到session,會造成用戶重新登錄的狀況。
改造過程:
#首先思考下,為什么單體架構中Servlet 的Session在服務重啟后會失效,原因在於重啟后資源被釋放,依賴的載體也不復存在,看過大型網站的演變過程那本書都知道為了統一管理緩存一般都會存儲在第三方,例如Redis,MembeCache。
#使用cookie作為媒介,存放用戶憑證。把保存用戶登陸信息的cookie的域設置成一樣,這樣由於一級二級域名都是共享的,所以他們的cookie也是共享的。然后在web.xml配置好DelegatingFilterProxy過濾器之后,Spring-session會對HttpServletRequest進行包裝,核心就是每次取session信息都拿到瀏覽器的jsessionid的value再到redis去取。
#Spring Session提供了一套創建和管理Servlet HttpSession的方案。Spring Session提供了集群Session(Clustered Sessions)功能,默認采用外置的Redis來存儲Session數據,以此來解決Session共享的問題。
配置流程:
#web.xml,核心過濾器。
<filter> <filter-name>springSessionRepositoryFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSessionRepositoryFilter</filter-name> <url-pattern>*.do</url-pattern> </filter-mapping>
#spring-session.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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="1800" />
</bean>
<bean id="defaultCookieSerializer" class="org.springframework.session.web.http.DefaultCookieSerializer">
<property name="domainName" value=".xxx.com" />
<property name="useHttpOnlyCookie" value="true" />
<property name="cookiePath" value="/" />
<property name="cookieMaxAge" value="31536000" />
</bean>
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="20"/>
</bean>
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="127.0.0.1" />
<property name="port" value="6379" />
<property name="poolConfig" ref="jedisPoolConfig" />
<property name="password" value="redis"></property>
</bean>
</beans>
property naehtpSessionStrategy" ref="cookieHttpSessionStrategy"/> </bean> 設置cookieName和path <bean id="defaultCookieSerializer" class="org.springframework.session.web.http.DefaultCookieSerializer"> <property name="cookieName" value="DTL_SESSION_ID" /> <property name="cookiePath" value="/" /> </bean> <bean id="cookieHttpSessionStrategy" class="org.springframework.session.web.http.CookieHttpSessionStrategy"> <property name="cookieSerializer" ref="defaultCookieSerializer" /> </bean> <