日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

单点登录实现(spring session+redis完成session共享)

發布時間:2024/4/17 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 单点登录实现(spring session+redis完成session共享) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、前言

  項目中用到的SSO,使用開源框架cas做的。簡單的了解了一下cas,并學習了一下?單點登錄的原理,有興趣的同學也可以學習一下,寫個demo玩一玩。

二、工程結構

  

  我模擬了 sso的客戶端和sso的服務端, sso-core中主要是一些sso需要的過濾器和工具類,緩存和session共享的一些XML配置文件,還有springmvc需要的一下jar包的管理。sso-cache中配置了redis緩存策略。

三、單點登錄原理圖

  

  簡單描述:

  用戶訪問系統1的受保護資源,系統1發現用戶未登錄,跳轉至sso認證中心,并將自己的地址作為參數

  sso認證中心發現用戶未登錄,將用戶引導至登錄頁面

  用戶輸入用戶名密碼提交登錄申請

  sso認證中心校驗用戶信息,創建用戶與sso認證中心之間的會話,稱為全局會話,同時創建授權令牌

  sso認證中心帶著令牌跳轉會最初的請求地址(系統1)

  系統1拿到令牌,去sso認證中心校驗令牌是否有效

  sso認證中心校驗令牌,返回有效,注冊系統1

  系統1使用該令牌創建與用戶的會話,稱為局部會話,返回受保護資源

  用戶訪問系統2的受保護資源

  系統2發現用戶未登錄,跳轉至sso認證中心,并將自己的地址作為參數

  sso認證中心發現用戶已登錄,跳轉回系統2的地址,并附上令牌

  系統2拿到令牌,去sso認證中心校驗令牌是否有效

  sso認證中心校驗令牌,返回有效,注冊系統2

  系統2使用該令牌創建與用戶的局部會話,返回受保護資源

?四、單點登錄實現

  1.SSOFilter.java(sso client filter實現)

