单点登陆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;
    }
View Code

登陆验证

@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();
    }

}
View Code

登陆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实现的主要内容,如果未能解决你的问题,请参考以下文章

单点登陆sso实现

django-sso单点登陆的实现

实现ADFS与阿里云SSO(单点登陆管理)——配置用户SSO

单点登陆

SpringBoot实现用户统一管理与单点登陆

单点登录(SSO)初识