需求:ajax
多個bs業務系統,在某個業務系統登錄後,訪問其餘bs應用系統無需重複登錄.redis
制約:必須同一瀏覽器.json
解決方案:windows
關鍵詞:cookie,跨域,sso跨域
環境瀏覽器
l Passport.com 登錄認證服務
l pis.com 病理業務系統
l lis.com 檢驗業務系統
l login 攔截器:驗證請求是否有令牌,令牌是否合法()服務器
l 令牌 ticketcookie
括號內爲加強功能session
l 用戶訪問pis.com,攔截器發現無令牌或令牌無效,跳轉至passport.com的登錄頁面(防止惡意測試密碼,服務器生成登錄令牌到passport.com的cookie)app
l 用戶輸入用戶名,密碼,發起請求(並攜帶登錄令牌,合法請求)到passport.com驗證,驗證經過,生成令牌,返回令牌-set cookie和須要sso的全部域名.
l ajax使用令牌發起跨域請求pis.com,lis.com傳送令牌到各個業務系統,成功後重定向到pis.com/home頁面
l 攔截器驗證令牌合法性.不合法跳轉到passport.com,合法返回頁面給用戶
l 用戶繼續訪問lis.com,由於令牌已經設置完成,因此請求能夠經過,無需登錄.
全部業務的Gateway與微服務都經過redis共享存儲驗證請求的合法性.實現業務系統的單點登陸.
攔截器代碼
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object arg2) throws Exception { if(request.getServletPath().equals("/sso")) return true; this.getCache(); Cookie[] cookies = request.getCookies(); boolean checkFlag = false; if (cookies == null) { logger.error("請求的cookie爲空,請啓用cookie"); checkFlag = false; } else { for (Cookie cookie : cookies) { if (cookie.getName().equals(Constants.TICKET)) { String ticket = cookie.getValue(); checkFlag = true; } } } if (!checkFlag) {// 驗證不經過 String requestType = request.getHeader("x-requested-with"); if ("XMLHttpRequest".equals(requestType)) { String contentType = "application/octet-stream"; ServletOutputStream out = response.getOutputStream(); response.setContentType(contentType); response.setHeader("sessionstatus", "timeout"); out.print("<script>"); out.print("windows.location.href='" + Constants.PASSPORT_LOGIN); out.print("</script>"); out.flush(); } else { response.sendRedirect(Constants.PASSPORT_LOGIN); } } return true; }
登錄驗證
@RestController @RequestMapping("/login") public class LoginContronller { @Autowired DictCacheHelper cache; @Autowired SSOHelper sso; String[] servers = new String[] { "my.pis.com", "my.lis.com" }; /** * 登錄驗證 ,驗證經過生成tictiket,並已tictiket爲key保存用戶權限信息,基本信息到redis. * * @param inParam * :userId,password,returnurl(驗證經過後前往的頁面,若是爲空返回到用戶有權訪問的第一個系統) * @return 成功返回: ticket 要跳轉到的系統 須要跨域的域名列表 驗證不經過拋出異常. * @throws Exception */ @RequestMapping("login") @ResponseBody public Map<String, Object> login(@RequestParam Map<String, Object> inParam ,HttpServletRequest request, HttpServletResponse response) throws Exception { // 此處應調用後臺服務獲取用戶權限信息. String userId= MapUtil.getValue("userId", inParam); if(!userId.equals("1")) throw new Exception(String.format("用戶 %s 登錄驗證失敗,請輸入正確的用戶名密碼", userId)); String ticket = String.valueOf(System.currentTimeMillis()); // UUID.randomUUID(); List<Map<String,Object>> userFuncs= new ArrayList<>(); userFuncs.add(new MyMap().put("funcCode", "1001").getMap()); userFuncs.add(new MyMap().put("funcCode", "1002").getMap()); cache.setItem("ticket", ticket, new MyMap().put("userFuncs", userFuncs).getMap()); Cookie cookie = new Cookie("ticket", ticket); // cookie.setDomain(request.getParameter("server")); cookie.setPath("/"); // cookie.setMaxAge(10000000); response.addCookie(cookie); return new MyMap().put("ticket", ticket).put("servers", servers).getMap(); } }
登錄js,登錄成功後會發起跨域請求,讓須要sso的域名設置正確的cookie
var passport = { init : function() { $("#btnLogin").on( "click", function() { $.ajax("login/login", { data : $('#loginForm').serialize(), error:function (XMLHttpRequest, textStatus, errorThrown) { $.messager.alert("操做失敗",XMLHttpRequest.responseText); }, dataType:"json", dataFilter:function (json, type) { var data = JSON.parse(json); for (var i = 0; i < data.servers.length; i++) { passport.crossAjax("http://" + data.servers[i] + ':81/pis/sso', { 'ticket' : data.ticket, 'server' : data.servers[i] }); } ; window.location.href = "http://" + data.servers[0] + ':81/pis'; }, sucess : function(data) { alert(data); for (var i = 0; i < data.servers.length; i++) { passport.crossAjax("http://" + data.servers[i] + ':81/pis/sso', { 'ticket' : data.ticket, 'server' : data.servers[i] }); } ; window.location.href = "http://" + data.servers[0] + ':81/pis'; } }); }) }, crossAjax : function(pageUrl, json, redirectFlag) { $.ajax({ url : pageUrl, data : json, xhrFields : { withCredentials : true }, crossDomain : true, async : false, timeout : 20 * 1000, }) } } $(document).ready(function() { passport.init(); });
業務系統收到sso請求
@RequestMapping(value = "/sso") public void doSSO(HttpServletRequest request, HttpServletResponse response) throws IOException { Cookie cookie = new Cookie("ticket", request.getParameter("ticket")); cookie.setDomain(request.getParameter("server")); cookie.setPath("/"); // cookie.setMaxAge(10000000); response.addCookie(cookie); response.setContentType("text/plain"); response.addHeader("P3P", "CP=CAO PSA OUR"); response.getWriter().write(request.getParameter("server")); }
至此已實現單點登錄,主要思路參考京東單點登錄業務邏輯.