XXl-SSO分布式单点登录框架

news2024/11/15 13:50:25

概述

下载地址:https://gitee.com/xuxueli0323/xxl-sso
文档地址:https://www.xuxueli.com/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 的实现原理

项目配置

  1. 在需要信任的认证的项目中引入项目中引入jar包
  <dependence>
        <groupId>com.xuxueli</groupId>
        <artifactId>xxl-sso</artifactId>
        <version>1.1.1-SNAPSHOT</version>
    </dependence>
  1. 单独部署xxl-sso-sever 服务,xxl-sso 中的登录/登录调用xxl-sso-sever服务。 在xxl-sso-sever 中做登录/登出的业务操作,一般会调用业务系统的人员组件实现用户的登录/退出的业务操作。

目前开源的这个xxl-sso中存在的问题

  • 项目的包名一般不符合一般公司的应用要求
  • 项目中用户的sessionid的存储,登录/退出都是基于缓存的测试案例,没有做真正的登录业务操作

基于以上问题,一般公司拿到项目源码后会进行本地化改造,符合本公司的项目工程应用

sso-core start 组件化的实现单点登录自动配置原理

  1. spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.xxl.sso.core.filter.XxlSsoConfig,\
com.xxl.sso.core.conf.FeignAutoConfiguration  

需要被认证服务中的xxl.sso.的属性初始化,以及sso-core 包中的fegin接口

``


@Configuration
@EnableFeignClients(basePackages = "com.xxl.sso.core.api")
@ComponentScan("com.xxl.sso.core.store")
public class FeignAutoConfiguration {

}


package com.xxl.sso.core.filter;

import com.xxl.sso.core.conf.Conf;
import com.xxl.sso.core.web.SsoReceiveController;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;

/**
 * @author xuxueli 2018-11-15
 */
@Configuration
@ConditionalOnProperty(prefix = "xxl.sso", name = "enable", havingValue = "true")
@EnableConfigurationProperties(XxlSsoProperties.class)
public class XxlSsoConfig {

    @Bean
    public FilterRegistrationBean<XxlSsoWebFilter> xxlSsoFilterRegistration(XxlSsoProperties xxlSsoProperties) {
        //filter init
        FilterRegistrationBean<XxlSsoWebFilter> registration = new FilterRegistrationBean<XxlSsoWebFilter>();
        registration.setName("XxlSsoWebFilter");
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
        registration.addUrlPatterns(xxlSsoProperties.getUrlPatterns());
        registration.setFilter(new XxlSsoWebFilter(xxlSsoProperties));
        registration.addInitParameter(Conf.SSO_SERVER, xxlSsoProperties.getServer());
        registration.addInitParameter(Conf.SSO_LOGOUT_PATH, xxlSsoProperties.getLogoutPath());
        registration.addInitParameter(Conf.SSO_EXCLUDED_PATHS, xxlSsoProperties.getExcludedPaths());
        registration.setEnabled(xxlSsoProperties.getEnable());
        return registration;
    }

    @Bean
    public SsoReceiveController receiveController(){
        return new SsoReceiveController();
    }

}

单点登录属性配置

@Component
@ConfigurationProperties(prefix = "xxl.sso")
@Data
public class XxlSsoProperties {
    /**
     * sso后端服务地址
     */
    private String server;

    /**
     * 登出地址
     */
    private String logoutPath;

    /**
     * 不需要拦截的url
     */
    private String excludedPaths;

    /**
     * 需要拦截的路径
     */
    private String urlPatterns;

    /**
     * 接入业务系统前端地址
     */
    private String clientWebUrl;
    /**
     * 接收session
     */
    private String receiveSession;
    /**
     * 业务系统是否是前后端分离
     */
    private Boolean redirect = false;

    /**
     * 登录校验开关
     */
    private Boolean enable = false;

    /**
     * 是否仅允许单个客户端登录
     */
    private Boolean singleClientEnable = false;
    
    /**登录地址*/
    private String loginPage;

    /**用户管理页*/
    private String userMgrPage;
    
    private String redirectUrl;
    
    private String toUrl;

}

xxl.sso需要单点登录应用的配置

提供是否开启、后端服务认证服务地址、后端跳转地址、退出/未登录等跳转前端页面地址、不需要请求拦截的地址等配置、门户首页地址、退出登录请求接口、用户认证方式

