應用場景:多個系統下同屬於一個用戶,當用戶登陸了web1系統,那麼訪問web2,web3. . . . 時候,用戶就無需再次登陸。如:淘寶與天貓,登出也如此,一個系統登出,其餘系統的登陸也隨之失效,這就是統一單點登陸登出。html
這裏配置三個web系統,一個用戶中心繫統爲栗子java
配置hosts實現跨域:nginx
127.0.0.1 ssofront.ljtest.xxxx.com #用戶中心 127.0.0.1 my.kuaiji.com #web1 127.0.0.1 my.zikao.com #web2 127.0.0.1 my.xuelxuew.com #web3
nginx 配置用於統一登陸頁面轉發web
server { listen 80; server_name ssofront.ljtest.xxxx.com; location /ajax/ { proxy_pass http://ssofront.ljtest.xxxx.com:9092/; } location / { root D:/Java/projects/SSO_Single; index index.html; } }
先看看效果圖:ajax
統一登陸頁面:spring
全部的web系統登陸都重定向到用戶中心的統一登陸頁面跨域
統一登陸以後訪問各系統瀏覽器
當web1點擊登陸重定向到用戶中心統一登陸以後,再訪問web2,web3系統都已是登陸狀態了。cookie
可以實現這種效果就說明用戶中心,3個web系統都分別在本身的域名下成功的把token放到各自的cookie下了,並且4個系統都是跨域的,因此cookie在這四個系統間是不可共享的,因此已經達到跨域統一登陸了。session
如何實現的??咱們先看看流程圖
各位看官若是看完流程圖還不是很清晰,且聽我分析:
當咱們第一次訪問web1系統時(未登陸狀態,其餘web系統也未登陸過),此時通過過濾器,token,action固然爲null,就會查找cookie是否存在token,不過是否取得都把這個token拿到用戶中心去校驗,第一次確定拿不到,因此校驗失敗,efftoken有效token爲空,重定向到用戶中心(重定向由瀏覽器從新發出能夠獲取到用戶中心的cookie)獲取cookie中的token,token去到SSO校驗是否失效,第一次訪問因此token爲空失效,返回token,重定向回web1系統(此時得帶上參數:返回的token 和 action=callback(用以標識是用戶中心重定向回來的,避免屢次重定向)),到這裏就完成了一次詢問,假如一直未登陸,每次訪問web1系統都會去用戶中心詢問是否有別的系統登陸過啊,有就返回有效token放入web1系統的session和cookie中,那麼下次訪問就拿着這個token去用戶中心校驗判斷是否失效。當咱們去到用戶中心登陸了生成token,token會放入用戶中心的cookie中,而後重定向回web1系統,通過過濾器時執行上述步驟即可以拿到用戶中心的有效token。其中任何一個系統登出都會調用用戶中心單點登出,此時token失效,其餘系統再次訪問時就會再次詢問是否失效,失效清空cookie,這樣就完成統一登出啦。其餘系統同理也是如此。
這麼還存在一個小問題就是當從用戶中心詢問發現沒有登陸過,帶參數重定向回web1系統,瀏覽器上的url會加上參數 ?token=&action=callback,當咱們只是刷新頁面沒有其餘操做,此時過濾器中action值爲callback會跳過到用戶中心的詢問,而token是失效的未登陸狀態,因此的點擊頁面刷新,這個問題你們若是想的好的解決辦法能夠告訴我一下。
主要代碼:
web系統:
Login.java
@RequestMapping("/login") public void login(HttpServletRequest request, HttpServletResponse response){ try { //獲取上一個URL地址 String previousUrl = request.getHeader("Referer").toString(); response.sendRedirect(LOGIN_URL+previousUrl); }catch (IOException e) { e.printStackTrace(); } }
過濾器
/** * 統一登陸過濾器 * Created by Administrator on 2018/4/21 0021. * @author Evan */ public class UnifiedLoginFilter extends OncePerRequestFilter { @Value("${tokenExpired.url}") private String TOKENEXPIRED_URL; @Value("${userCenterToken.url}") private String USERCENTERTOKEN_URL; private final static String ACTION_NAME = "callback"; @Autowired private UserService userService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String action = ServletRequestUtils.getStringParameter(request, "action", null); HttpSession session = request.getSession(); String currentUrl = request.getRequestURL().toString(); //獲取用戶中心有效token String effectiveToken = null; String currentToken = null; boolean doFilter = true; if(ACTION_NAME.equals(action)) { effectiveToken = ServletRequestUtils.getStringParameter(request, "token", null); } else { currentToken = (String) session.getAttribute(SystemConfig.SESSION_TOKEN_KEY); Cookie[] cookies = request.getCookies(); if (StringUtils.isBlank(currentToken) && cookies != null) { for (int i = 0; i < cookies.length; i++) { Cookie cookie = cookies[i]; if (cookie != null && SystemConfig.COOKIE_TOKEN_NAME.equals(cookie.getName())) { currentToken = cookie.getValue(); break; } } } String result = HttpUtil.doGet4Json(TOKENEXPIRED_URL+currentToken); if(StringUtils.isNotBlank(result)) { JSONObject object = JSONObject.parseObject(result); effectiveToken = object.getString("data"); if(StringUtils.isBlank(effectiveToken)) { doFilter = false; response.sendRedirect(USERCENTERTOKEN_URL+currentUrl); } } } if(doFilter) { //用戶中心token有效 if(StringUtils.isNotBlank(effectiveToken)) { if(effectiveToken.equals(currentToken)) { //當前系統token有效,把token存入session session.setAttribute(SystemConfig.SESSION_TOKEN_KEY, effectiveToken); } else { //當前系統token失效,更新token setCookie(effectiveToken, request, response); } } //用戶中心token失效 else { removeCookie(request, response); } //重定向去掉url地址參數 if(ACTION_NAME.equals(action) && StringUtils.isNotBlank(effectiveToken)) { response.sendRedirect(currentUrl); } else { filterChain.doFilter(request, response); } } } }
過濾器配置web.xml
<!-- 統一登陸過濾 --> <filter> <filter-name>unifiedLoginFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>unifiedLoginFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
使用DelegatingFilterProxy代理過濾器,可讓Spring管理被代理的過濾器,這樣就能夠在過濾器中訪問Spring容器中的bean,屬性等。
用戶中心:
token校驗 UserInfo.java
@ApiOperation(value = "重定向獲取用戶中心Token") @ApiImplicitParam(name = "redirectUrl", value = "回調地址", required = true, dataType = "String", paramType = "query") @RequestMapping(value = "/userCenterToken", method = RequestMethod.GET) public void userCenterToken(HttpServletRequest request, HttpServletResponse response) { String redirectUrl = ServletRequestUtils.getStringParameter(request, "redirectUrl", null); String schoolId = this.getSchoolId(request); String token = null; Cookie[] cookies = request.getCookies(); if(null != cookies) { for (int i = 0; i < cookies.length; i++) { Cookie cookie = cookies[i]; if (cookie != null && config.getCookieTokenName().equals(cookie.getName())) { token = cookie.getValue(); break; } } } try { String effectiveToken = getEffectiveToken(token,schoolId); response.sendRedirect(redirectUrl+"?token="+effectiveToken+"&action=callback"); } catch (Exception e) { TRACER.error("", e); e.printStackTrace(); } } @ApiOperation(value = "校驗Token是否失效") @ApiImplicitParam(name = "token", value = "用戶 Token", required = true, dataType = "String", paramType = "query") @RequestMapping(value = "/tokenExpired", method = RequestMethod.GET) @ResponseBody public ResponseEntity<WrappedResponse<String>> tokenExpired(HttpServletRequest request, HttpServletResponse response) { try { String token = ServletRequestUtils.getStringParameter(request, "token", null); String schoolId = this.getSchoolId(request); String effectiveToken = getEffectiveToken(token,schoolId); return this.success(effectiveToken); } catch (Throwable t) { TRACER.error("", t); return this.fail(TransactionStatus.INTERNAL_SERVER_ERROR); } } private String getEffectiveToken(String token, String schoolId) throws Exception { String effectiveToken = ""; HashMap<String, Object> map = new HashMap<>(1); map.put("token", token); HttpPlainResult result = httpConnManager.invoke(HttpMethod.GET, config.getSsoHost()+"/inner/tokenExpired", map,schoolId); TRACER.info(result.getResult()); HttpResultDetail<TokenStatus> entry = HttpResultHandler.handle(result, TokenStatus.class); if (entry.isOK()) { TokenStatus tokenStatus = entry.getResult(); if (!tokenStatus.getExpired()) { effectiveToken = token; } } return effectiveToken; }
統一登陸 Login.java
HttpPlainResult result = httpConnManager.invoke(HttpMethod.POST, config.getSsoHost() + "/inner/login", map, schoolId); TRACER.info(result.getResult()); HttpResultDetail<Token> entry = HttpResultHandler.handle(result,Token.class); if(entry.isOK()){ ClientTypeEnum clientTypeEnum = ClientTypeEnum.convertString2ClientType(clientType); if(clientTypeEnum == ClientTypeEnum.WEB){ String token = entry.getResult().getToken(); Cookie token_cookie = new Cookie(config.getCookieTokenName(),token); token_cookie.setMaxAge(config.getCookieTokenTimeout()); token_cookie.setDomain(request.getHeader("Host").split(":")[0]); response.addCookie(token_cookie); return this.success(redirectUrl,token_cookie); }else{ return this.success(entry.getResult(), entry.getResponseStatus()); } }else if(entry.isClientError()){ return this.error(entry.getResponseMessage(), entry.getResponseStatus()); }else if(entry.isServerError()){ return this.fail(entry.getResponseMessage(), entry.getResponseStatus()); }
統一登出 Logout.java
HttpPlainResult result = httpConnManager.invoke(HttpMethod.POST, config.getSsoHost()+"/inner/logout", map,schoolId); TRACER.info(result.getResult()); HttpResultDetail<String> entry = HttpResultHandler.handle(result,String.class); if(entry.isOK()){ Cookie token_cookie = new Cookie(config.getCookieTokenName(),null); token_cookie.setMaxAge(0); token_cookie.setDomain(request.getHeader("Host").split(":")[0]); response.addCookie(token_cookie); return this.success(entry.getResult(), entry.getResponseStatus()); }else if(entry.isClientError()){ return this.error(entry.getResponseMessage(), entry.getResponseStatus()); }else if(entry.isServerError()){ return this.fail(entry.getResponseMessage(), entry.getResponseStatus()); }