XXL-SSO源码解读

一. 概述

XXL-SSO 是一个分布式单点登录框架。只需要登录一次就可以访问所有相互信任的应用系统。拥有”轻量级、分布式、跨域、Cookie+Token均支持、Web+APP均支持”等特性。现已开放源代码,开箱即用。

 

二.特性

  • 1、简洁:API直观简洁,可快速上手
  • 2、轻量级:环境依赖小,部署与接入成本较低
  • 3、单点登录:只需要登录一次就可以访问所有相互信任的应用系统
  • 4、分布式:接入SSO认证中心的应用,支持分布式部署
  • 5、HA:Server端与Client端,均支持集群部署,提高系统可用性
  • 6、跨域:支持跨域应用接入SSO认证中心
  • 7、Cookie+Token均支持:支持基于Cookie和基于Token两种接入方式,并均提供Sample项目
  • 8、Web+APP均支持:支持Web和APP接入
  • 9、实时性:系统登陆、注销状态,全部Server与Client端实时共享
  • 10、CS结构:基于CS结构,包括Server”认证中心”与Client”受保护应用”
  • 11、记住密码:未记住密码时,关闭浏览器则登录态失效;记住密码时,支持登录态自动延期,在自定义延期时间的基础上,原则上可以无限延期
  • 12、路径排除:支持自定义多个排除路径,支持Ant表达式,用于排除SSO客户端不需要过滤的路径

三. sso-server 登录

3.1 sso-server 登录流程

 

 

sso 登录的 sessionId 是存在 redis 中,解决了分布式登录的问题。

 

1.首页请求 http://xxlssoserver.com:8080/xxl-sso-server 后台会请求到 WebController的 index方法

2.校验是否已登录,已登录跳转到首页,未登录,跳转到 login 页面,即http://xxlssoserver.com:8080/xxl-sso-server/login

3.填写登录名,密码登录,请求WebController的 doLogin方法,首先会判断是否是记住密码,若是记住密码,则 cookie 有效期是int 的最大值,单位为秒

登录成功,则跳转到首页页面

 

 

 

 

 

 

 

 

 

 

 4. sessionId 是 userId 和一个随机生成的 uuid,每次登录都会重新生成一个 sessionId;

那么之前的 sessionId 怎么失效呢,有两种情况:

第一种是主动点击退出按钮,退出接口logout,会清除 redis 中的对应的 sessionId,  并设置 cookie 中的maxAge 为0,即让当前 cookie 立即失效

第二种是 sessionId 设置在 redis 中是有过期时间的,默认为24h, 登录后,长时间,不操作,redis 中的 sessionId过期后也需要重新登录的

 

 

 

 

 

 

 

loginCheck 里有一段代码也很重要,就是主动把 redis 中 sessionId 的过期时间续期的代码,这段逻辑是 xxlUser 中有个expireFreshTime 属性,每次 loginCheck 时,会校续期时间是否少于定义的一半时间,是的话,就主动续期

    /**
     * login check
     *
     * @param sessionId
     * @return
     */
    public static XxlSsoUser loginCheck(String  sessionId){

        String storeKey = SsoSessionIdHelper.parseStoreKey(sessionId);
        if (storeKey == null) {
            return null;
        }

        XxlSsoUser xxlUser = SsoLoginStore.get(storeKey);
        if (xxlUser != null) {
            String version = SsoSessionIdHelper.parseVersion(sessionId);
            if (xxlUser.getVersion().equals(version)) {

                // After the expiration time has passed half, Auto refresh
                if ((System.currentTimeMillis() - xxlUser.getExpireFreshTime()) > xxlUser.getExpireMinute()/2) {
                    xxlUser.setExpireFreshTime(System.currentTimeMillis());
                    SsoLoginStore.put(storeKey, xxlUser);
                }

                return xxlUser;
            }
        }
        return null;
    }

 

 

 5. cookie 的保存,maxAge 是 cookie 的生命的周期

    /**
     * 保存
     *
     * @param response
     * @param key
     * @param value
     * @param ifRemember 
     */
    public static void set(HttpServletResponse response, String key, String value, boolean ifRemember) {
        //如果maxAge属性为正数,则表示该Cookie会在maxAge秒之后自动失效。浏览器会将maxAge为正数的Cookie持久化,
        // 即写到对应的Cookie文件中。无论客户关闭了浏览器还是电脑,只要还在maxAge秒之前,登录网站时该Cookie仍然有效。
        //如果maxAge为负数,则表示该Cookie仅在本浏览器窗口以及本窗口打开的子窗口内有效,
        // 关闭窗口后该Cookie即失效。maxAge为负数的Cookie,为临时性Cookie,不会被持久化,不会被写到Cookie文件中。
        // Cookie信息保存在浏览器内存中,因此关闭浏览器该 Cookie就消失了。Cookie默认的maxAge值为-1
        int age = ifRemember?COOKIE_MAX_AGE:-1;
        set(response, key, value, null, COOKIE_PATH, age, true);
    }  
