文档章节

深度源码分析——XXL-SSO分布式单点登录框架(轻量级、分布式、跨域、Cookie+Token、Web+APP均支持)

须臾之余
 须臾之余
发布于 06/13 13:09
字数 2185
阅读 48
收藏 0

什么是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客户端不需要过滤的路径

源码编译

- xxl-sso-server:中央认证服务,支持集群

- xxl-sso-core:Client端依赖

- xxl-sso-samples:单点登陆Client端接入示例项目

       - xxl-sso-web-sample-springboot:基于Cookie接入方式,供用户浏览器访问,springboot版本

       - xxl-sso-token-sample-springboot:基于Token接入方式,常用于无法使用Cookie的场景使用,如APP、Cookie被禁用等, springboot版本

               

导入idea

先启动xxl-sso-server

启动之前先看配置文件有redis连接信息

所以先启动本地redis服务,但没发现redis的密码配置,密码配置写在哪里呢,我们先启动项目看看

如果你本地没有配置redis密码,则正常启动,配置了则启动报错,我本地redis没有配置密码所以可以正常启动

我们先从config开始断点调试

F8进入

我们就可以发现password在这里配置

接下来:修改Host文件:域名方式访问认证中心,模拟跨域与线上真实环境

### 在host文件中添加以下内容

127.0.0.1   xxlssoserver.com

127.0.0.1   xxlssoclient1.com

127.0.0.1   xxlssoclient2.com

分别运行 "xxl-sso-server" 与 "xxl-sso-token-sample-springboot"

1、SSO认证中心地址: http://xxlssoserver.com:8080/xxl-sso-server

2、Client01应用地址: http://xxlssoclient1.com:8084/xxl-sso-token-sample-springboot/

3、Client02应用地址: http://xxlssoclient2.com:8085/xxl-sso-token-sample-springboot/

启动:xxl-sso-web-sample-springboot

配置文件信息

### xxl-sso
xxl.sso.server=http://xxlssoserver.com:8080/xxl-sso-server
xxl.sso.logout.path=/logout
xxl-sso.excluded.paths=
xxl.sso.redis.address=redis://127.0.0.1:6379

说明客户端会重定向到认证授权中心进行登录

redis也是连接同一个redis

为什么客户端也要集成redis呢?后面源码分析就知道了

启动客户端两次,分别改为不同端口,模拟,发现修改配置文件,项目自动重启,所以注意要删除热部署的jar包

        <!-- devtools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>provided</scope>
            <optional>true</optional>
        </dependency>

1.访问这个客户端,会自动重定向到server端进行登录

2.点击登录,server端又跳转到client后面带sessionId参数

3.再访问第二个客户端,发现可以免登录

重点:断点调试XxlSsoWebFilter源码

思考问题:

访问客户端的时候,如何自动重定向到认证授权中心server端实现登录的?

过滤器,过滤请求,如果当前没有获取到用户的会话信息,会自动重定向跳转到认证授权中心进行登录。

所以断点调试 核心依赖jar包中的XxlSsoWebFilter

所以找到这个类在doFilter中断点调试

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;
                }

            }
        }

访问:http://xxlssoclient1.com:8084/xxl-sso-web-sample-springboot/

client原理分析:

1.先从Cookie中获取当前的CooikeId

2.如果用户没有登录的情况下,重定向到认证授权中心进行登录

3.在认证授权中心进行登录成功之后返回原来地址(重定向地址)

http://xxlssoserver.com:8080/xxl-sso-server/login?redirect_url=http://xxlssoclient1.com:8084/xxl-sso-web-sample-springboot/

重点:重定向到认证授权中心源码分析server端