import java.io.IOException;import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession;import org.slf4j.Logger; import org.slf4j.LoggerFactory;import com.alibaba.fastjson.JSONObject; import com.hjz.sso.utils.RestTemplateUtil;public class SSOFilter implements Filter{public static Logger logger = LoggerFactory.getLogger(SSOFilter.class);private String SSO_SERVER_URL;private String SSO_SERVER_VERIFY_URL;@Overridepublic void init(FilterConfig filterConfig) throws ServletException {SSO_SERVER_URL = filterConfig.getInitParameter("SSO_SERVER_URL");SSO_SERVER_VERIFY_URL = filterConfig.getInitParameter("SSO_SERVER_VERIFY_URL");if(SSO_SERVER_URL == null) logger.error("SSO_SERVER_URL is null.");if(SSO_SERVER_VERIFY_URL == null) logger.error("SSO_SERVER_VERIFY_URL is null.");}@Overridepublic void doFilter(ServletRequest req, ServletResponse res,FilterChain chain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) res;//請求中帶有token,去sso-server驗證token是否有效String authority = null;if(request.getParameter("token") != null) {boolean verifyResult = this.verify(request, SSO_SERVER_VERIFY_URL, request.getParameter("token"));if (verifyResult) {chain.doFilter(req, res);return;} else {authority = "token->" + request.getParameter("token") + " is invalidate.";}}HttpSession session = request.getSession();if (session.getAttribute("login") != null && (boolean)session.getAttribute("login") == true) {chain.doFilter(req, res);return;}//跳轉至sso認證中心String callbackURL = request.getRequestURL().toString();StringBuilder url = new StringBuilder();url.append(SSO_SERVER_URL).append("?callbackURL=").append(callbackURL);if(authority != null) {url.append("&authority=").append(authority);}response.sendRedirect(url.toString());}private boolean verify(HttpServletRequest request, String verifyUrl, String token) {String result = RestTemplateUtil.get(request, verifyUrl + "?token=" + token, null);JSONObject ret = JSONObject.parseObject(result);if("success".equals(ret.getString("code"))) {return true;}logger.error(request.getRequestURL().toString() + " : " + ret.getString("msg"));return false;}@Overridepublic void destroy() {}} View Code

  2.LoginController.java(sso server登錄controller)

import java.util.UUID;import javax.servlet.http.HttpSession;import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam;@Controller @RequestMapping("sso") public class LoginController {private Logger logger = LoggerFactory.getLogger(LoginController.class);@RequestMapping(value="login", method={RequestMethod.GET, RequestMethod.POST})public String login(HttpSession session, Model model,@RequestParam(value="name", required=false) String name,@RequestParam(value="password", required=false) String password) {if(name == null && password == null) return "login";if("admin".equals(name) && "admin".equals(password)) {String token = UUID.randomUUID().toString();session.setAttribute("login", true);session.setAttribute("token", token);return "index";} else {model.addAttribute("error", true);model.addAttribute("message", "用戶名或密碼錯誤。");return "login";}} } View Code

  3.ValidateController.java(sso server驗證token controller)

import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession;import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody;import com.alibaba.fastjson.JSONObject;@Controller @RequestMapping("sso") public class ValidateController {@RequestMapping("verify")@ResponseBodypublic JSONObject verify(HttpServletRequest request, @RequestParam String token) {HttpSession session = request.getSession();JSONObject result = new JSONObject();if(session.getAttribute("token") != null && token.equals(session.getAttribute("token"))) {result.put("code", "success");result.put("msg", "認證成功");} else {result.put("code", "failure");result.put("msg", "token已失效,請重新登錄!");}return result;}} View Code

?  4.在sso client工程中加上SSOFilter(web.xml部分配置)

<filter><filter-name>ssoFilter</filter-name><filter-class>com.hjz.sso.filter.SSOFilter</filter-class><init-param><param-name>SSO_SERVER_URL</param-name><param-value>http://localhost:8088/sso-server/sso/login</param-value></init-param><init-param><param-name>SSO_SERVER_VERIFY_URL</param-name><param-value>http://localhost:8088/sso-server/sso/verify</param-value></init-param> </filter><filter-mapping><filter-name>ssoFilter</filter-name><url-pattern>/*</url-pattern> </filter-mapping> View Code

  基本模型已經出來了,啟動sso-client 和 sso-server(本人都部署到了同一個tomcat下),試圖去驗證單點登錄。測試的時候,從瀏覽器中的cookie中查看,可以看到 localhost域下有多個JSESSIONID。這也難怪, Tomcat中的每一個application都會創建自己的session會話。那接下來的事情就是解決 session 共享的問題,這樣我們就可以完成我們的單點登陸了。

  為完成 session共享,這里推薦兩種方案。一個是?tomcat+redis實現session共享,一個是 spring session+redis實現session共享。我這里采用了第二種方案,詳情請接著看下面的步驟。

  5.為每個工程的web.xml中增加spring session代理filter的配置

<!-- session 代理 --> <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>/*</url-pattern> </filter-mapping>

  6.在sso-core中加入 緩存和spring session的xml配置(cache-config.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="false"><description>Cache公共配置</description><bean id="cookieSerializer" class="org.springframework.session.web.http.DefaultCookieSerializer"><property name="cookiePath" value="/"></property></bean><bean class="com.sso.cache.config.CacheConfig"/><bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"><property name="maxInactiveIntervalInSeconds" value="1800"></property></bean> </beans>

  這里說一下為什么有定義一個 cookieSerializer 這個bean。參看RedisHttpSessionConfiguration的源碼,發現它繼承了SpringHttpSessionConfiguration,繼續查看源碼,發現SpringHttpSessionConfiguration中實現了我們配置的spring session代理filter,如下所示。

SpringHttpSessionConfiguration.java
@Bean
public <S extends ExpiringSession> SessionRepositoryFilter<? extends ExpiringSession> springSessionRepositoryFilter(SessionRepository<S> sessionRepository) {SessionRepositoryFilter sessionRepositoryFilter = new SessionRepositoryFilter(sessionRepository);sessionRepositoryFilter.setServletContext(this.servletContext);if (this.httpSessionStrategy instanceof MultiHttpSessionStrategy) {sessionRepositoryFilter.setHttpSessionStrategy((MultiHttpSessionStrategy) this.httpSessionStrategy);} else {sessionRepositoryFilter.setHttpSessionStrategy(this.httpSessionStrategy);}return sessionRepositoryFilter; }

  查看源碼,可以發現?SpringHttpSessionConfiguration使用的默認會話策略(httpSessionStrategy)是CookieHttpSessionStrategy。繼續查看CookieHttpSessionStrategy的源碼,如新建session寫入cookie。

public void onNewSession(Session session, HttpServletRequest request, HttpServletResponse response) {Set sessionIdsWritten = getSessionIdsWritten(request);if (sessionIdsWritten.contains(session.getId())) {return;}sessionIdsWritten.add(session.getId());Map sessionIds = getSessionIds(request);String sessionAlias = getCurrentSessionAlias(request);sessionIds.put(sessionAlias, session.getId());String cookieValue = createSessionCookieValue(sessionIds);this.cookieSerializer.writeCookieValue(new CookieSerializer.CookieValue(request, response, cookieValue)); }

  cookieSerializer 默認是?DefaultCookieSerializer。查看DefaultCookieSerializer 的?writeCookieValue方法如下。

public void writeCookieValue(CookieSerializer.CookieValue cookieValue) {HttpServletRequest request = cookieValue.getRequest();HttpServletResponse response = cookieValue.getResponse();String requestedCookieValue = cookieValue.getCookieValue();String actualCookieValue = requestedCookieValue + this.jvmRoute;Cookie sessionCookie = new Cookie(this.cookieName, actualCookieValue);sessionCookie.setSecure(isSecureCookie(request)); sessionCookie.setPath(getCookiePath(request));String domainName = getDomainName(request);if (domainName != null) {sessionCookie.setDomain(domainName);}if (this.useHttpOnlyCookie) {sessionCookie.setHttpOnly(true);}if ("".equals(requestedCookieValue)) {sessionCookie.setMaxAge(0);} else {sessionCookie.setMaxAge(this.cookieMaxAge);}response.addCookie(sessionCookie); }

  sessionCookie.setPath(getCookiePath(request));這塊有一個問題,看一下getCookiePath方法的實現,如下。

private String getCookiePath(HttpServletRequest request) {if (this.cookiePath == null) {return request.getContextPath() + "/";}return this.cookiePath; }

  如果要實現單點登錄,就不要使用默認的?cookiePath 的值。所以,我定義了一個?cookieSerializer 的bean,并指定了 cookiePath 的值。?SpringHttpSessionConfiguration中如下方法可以自動裝配 我們配置的cookieSerializer,而不是使用默認的。

@Autowired(required = false) public void setCookieSerializer(CookieSerializer cookieSerializer) {this.defaultHttpSessionStrategy.setCookieSerializer(cookieSerializer); }

?  7.在每個工程中的spring公共配置文件中增加如下配置。

<import resource="classpath*:cache-config.xml"/>

  8.后端之間rest請求傳遞 session ID。

private static ResponseEntity<String> request(ServletRequest req, String url, HttpMethod method, Map<String, ?> params) {HttpServletRequest request = (HttpServletRequest) req;//獲取header信息HttpHeaders requestHeaders = new HttpHeaders();Enumeration<String> headerNames = request.getHeaderNames();while (headerNames.hasMoreElements()) {String key = (String) headerNames.nextElement();String value = request.getHeader(key);requestHeaders.add(key, value);}HttpEntity<String> requestEntity = new HttpEntity<String>(params != null ? JSONObject.toJSONString(params) : null, requestHeaders);ResponseEntity<String> rss = restTemplate.exchange(url, method, requestEntity, String.class);return rss; }

  使用RestTemplate發送rest請求,發送之前復制request中的header信息,保證session ID可以傳遞。

  9.最后,啟動工程,測試結果如下。

  ?

  http://study.hujunzheng.cn:8000/sso-client-user/  和  http://study.hujunzheng.cn:8000/sso-client-org/ 切換訪問工程。

五、完整項目地址

  https://github.com/hjzgg/SSO

六、項目演示地址

  http://study.hujunzheng.cn:8000/sso-client-user/

總結

以上是生活随笔為你收集整理的单点登录实现(spring session+redis完成session共享)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。