xxl:
  sso:
    # 是否开启登录校验
    enable: false
    # 是否仅允许单个客户端登录
    singleClientEnable: ${singleClientEnable:false}
    # sso后端服务地址
    server: ${xxlSsoServer:http:///前端ip:5011}
    # 默认跳转后端
    redirectUrl: ${xxlSsoRedirectUrl:http:///前端ip:5011/sso-server/sso/receive}
    # 默认跳转前端页面
    toUrl: ${xxlSsoToUrl:http:///前端ip:5011}
    # 登录页
    loginPage: ${loginPage:http:///前端ip:5011/sso-server/static/login.html}
    # 用户管理页
    userMgrPage: ${userMgrPage:http:///前端ip:5011/usercenter-admin-web/}
    # 登出接口
    logoutPath: ${xxlLogoutPath:/logout}
    # 不需要拦截的url
    excludedPaths: /*.html,/*.ico,/**/*.html,/**/*.css,/**/*.js,/**/*.png,/**/*.jpg,/**/*.docx,/sso/login,/potral/**,/restapi/**,/swagger-ui.html,/v2/api-docs,/swagger-resources/**,/webjars/**,/doc.html,/sso-server/doLogin2,/doGetLoginType/**,/usercenter-admin-app/admin/sysCarousel/**,/usercenter/sysCache/**
    # sso需要的redis地址
    redisAddress: redis://xxl-qweqwe:xxx_2024@/前端ip:6379/0
    # 需要拦截的url,默认所有请求都拦截,这里配置/*不要/**
    urlPatterns: ${xxlUrlPatterns:/admin/*}
    redis:
      expire:
        minute: ${xxlRedisExpireMinute:1440}
    #mysql替换redis开关(true:使用mysql替换redis)
    mysqlSubstitute: ${xxlSsoMysqlSubstitute:false}
    #cache过期时间(mysql过期时间读取此配置)
    cache:
      expireMinute: ${xxlCacheExpireMinute:1440}
    #登出是否跳门户首页
    logout2ComacPortal:
      #匹配的地址清单,多个逗号分割
      matchedUrls:
      #门户首页地址
      comacPortalUrl:

单点登录过滤

XxlSsoWebFilter (sso-core)

  1. 需要单点登录用户的认证的服务引用,拦截用户的所有后台请求

    1. 判断请求路径是否在excludedPaths检验的路径中,如果是则拦截请求返回,跳过单点登录校验返回

    2. 检查用户是否是退出/loginout ,如果是退出操作,则

      2.1 清除用户的登录缓存

      2.2 判断是否是前后端分离项目,如果不是则直接重定向至退出登录页res.sendRedirect(logoutPageUrl);
      重定向地址为 /xxxxxx-admin-app/logout
      2.3 如果是前后端分离项目,则返回前端状态返回501,设置登录跳转的redirect_url为前端系统页面地址
      重定向地址为 /xxxxxx-admin-app/logout?redirect_url=

    3. 判断用户是否登录,根据用户请求的xxl_sso_sessionid 获取登录用户,如果获取不到怎,则返会状态501,提示sso not login,返回数据包含跳转地址,由前端去控制是否跳转至登录页
      返回包含的数据为

http://前端IP:5011/sso-server/sso/receive/login?redirect_url=http://前端IP:5011/xxx-admin-app/sso/receive&to_url=

   4. 如果能获取到登录用户,则在request中设置登录用户,xxl_sso_user,保存登录的用户
public class XxlSsoWebFilter extends HttpServlet implements Filter {
    private static Logger logger = LoggerFactory.getLogger(XxlSsoWebFilter.class);

    private static final AntPathMatcher antPathMatcher = new AntPathMatcher();

    private String ssoServer;
    private String logoutPath;
    private String excludedPaths;
    private XxlSsoProperties xxlSsoProperties;

    public XxlSsoWebFilter (XxlSsoProperties xxlSsoProperties){
        this.xxlSsoProperties = xxlSsoProperties;
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

        ssoServer = filterConfig.getInitParameter(Conf.SSO_SERVER);
        logoutPath = filterConfig.getInitParameter(Conf.SSO_LOGOUT_PATH);
        excludedPaths = filterConfig.getInitParameter(Conf.SSO_EXCLUDED_PATHS);

        logger.info("XxlSsoWebFilter init,xxlSsoProperties:{}",JSON.toJSONString(xxlSsoProperties));
    }

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



        logger.info("-过滤请求--->{}", request.toString());
        logger.info("--过滤参数--->{}", request.getParameterMap().toString());
        logger.info("-过滤响应--->{}", response.toString());


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

        logger.info("servlet path={}",servletPath);

        // 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)) {
            logger.info("######################XxlSsoWebFilter logoutPath:{}",logoutPath);
            // remove cookie
            SsoWebLoginHelper.logout(req, res);

            // redirect logout
            String logoutPageUrl = ssoServer.concat(Conf.SSO_LOGOUT);
            //判断业务系统是否是前后端分离
            if (xxlSsoProperties.getRedirect()){
                Map<String,String> dataMap = new HashMap<>();
                dataMap.put("loginPageUrl",logoutPageUrl+"?redirect_url=");
                ResponseUtils.writeResponse(res,BaseResult.buildError(Conf.SSO_LOGIN_FAIL_RESULT.getCode(),Conf.SSO_LOGIN_FAIL_RESULT.getMsg(),dataMap));
                return;
            }
            res.sendRedirect(logoutPageUrl);
            return;
        }

        // valid login user, cookie + redirect
        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");
                Map<String,String> dataMap = new HashMap<>();
                dataMap.put("loginPageUrl", ssoServer
                        .concat(Conf.SSO_LOGIN) + "?" + Conf.REDIRECT_URL + "=" + xxlSsoProperties.getClientWebUrl() + xxlSsoProperties.getReceiveSession() + "&"
                        + Conf.TO_URL + "=");
                ResponseUtils.writeResponse(res, BaseResult.buildError(Conf.SSO_LOGIN_FAIL_RESULT.getCode(), Conf.SSO_LOGIN_FAIL_RESULT.getMsg(),dataMap));
                return;
            } else {
                // total link
                String link = req.getRequestURL().toString();
                // redirect logout
                String loginPageUrl = ssoServer.concat(Conf.SSO_LOGIN)
                        + "?" + Conf.REDIRECT_URL + "=" + link;
                //判断业务系统是否是前后端分离
                if (xxlSsoProperties.getRedirect()){
                    res.getWriter().println(
                            "{\"code\":" + Conf.SSO_LOGIN_FAIL_RESULT.getCode() + ", \"msg\":\"" + Conf.SSO_LOGIN_FAIL_RESULT.getMsg() + "\", \"loginPageUrl\":\"" + ssoServer
                                    .concat(Conf.SSO_LOGIN) + "?" + Conf.REDIRECT_URL + "="+xxlSsoProperties.getClientWebUrl()+xxlSsoProperties.getReceiveSession()+"&"+Conf.TO_URL+"=" + "\"}");
                    return;
                }
                res.sendRedirect(loginPageUrl);
                return;
            }
        }

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


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

}

XxlSsoTokenFilter(sso-core)

需要单点登录用户的认证的服务引用,拦截用户的所有后台请求

  1. 判断请求路径是否在excludedPaths检验的路径中,如果是则拦截请求返回,跳过单点登录校验
  2. 检查用户是否是退出/loginout ,如果是退出操作,则清除用户的登录缓存,返回状态200,退出登录成功!
  3. 判断用户是否登录,根据用户请求的xxl_sso_sessionid 获取登录用户,如果获取不到怎,则返会状态501,提示sso not login
  4. 如果能获取到登录用户,则在request中设置登录用户,xxl_sso_user,保存登录的用户
public class XxlSsoTokenFilter extends HttpServlet implements Filter {
    private static Logger logger = LoggerFactory.getLogger(XxlSsoTokenFilter.class);

    private static final AntPathMatcher antPathMatcher = new AntPathMatcher();

    private String ssoServer;
    private String logoutPath;
    private String excludedPaths;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

        ssoServer = filterConfig.getInitParameter(Conf.SSO_SERVER);
        logoutPath = filterConfig.getInitParameter(Conf.SSO_LOGOUT_PATH);
        excludedPaths = filterConfig.getInitParameter(Conf.SSO_EXCLUDED_PATHS);

        logger.info("XxlSsoTokenFilter init.");
    }

    @Override
    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 filter
        if (logoutPath!=null
                && logoutPath.trim().length()>0
                && logoutPath.equals(servletPath)) {

            // logout
            SsoTokenLoginHelper.logout(req);

            // response
            res.setStatus(HttpServletResponse.SC_OK);
            res.setContentType("application/json;charset=UTF-8");
            res.getWriter().println("{\"code\":"+ReturnT.SUCCESS_CODE+", \"msg\":\"\"}");

            return;
        }

        // login filter
        XxlSsoUser xxlUser = SsoTokenLoginHelper.loginCheck(req);
        if (xxlUser == null) {

            // response
            res.setStatus(HttpServletResponse.SC_OK);
            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;
        }

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


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

用户未登录跳转至用户认证页面

1.请求关接口

包括

    1. js css等前端静态页面

    2. GET /flow-platform/message/msgtype? 获取消息类型

      GET /flow-platform/message/msgtype? HTTP/1.1

      Host: /前端ip:5011

      Connection: keep-alive

      Access-Control-Allow-Origin: *

      Accept: application/json

      x-requested-with: XMLHttpRequest

      User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36

      Content-Type: application/json

      Referer: http://前端ip:5011/xxxxx-web/home

      Accept-Encoding: gzip, deflate

      Accept-Language: zh-CN,zh;q=0.9

      HTTP/1.1 200

      Server: nginx/1.26.1

      Date: Sat, 14 Sep 2024 09:36:56 GMT

      Content-Type: application/json;charset=UTF-8

      Content-Length: 197

      Connection: keep-alive

      Access-Control-Allow-Origin: *

      Access-Control-Allow-Methods: GET,POST

      Access-Control-Allow-Headers: DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,__gsuite_user_id,__gsuite_user_name

      {“code”:501,“message”:“sso not login.”,“data”:{“loginPageUrl”:“http:/10.216.40.123:8089/sso-server/login?redirect_url=http:///前端ip:5011/flowplatform-web/flow-platform/sso/receive&to_url=”}}

用户退出

POST /usercenter-admin-app/logout HTTP/1.1
Host: /前端ip:5011
Connection: keep-alive
Content-Length: 0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http:///前端ip:5011
Referer: http:/前端ip:5011/usercenter-admin-web/authoritymanage/usermanage
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: xxl_sso_sessionid=99779001_d9b3bd189e2540b78523e707ab6fcefb; JSESSIONID=node01rkxul7artxv61t4oxv8fffog05184.node0


HTTP/1.1 200 OK
Server: nginx/1.26.1
Date: Wed, 18 Sep 2024 03:37:49 GMT
Content-Type: application/json;charset=UTF-8
Content-Length: 135
Connection: keep-alive
Set-Cookie: xxl_sso_sessionid=""; Version=1; Path=/; Domain="http://cmos-base-usercenter/"; HttpOnly; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:00 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST
Access-Control-Allow-Headers: DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,__gsuite_user_id,__gsuite_user_name

{"code":501,"message":"sso not login.","data":{"loginPageUrl":"http:///前端ip:5011/sso-server/sso/receive/logout?redirect_url="}}




GET /sso-server/sso/receive/logout?redirect_url=http%3A%2F%2F前端ip%3A5011%2Fusercenter-admin-web%2F HTTP/1.1
Host: 前端ip
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://前端ip:5011/usercenter-admin-web/authoritymanage/usermanage
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: xxl_sso_sessionid=99779001_d9b3bd189e2540b78523e707ab6fcefb; JSESSIONID=node01rkxul7artxv61t4oxv8fffog05184.node0


HTTP/1.1 404 
Server: nginx/1.26.1
Date: Wed, 18 Sep 2024 03:37:50 GMT
Content-Type: text/html;charset=UTF-8
Content-Length: 341
Connection: keep-alive
Content-Language: zh-CN

<html><body><h1>Whitelabel Error Page</h1><p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p><div id='created'>Wed Sep 18 11:41:48 CST 2024</div><div>There was an unexpected error (type=Not Found, status=404).</div><div>No handler found for GET /sso-server/sso/receive/logout</div></body></html>
GET /favicon.ico HTTP/1.1
Host: 前端ip:5011
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Referer: http://前端ip:5011/sso-server/sso/receive/logout?redirect_url=http%3A%2F%2F前端ip%3A5011%2Fusercenter-admin-web%2F
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: xxl_sso_sessionid=99779001_d9b3bd189e2540b78523e707ab6fcefb; JSESSIONID=node01rkxul7artxv61t4oxv8fffog05184.node0

后台业务操作

退出登录后台操作

SsoTokenLoginHelper.logout(req)

  1. 根据对象请求头获取xxl_sso_sessionid
  2. 根据对象请求头xxl_sso_sessionid 删除缓存记录

如果是记录在mysql 中,则缓存记录表结果如下

如果是记录在mysql 中,则缓存记录表结果如下

idbigint非空主键id
cache_keyvarchar缓存键
cache_valuetext缓存值
gmt_expirationdatetime有效期

如果是记录在redis中则,根据xxl_sso_sessionid 删除redis中的缓存

检查登录

 // valid login user, cookie + redirect
    XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(req, res);
 public static XxlSsoUser loginCheck(HttpServletRequest request, HttpServletResponse response){
        String cookieSessionId = CookieUtil.getValue(request, Conf.SSO_SESSIONID);
        XxlSsoUser xxlUser = SsoTokenLoginHelper.loginCheck(cookieSessionId);
        if (xxlUser != null) {
            return xxlUser;
        }
        // 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(request,response, Conf.SSO_SESSIONID, paramSessionId, false);    // expire when browser close (client cookie)
            return xxlUser;
        }
        return null;
    }
  • CookieUtil.getValue 方法
	/**
	 * 查询value
	 *
	 * @param request
	 * @param key
	 * @return
	 */
	public static String getValue(HttpServletRequest request, String key) {
		Cookie cookie = get(request, key);
		if (cookie != null) {
			return cookie.getValue();
		}
		return null;
	}
  • SsoTokenLoginHelper.loginCheck方法
 /**
     * login check
     *
     * @param sessionId
     * @return
     */
    public static XxlSsoUser loginCheck(String  sessionId){

        String storeKey = SsoSessionIdHelper.parseStoreKey(sessionId);
        if (storeKey == null || "null".equals(storeKey)) {
            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;
    }

根据缓存的session-id,一般是从缓存或者接口中查询session-id 对应的用户信息
如果从数据库中查询


 @Override
    public BaseResult<SysCacheResVO> cacheValAqusitionByCacheKey(@RequestBody SysCacheKeyReqVO vo) {
        log.info("SysCacheController cacheValAqusitionByCacheKey() executed.param is {}", vo.getCacheKey());
        if (mysqlSubstitute) {
            if (ObjectUtils.isEmpty(vo) || ObjectUtils.isEmpty(vo.getCacheKey())) {
                log.error("SysCacheController cacheEvictionByCacheKey() param cacheKey is null");
                return BaseResult.buildError("SysCacheController cacheValAqusitionByCacheKey() param cacheKey is null.");
            }
            ReturnT<SysCacheResVO> result = sysCacheService.findByCacheKey(vo.getCacheKey());
            if (ReturnT.FAIL_CODE != result.getCode()) {
                log.debug("###############SysCacheController cacheValAqusitionByCacheKey in mysql, cacheKey:{},xxlSsoUser:{}", result.getData().getCacheKey(), result.getData().getCacheValueObj());
                log.info("SysCacheController cacheValAqusitionByCacheKey() successfully executed.");
                return BaseResult.buildSuccess(result.getData());
            } else {
                log.error("SysCacheController cacheValAqusitionByCacheKey() failed to execute.");
                return BaseResult.buildError("SysCacheController cacheValAqusitionByCacheKey() failed to execute.");
            }
        } else {
            if (!ObjectUtils.isEmpty(vo) && !ObjectUtils.isEmpty(vo.getCacheKey())) {
                Object objectValue = JedisUtil.getObjectValue(vo.getCacheKey());
                XxlSsoUser xxlUser = (XxlSsoUser) objectValue;
                SysCacheResVO sysCacheResVO = new SysCacheResVO();
                sysCacheResVO.setCacheKey(vo.getCacheKey());
                sysCacheResVO.setCacheValueObj(xxlUser);
                log.info("###############SysCacheController cacheValAqusitionByCacheKey in redis, cacheKey:{},xxlSsoUser:{}", vo.getCacheKey(), xxlUser);
                return BaseResult.buildSuccess(sysCacheResVO);
            } else {
                log.error("SysCacheController cacheValAqusitionByCacheKey() objectValue is null.");
                return BaseResult.buildSuccess(null);
            }
        }
    }
 @Override
    public BaseResult<SysCacheResVO> xxxxxcacheValAqusitionByCacheKey(@RequestBody SysCacheKeyReqVO vo) {
        log.info("SysCacheController cacheValAqusitionByCacheKey() executed.param is {}", vo.getCacheKey());
        if (mysqlSubstitute) {
            if (ObjectUtils.isEmpty(vo) || ObjectUtils.isEmpty(vo.getCacheKey())) {
                log.error("SysCacheController cacheEvictionByCacheKey() param cacheKey is null");
                return BaseResult.buildError("SysCacheController cacheValAqusitionByCacheKey() param cacheKey is null.");
            }
            ReturnT<SysCacheResVO> result = sysCacheService.findByCacheKey(vo.getCacheKey());
            if (ReturnT.FAIL_CODE != result.getCode()) {
                log.info("SysCacheController cacheValAqusitionByCacheKey() successfully executed.");
                return BaseResult.buildSuccess(result.getData());
            } else {
                log.error("SysCacheController cacheValAqusitionByCacheKey() failed to execute.");
                return BaseResult.buildError("SysCacheController cacheValAqusitionByCacheKey() failed to execute.");
            }
        } else {
            if (!ObjectUtils.isEmpty(vo) && !ObjectUtils.isEmpty(vo.getCacheKey())) {
                Object objectValue = JedisUtil.getObjectValue(vo.getCacheKey());
                XxlSsoUser xxlUser = (XxlSsoUser) objectValue;
                SysCacheResVO sysCacheResVO = new SysCacheResVO();
                sysCacheResVO.setCacheKey(vo.getCacheKey());
                sysCacheResVO.setCacheValueObj(xxlUser);
                return BaseResult.buildSuccess(sysCacheResVO);
            } else {
                return BaseResult.buildSuccess(null);
            }
        }
    }

如果从数据库中查询如下

 @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    @Override
    public ReturnT<SysCacheResVO> findXXXByCacheKey(String cacheKey) {
        SysCacheResVO sysCacheResVO = new SysCacheResVO();
        CompletableFuture<SysCache> selectCompletableFuture = CompletableFuture.supplyAsync(() -> {
            SysCache sysCache = sysCacheDomain.getOneByCacheKey(cacheKey);
            return sysCache;
        }, executor).thenApply((sysCache) -> {
            //根据查询结果判断是否删除
            if(!ObjectUtils.isEmpty(sysCache)){
                LocalDateTime expiration = sysCache.getGmtExpiration();
                if(null != expiration){
                    //-1:小于
                    int compareResult = expiration.compareTo(LocalDateTime.now());
                    //过期删除数据库对象,并返回null
                    if(compareResult <= 0 && !ObjectUtils.isEmpty(sysCache.getId())){//过期
                        int deleteResult = sysCacheDomain.deleteById(sysCache.getId());
                        if(deleteResult > 0){
                            sysCache = null;
                        }
                    }
                }
            }
            return sysCache;
        });
        try {
            CompletableFuture.allOf(selectCompletableFuture).get();
            SysCache sysCache = selectCompletableFuture.get();
            if(!ObjectUtils.isEmpty(sysCache) && !ObjectUtils.isEmpty(sysCache.getCacheValue())){
                sysCacheResVO.setCacheKey(cacheKey);       sysCacheResVO.setCacheValueObj(JedisUtil.unserialize(Base64.getDecoder().decode(sysCache.getCacheValue())));
            }
        } catch (Exception e) {
            log.error("SysCacheService findByCacheKey() 异步线程异常:", e);
            return new ReturnT(ReturnT.FAIL_CODE, "findByCacheKey异步执行异常");
        }
        return new ReturnT(sysCacheResVO);
    }
  • XxlSsoUser
public class XxlSsoUser implements Serializable {
    private static final long serialVersionUID = 42L;

    // field
    private String userid;
    private String username;
    private String userCode;
    private Long employeeId;
    private String employeeCode;
    private Map<String, String> plugininfo;

    private String version;
    private int expireMinute;
    private long expireFreshTime;
    }

/sso/receive 登录跳转 移除掉SSO_SESSIONID和TO_URL两个参数

 @GetMapping("/sso/receive")
    public void xxxreceive(HttpServletRequest request, HttpServletResponse response) throws IOException {
        StringBuilder builder = new StringBuilder();
        // 作为baseUrl
        String toUrl = request.getParameter(Conf.TO_URL);
        builder.append(toUrl);
        for (Map.Entry entry : request.getParameterMap().entrySet()) {
            String key = entry.getKey().toString();
            // 排除这两个参数
            if ((!Conf.SSO_SESSIONID.equals(key)) && (!Conf.TO_URL.equals(key))) {
                builder.append("&").append(key).append("=").append(request.getParameter(key));
            }
        }
        response.sendRedirect(builder.toString());
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2163423.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

工业数据采集系统

一、网页部分代码 效果图&#xff1a; 代码实现&#xff1a; <!DOCTYPE html> <html lang"zh"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0&qu…

中国蚁剑(antSword)安装使用

antSword下载 antSword-Loader下载 作者&#xff1a;程序那点事儿 日期&#xff1a;2024/09/12 19:35 中国蚁剑&#xff08;AntSword&#xff09;是一款跨平台的开源网站管理工具&#xff0c;旨在满足渗透测试人员的需求。它是一个功能强大的工具&#xff0c;可以帮助用户管理…

这样做PPT也太酷了吧,27.9kstar,适合开发者的ppt工具推荐

1 slidev简介 slidev 是一个基于 Vue 开发的网页演示文稿工具,主要功能是将 Markdown 文档转换为演示幻灯片。 为开发者打造的演示文稿工具 Slidev最大的优势在于整个演示文稿内容的编写采用简单的Markdown格式,这极大降低了创作门槛。我们可以使用VS Code等编辑器高效编写Mar…

Linux centerOS 服务器搭建NTP服务

1&#xff0c;安装 NTP软件 sudo yum -y install ntp2&#xff0c;编辑配置文件 sudo vim /etc/ntp.conf 3&#xff0c;修改配置 在ntp.conf文件中&#xff0c;可以配置服务器从哪些上游时间源同步时间。如果你想让你的服务器对外同步时间&#xff0c;可以去掉restrict d…

js中Fucntion的意义

在js中&#xff0c;我们常常如下方式写函数&#xff1a; function fn(){console.log("这是一个函数."); }; fn(); 在js中&#xff0c;函数本质就是一个对象。 那么&#xff0c;结合我的上一篇文章&#xff1a;通俗讲解javascript的实例对象、原型对象和构造函数以及…

C盘空间不足--WizTree(管理空间)

WizTree&#xff1a;高效的磁盘空间分析工具 在日常使用电脑的过程中&#xff0c;磁盘空间的管理常常成为一个棘手的问题。随着文件的不断增加&#xff0c;我们的硬盘空间逐渐被占满&#xff0c;而这些文件中有很多其实并不重要。为了帮助用户更好地管理磁盘空间&#xff0c;Wi…

CNS-WRFID-01地标卡读写器|写卡器DEMO软件读、写操作说明

CNS-WRFID-01地标卡读写器|写卡器是一款高频读写设备&#xff0c;支持ISO15693协议芯片卡&#xff0c;地标标签读写&#xff0c;支持兴颂系列抗金属|非抗金属RFID标签&#xff0c;如&#xff1a;CNS-CRFID-01、CNS-CRFID-02、CNS-CRFID-03、CNS-CRFID-04、CNS-CRFID-05、CNS-CR…

Vue3 中集成海康 H5 监控视频播放功能

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来Vuet篇专栏内容:Vue-集成海康 H5 监控视频播放功能 目录 一、引言 二、环境搭建 三、代码解析 子组件部分 1.…

Spring项目报错:Public Key Retrieval is not allowed

报错原因&#xff1a; 如图 在你项目的数据库下可以找到该文件 解决方案&#xff1a; 在service-oa模块的application-dev.yml文件中 在红框中改成下面代码&#xff0c;数据库与具体项目保持一致&#xff0c;不一定是我这个&#xff0c;改allowPublicKeyRetrievaltrue String…

支持K歌音箱方案应用的高性能 32 位蓝牙音频应用处理器-BP1048B2

DSP是一类嵌入式通用可编程微处理器&#xff0c;主要用于实现对信号的采集、识别、变换、增强、控制等算法处理&#xff0c;是各类嵌入式系统的“大脑”应用十分广泛。BP1048B2是一款高性能DSP音频数字信号处理器芯片&#xff0c;能实现多种音频功能如混响、均衡、滤波、反馈抑…

Python邮件发送附件:怎么配置SMTP服务器?

Python邮件发送附件如何实现&#xff1f;Python发送带附件邮件&#xff1f; 在自动化和脚本编写中&#xff0c;使用Python发送带有附件的邮件是一个非常实用的功能。AokSend将详细介绍如何配置SMTP服务器&#xff0c;以便在Python中实现邮件发送附件的功能。 Python邮件发送附…

Postman/Jmeter接口测试

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、什么是接口测试 通常做的接口测试指的是系统对外的接口&#xff0c;比如你需要从别的系统来获取到或者同步资源与信息&#xff0c;他们会提供给你一个写好…

独立站烧钱背后:Facebook 广告转化率策略的神秘“致胜法则”

Facebook广告作为大部分零售独立站的主要“烧钱”渠道&#xff0c;投放策略的决策往往比日常操作技巧更为重要。 之前出海笔记分享过关于在我们烧了一年广告预算后&#xff0c;对人群定位的总结 这次在另外一个项目上再次印证了我们的推断。 省流&#xff1a;直接告诉你们结论…

全国糖酒会全域采购商选品会前瞻-见证零售新势力的崛起与变革

消费市场正经历着前所未有的变革。新兴零售渠道如雨后春笋般迅速崛起&#xff0c;对传统大卖场和电商模式提出了严峻挑战。在这一变革浪潮中&#xff0c;仓储会员店、零食折扣店、即时零售、内容电商以及私域团购等新兴模式脱颖而出&#xff0c;零售商与供应商关系被重构&#…

2024最新Linux发行版,Kali Linux迎来劲敌,零基础入门到精通,收藏这一篇就够了

概念简介 Parrot OS 是一款基于 Debian 的 Linux 发行版&#xff0c;专门为安全研究、渗透测试、开发以及隐私保护而设计。由 Frozenbox 团队开发的 Parrot OS&#xff0c;结合了现代安全工具、用户友好的环境以及出色的隐私保护特性&#xff0c;成为网络安全从业者、开发人员…

EasyExcel 多个不同对象集合,导入同一个sheet中

需求&#xff1a;将两块数据&#xff0c;写入要一个excel里面。 根据项目&#xff0c;导入依赖版本 <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.0.5</version> <…

旷视 ShuffleNetV2

目录 前言 一、ShuffleNetV2带来的创新点四条轻量化网络设计原则 1.1 准则一&#xff1a;相同的输入输出通道数能够减少内存访问成本(MAC) 1.2 准则二: 过多的分组卷积会增加 MAC 1.3 准则三: 网络的碎片化程度会减少并行化程度&#xff08;碎片化操作对并行加速不友好&am…

用计算器学习float、输入输出函数的格式化

上一篇介绍了C语言的一部分基础知识&#xff0c;今天我们来继续深入一点讲。 引入浮点数——float 做一个加减法计算器显然不能满足其他计算需求&#xff0c;比如我们生活中的东西并不总是用整数来衡量。我们有五毛钱&#xff0c;有半杯水&#xff0c;有随时都在变的速度&…

大模型面试百问百答

大家好&#xff0c;这里是 大模型八哥。今天分享大模型面试相关知识点&#xff0c;持续更新。 1. RAG技术体系的总体思路 数据预处理->分块&#xff08;这一步骤很关键&#xff0c;有时候也决定了模型的效果&#xff09;->文本向量化->query向量化->向量检索->重…

钰泰-ETA6964A 锂电池充电器IC

描述. ETA6964A 是一款高度集成的 3A 开关模式电池充电管理和系统电源路径管理器件&#xff0c;适用于单节锂离子和锂聚合物电池。它具有快速充电功能&#xff0c;支持高输入电压&#xff0c;适用于各种智能手机、平板电脑和便携式设备。其低阻抗电源路径优化了开关模式的运行…