在WebController中打断点

 @RequestMapping(Conf.SSO_LOGIN)
    public String login(Model model, HttpServletRequest request, HttpServletResponse response) {

        // login check
        XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(request, response);

        if (xxlUser != null) {

            // success redirect
            String redirectUrl = request.getParameter(Conf.REDIRECT_URL);
            if (redirectUrl!=null && redirectUrl.trim().length()>0) {

                String sessionId = SsoWebLoginHelper.getSessionIdByCookie(request);
                String redirectUrlFinal = redirectUrl + "?" + Conf.SSO_SESSIONID + "=" + sessionId;;

                return "redirect:" + redirectUrlFinal;
            } else {
                return "redirect:/";
            }
        }

// login check
XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(request, response);

跳转到登录界面

F12,查看提交表单url

 

所以在这里打断点

@RequestMapping("/doLogin")
    public String doLogin(HttpServletRequest request,
                        HttpServletResponse response,
                        RedirectAttributes redirectAttributes,
                        String username,
                        String password,
                        String ifRemember) {

        boolean ifRem = (ifRemember!=null&&"on".equals(ifRemember))?true:false;

        // valid login
        ReturnT<UserInfo> result = userService.findUser(username, password);
        if (result.getCode() != ReturnT.SUCCESS_CODE) {
            redirectAttributes.addAttribute("errorMsg", result.getMsg());

            redirectAttributes.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL));
            return "redirect:/login";
        }

        // 1、make xxl-sso user
        XxlSsoUser xxlUser = new XxlSsoUser();
        xxlUser.setUserid(String.valueOf(result.getData().getUserid()));
        xxlUser.setUsername(result.getData().getUsername());
        xxlUser.setVersion(UUID.randomUUID().toString().replaceAll("-", ""));
        xxlUser.setExpireMinite(SsoLoginStore.getRedisExpireMinite());
        xxlUser.setExpireFreshTime(System.currentTimeMillis());


        // 2、make session id
        String sessionId = SsoSessionIdHelper.makeSessionId(xxlUser);

        // 3、login, store storeKey + cookie sessionId
        SsoWebLoginHelper.login(response, sessionId, xxlUser, ifRem);

        // 4、return, redirect sessionId
        String redirectUrl = request.getParameter(Conf.REDIRECT_URL);
        if (redirectUrl!=null && redirectUrl.trim().length()>0) {
            String redirectUrlFinal = redirectUrl + "?" + Conf.SSO_SESSIONID + "=" + sessionId;
            return "redirect:" + redirectUrlFinal;
        } else {
            return "redirect:/";
        }

    }

发现这里是写死了的,可以自己修改为查数据库

 @Override
    public ReturnT<UserInfo> findUser(String username, String password) {

        if (username==null || username.trim().length()==0) {
            return new ReturnT<UserInfo>(ReturnT.FAIL_CODE, "Please input username.");
        }
        if (password==null || password.trim().length()==0) {
            return new ReturnT<UserInfo>(ReturnT.FAIL_CODE, "Please input password.");
        }

通过用户信息创建sessionId

 public static String makeSessionId(XxlSsoUser xxlSsoUser){
        String sessionId = xxlSsoUser.getUserid().concat("_").concat(xxlSsoUser.getVersion());
        return sessionId;
    }

登录时

public static void login(HttpServletResponse response,
                             String sessionId,
                             XxlSsoUser xxlUser,
                             boolean ifRemember) {

        String storeKey = SsoSessionIdHelper.parseStoreKey(sessionId);
        if (storeKey == null) {
            throw new RuntimeException("parseStoreKey Fail, sessionId:" + sessionId);
        }

        SsoLoginStore.put(storeKey, xxlUser);
        CookieUtil.set(response, Conf.SSO_SESSIONID, sessionId, ifRemember);
    }

key为sessionId,value为用户信息在redis中存一份

public static void put(String storeKey, XxlSsoUser xxlUser) {
        String redisKey = redisKey(storeKey);
        JedisUtil.setObjectValue(redisKey, xxlUser, redisExpireMinite * 60);  // minite to second
    }

    private static String redisKey(String sessionId){
        return Conf.SSO_SESSIONID.concat("#").concat(sessionId);
    }

认证授权登录成功之后,在认证授权系统域名下(server端)存放对应的cookie信息

认证授权系统回调到子系统中传递xxl-ssso-sessionid,子系统域名下还没有对应的cookie信息

回调到子系统的时候,会被xxlssoFilter拦截

cookie信息会在客户端域名下存一份,这样可以保证认证授权系统和子系统双方Cookie信息同步

 public static XxlSsoUser loginCheck(HttpServletRequest request, HttpServletResponse response){

        String cookieSessionId = CookieUtil.getValue(request, Conf.SSO_SESSIONID);

        // cookie user
        XxlSsoUser xxlUser = SsoTokenLoginHelper.loginCheck(cookieSessionId);
        if (xxlUser != null) {
            return xxlUser;
        }

        // redirect user

        // remove old cookie
        SsoWebLoginHelper.removeSessionIdByCookie(request, response);

        // set new cookie
        String paramSessionId = request.getParameter(Conf.SSO_SESSIONID);
        xxlUser = SsoTokenLoginHelper.loginCheck(paramSessionId);
        if (xxlUser != null) {
            CookieUtil.set(response, Conf.SSO_SESSIONID, paramSessionId, false);    // expire when browser close (client cookie)
            return xxlUser;
        }

        return null;
    }

在redis中查询对应的sessionId信息,所以前面为什么client端也要集成redis的原因解决了。

 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.getExpireMinite()/2) {
                    xxlUser.setExpireFreshTime(System.currentTimeMillis());
                    //在redis里面也存一份
                    SsoLoginStore.put(storeKey, xxlUser);
                }

                return xxlUser;
            }
        }
        return null;
    }
 public static void put(String storeKey, XxlSsoUser xxlUser) {
        String redisKey = redisKey(storeKey);
        JedisUtil.setObjectValue(redisKey, xxlUser, redisExpireMinite * 60);  // minite to second
    }

    private static String redisKey(String sessionId){
        return Conf.SSO_SESSIONID.concat("#").concat(sessionId);
    }