/**
* 保存
*
* @param response
* @param key
* @param value
* @param maxAge
*/
private static void set(HttpServletResponse response, String key, String value, String domain, String path, int maxAge, boolean isHttpOnly) {
Cookie cookie = new Cookie(key, value);
if (domain != null) {
cookie.setDomain(domain);
}
cookie.setPath(path);
cookie.setMaxAge(maxAge);
cookie.setHttpOnly(isHttpOnly);
response.addCookie(cookie);
}


 

7. 退出登录接口

 删除 redis 中的 sessionId, 并设置 cookie 失效

    /**
     * Logout
     *
     * @param request
     * @param redirectAttributes
     * @return
     */
    @RequestMapping(Conf.SSO_LOGOUT)
    public String logout(HttpServletRequest request, HttpServletResponse response, RedirectAttributes redirectAttributes) {

        // logout
        SsoWebLoginHelper.logout(request, response);

redirectAttributes.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL));
return "redirect:/login"; } /** * client logout * * @param request * @param response */ public static void logout(HttpServletRequest request, HttpServletResponse response) { String cookieSessionId = CookieUtil.getValue(request, Conf.SSO_SESSIONID); if (cookieSessionId==null) { return; } String storeKey = SsoSessionIdHelper.parseStoreKey(cookieSessionId); if (storeKey != null) {
//删除 redis 中的 sessionId SsoLoginStore.remove(storeKey); }
//cookie 设置失效,即 maxAge 设置为0 CookieUtil.remove(request, response, Conf.SSO_SESSIONID); }

 

 

四. sso-client 登录/退出

 

4.1登录流程图

 

 

 

 

clinet 登录主要是

XxlSsoConfig 主要是初始化redis 示例,即注册一个过滤器XxlSsoWebFilter

XxlSsoWebFilter是client 客户登录校验的逻辑

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;

        // make url
        String servletPath = req.getServletPath();

        // excluded path check
        if (excludedPaths!=null && excludedPaths.trim().length()>0) {
            for (String excludedPath:excludedPaths.split(",")) {
                String uriPattern = excludedPath.trim();

                // 支持ANT表达式
                if (antPathMatcher.match(uriPattern, servletPath)) {
                    // excluded path, allow
                    chain.doFilter(request, response);
                    return;
                }

            }
        }

        // logout path check
        if (logoutPath!=null
                && logoutPath.trim().length()>0
                && logoutPath.equals(servletPath)) {

            // remove cookie
            SsoWebLoginHelper.removeSessionIdByCookie(req, res);

            // redirect logout
            String logoutPageUrl = ssoServer.concat(Conf.SSO_LOGOUT);
            res.sendRedirect(logoutPageUrl);

            return;
        }

        // valid login user, cookie + redirect

//校验 sessionId 是否存在于 redis 中 XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(req, res); // valid login fail if (xxlUser == null) { String header = req.getHeader("content-type"); boolean isJson= header!=null && header.contains("json"); if (isJson) { // json msg res.setContentType("application/json;charset=utf-8"); res.getWriter().println("{\"code\":"+Conf.SSO_LOGIN_FAIL_RESULT.getCode()+", \"msg\":\""+ Conf.SSO_LOGIN_FAIL_RESULT.getMsg() +"\"}"); return; } else { // total link String link = req.getRequestURL().toString(); // redirect logout sessionId
//重定向到 sso-server 的登录页,登录成功后,跳转到当前请求的路径地址
                 //loginPageUrl= http://xxlssoserver.com:8080/xxl-sso-server/login?redirect_url=http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/
                String loginPageUrl = ssoServer.concat(Conf.SSO_LOGIN)
                        + "?" + Conf.REDIRECT_URL + "=" + link;

                res.sendRedirect(loginPageUrl);
                return;
            }

        }

        // ser sso user
        request.setAttribute(Conf.SSO_USER, xxlUser);


        // already login, allow
        chain.doFilter(request, response);
        return;
    }

 

 

 

 

 

 

 client1登录成功后,直接访问 client2无需再次登录,直接访问成功,原因就是client1登录成功后,sessionId 已经存在于 redis 中了,此时client2拿到当前的 cookie 请求即可校验通过

 

 

 

 

 

 

 

 

 

4.2 退出流程图

 

 XxlSsoWebFilter中的退出逻辑比较简单,重定向到 logout 接口 , logout 接口删除 redis 中的 sessionId 后,重定向到 sso-server 的登录页面

 

xxl-sso 已支持 token ,但还未支持 jwt, 介绍一篇 jwt 的文章也很好,https://blog.csdn.net/weixin_45070175/article/details/118559272

 

posted @ 2022-10-15 23:50  纵码万水千山  阅读(544)  评论(0编辑  收藏  举报