单点登陆sso实现
Posted lin_sen
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单点登陆sso实现相关的知识,希望对你有一定的参考价值。
需求:
多个bs业务系统,在某个业务系统登陆后,访问其他bs应用系统无需重复登陆.
制约:必须同一浏览器.
解决方案:
关键词:cookie,跨域,sso
环境
l Passport.com 登陆认证服务
l pis.com 病理业务系统
l lis.com 检验业务系统
l login 拦截器:验证请求是否有令牌,令牌是否合法()
l 令牌 ticket
括号内为增强功能
l 用户访问pis.com,拦截器发现无令牌或令牌无效,跳转至passport.com的登陆页面(防止恶意测试密码,服务器生成登陆令牌到passport.com的cookie)
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")); }
至此已实现单点登陆,主要思路参考京东单点登陆业务逻辑.
以上是关于单点登陆sso实现的主要内容,如果未能解决你的问题,请参考以下文章