登录成功

其他系统如何实现面密登陆源码分析

在第一个ssoclient系统在ssoserver端登录了之后,第二个ssoclient系统登录的话,会重定向到认证授权系统进行登录,因为认证授权系统有对应的Cookie信息,会直接把认证授权中心第一个ssoclient登录的cookie信息回调给第二个ssoclient系统。

访问:http://xxlssoclient1.com:8085/xxl-sso-web-sample-springboot

也会走filter拦截,把当前会话信息也会保存在本地一份

直接免登录

登录流程总结

  • 用户于Client端应用访问受限资源时,将会自动 redirect 到 SSO Server 进入统一登录界面
  • 用户登录成功之后将会为用户分配 SSO SessionId 并 redirect 返回来源Client端应用,同时附带分配的 SSO SessionId
  • 在Client端的SSO Filter里验证 SSO SessionId 无误,将 SSO SessionId 写入到用户浏览器Client端域名下 cookie 中
  • SSO Filter验证 SSO SessionId 通过,受限资源请求放行

整体架构图

版权@须臾之余https://my.oschina.net/u/3995125

本文参考蚂蚁课堂:http://www.mayikt.com

© 著作权归作者所有

须臾之余
粉丝 65
博文 26
码字总数 41561
作品 0
吉安
程序员
私信 提问
XXL-SSO v1.1.0 发布, 分布式单点登录框架

XXL-JOB 正在角逐 “2018年度最受欢迎中国开源软件”,期待您宝贵的一票!投票链接 Release Notes 1、Redis配置方式增强,支持自定义DB、密码、IP、PORT等等;; 2、Token接入方式;除了常规...

许雪里
2018/11/16
1K
0
分布式单点登录框架 - xxl-sso

分布式单点登录框架 XXL-SSO XXL-SSO 是一个分布式单点登录框架。只需要登录一次就可以访问所有相互信任的应用系统。 其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码,...

许雪里
2018/04/04
0
0
XXL-SSO — 轻量级分布式单点登录框架

XXL-SSO 是一个分布式单点登录框架。只需要登录一次就可以访问所有相互信任的应用系统。 其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码,开箱即用。

h4cd
2018/04/11
23
0
基于 Cookie 的 SSO 中间件 - kisso

kisso = cookie sso 基于 Cookie 的 SSO 中间件,它是一把快速开发 java Web 登录系统(SSO)的瑞士军刀。欢迎大家使用 kisso !! kisso 帮助文档下载 1、支持单点登录 2、支持登录Cookie缓存...

青苗
2014/06/18
0
21
kisso 3.5 发布,最快最省心的 SSO 构建中间件。

kisso = cookie sso 基于 Cookie 的 SSO 中间件,它是一把快速开发 java Web 登录系统(SSO)的瑞士军刀。引用 @JFinal詹波的一句话:"为您节约更多时间,去陪恋人、家人和朋友 :) "。 首先感...

青苗
2015/12/25
7K
9

没有更多内容

加载失败,请刷新页面

加载更多

nproc systemd on CentOS 7

Increasing nproc for processes launched by systemd on CentOS 7 Ask Question I have successfully increased the nofile and nproc value for the local users, but I couldn't find a p......

MtrS
今天
3
0
了解微信小程序下拉刷新功能

小程序提供了这个事件。 onPullDownRefresh() 监听用户下拉刷新事件。 如果要开启下拉刷新功能,要先到json配置: "enablePullDownRefresh":true 配置后下拉有反应了但是没有加载效果,在onP...

oixxan__
今天
2
0
springmvc java对象转json,上传下载(未完)拦截器Interceptor以及源码解析(未完待续)

package com.atguigu.my.controller;import java.util.Collection;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Contr......

architect刘源源
今天
29
0
[日更-2019.5.24、25、26] Android系统中的Binder通信机制分析(一)--servicemanager

声明 其实对于Android系统Binder通信的机制早就有分析的想法,记得去年6、7月份Mr.Deng离职期间约定一起对其进行研究的,但因为我个人问题没能实施这个计划,留下些许遗憾... 最近,刚好在做...

Captain_小馬佩德罗
昨天
24
0
聊聊dubbo的DataStore

序 本文主要研究一下dubbo的DataStore DataStore dubbo-2.7.2/dubbo-common/src/main/java/org/apache/dubbo/common/store/DataStore.java @SPI("simple")public interface DataStore { ......

go4it
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部