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

概述

下载地址: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 {@Beanpublic FilterRegistrationBean<XxlSsoWebFilter> xxlSsoFilterRegistration(XxlSsoProperties xxlSsoProperties) {//filter initFilterRegistrationBean<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;}@Beanpublic 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}# 不需要拦截的urlexcludedPaths: /*.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;}@Overridepublic 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));}@Overridepublic 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 urlString servletPath = req.getServletPath();logger.info("servlet path={}",servletPath);// excluded path checkif (excludedPaths!=null && excludedPaths.trim().length()>0) {for (String excludedPath:excludedPaths.split(",")) {String uriPattern = excludedPath.trim();// 支持ANT表达式if (antPathMatcher.match(uriPattern, servletPath)) {// excluded path, allowchain.doFilter(request, response);return;}}}// logout path checkif (logoutPath!=null&& logoutPath.trim().length()>0&& logoutPath.equals(servletPath)) {logger.info("######################XxlSsoWebFilter logoutPath:{}",logoutPath);// remove cookieSsoWebLoginHelper.logout(req, res);// redirect logoutString 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 + redirectXxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(req, res);// valid login failif (xxlUser == null) {String header = req.getHeader("content-type");boolean isJson=  header!=null && header.contains("json");if (isJson) {// json msgres.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 linkString link = req.getRequestURL().toString();// redirect logoutString 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 userrequest.setAttribute(Conf.SSO_USER, xxlUser);// already login, allowchain.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;@Overridepublic 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.");}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {HttpServletRequest req = (HttpServletRequest) request;HttpServletResponse res = (HttpServletResponse) response;// make urlString servletPath = req.getServletPath();// excluded path checkif (excludedPaths!=null && excludedPaths.trim().length()>0) {for (String excludedPath:excludedPaths.split(",")) {String uriPattern = excludedPath.trim();// 支持ANT表达式if (antPathMatcher.match(uriPattern, servletPath)) {// excluded path, allowchain.doFilter(request, response);return;}}}// logout filterif (logoutPath!=null&& logoutPath.trim().length()>0&& logoutPath.equals(servletPath)) {// logoutSsoTokenLoginHelper.logout(req);// responseres.setStatus(HttpServletResponse.SC_OK);res.setContentType("application/json;charset=UTF-8");res.getWriter().println("{\"code\":"+ReturnT.SUCCESS_CODE+", \"msg\":\"\"}");return;}// login filterXxlSsoUser xxlUser = SsoTokenLoginHelper.loginCheck(req);if (xxlUser == null) {// responseres.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 userrequest.setAttribute(Conf.SSO_USER, xxlUser);// already login, allowchain.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.node0HTTP/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.node0HTTP/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 + redirectXxlSsoUser 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 cookieSsoWebLoginHelper.removeSessionIdByCookie(request, response);// set new cookieString 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 refreshif ((System.currentTimeMillis() - xxlUser.getExpireFreshTime()) > xxlUser.getExpireMinute()/2) {xxlUser.setExpireFreshTime(System.currentTimeMillis());SsoLoginStore.put(storeKey, xxlUser);}return xxlUser;}}return null;}

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

@Overridepublic 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);}}}
 @Overridepublic 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)@Overridepublic 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());//过期删除数据库对象,并返回nullif(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;// fieldprivate 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();// 作为baseUrlString 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.xdnf.cn/news/146035.html

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

相关文章

基于SpringBoot+Vue的时尚美妆电商网站系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目源码、Python精…

Adobe出现This unlicensed Photoshop app has been disabled

Adobe Acrobat或Photoshop软件突然出现This unlicensed Photoshop app has been disabled 症状 解决方法 删除软件安装目录下的AcroCEF和acrocef_1l两个子文件夹。主要是为了删除AcroCEF.exe。 如果存在复发&#xff0c;则删除xxxxxxx\AdobeGCClient\AdobeGCClient.exe。 不…

Win10 安装VS Code

一、软件介绍 Visual Studio Code&#xff08;简称VS Code&#xff09;是一个由微软开发的免费、开源的代码编辑器。它支持Windows、Linux和macOS操作系统&#xff0c;并且提供了许多功能&#xff0c;使其成为许多开发者的首选开发工具。以下是VS Code的一些主要特点&#xff…

如何在 Debian 系统中启用 root 用户的 SSH 登录功能?

本章教程主要介绍如何在 Debian 上启用 root 用户通过 SSH 登录功能。 注意:root 用户通过 SSH 登录可能会带来安全风险,建议仅在必要时使用,并确保有足够的安全措施。 1. 编辑 SSH 配置文件: 使用文本编辑器打开 SSH 配置文件:sudo vi /etc/ssh/sshd_config2. 修改 Permi…

14_Python面向对象

面向过程与面向对象 在编程范式&#xff08;programming paradigms&#xff09;中&#xff0c;面向过程&#xff08;Procedural Programming&#xff09;和面向对象&#xff08;Object-Oriented Programming&#xff0c;简称OOP&#xff09;是两种主要的编程风格。 Python是一…

vulnhub(12):bob 1.0.1(gpg文件解密)

端口 nmap主机发现 nmap -sn 192.168.72.0/24 ​ Nmap scan report for 192.168.72.169 Host is up (0.00020s latency). ​ 169是新出现的机器&#xff0c;他就是靶机 nmap端口扫描 nmap -Pn -sV 192.168.72.169 -p- --min-rate 10000 -oA nmap/scan 扫描开放端口保存到 nmap…

力扣最热一百题——除自身以外数组的乘积

目录 题目链接&#xff1a;238. 除自身以外数组的乘积 - 力扣&#xff08;LeetCode&#xff09; 题目描述 示例 提示&#xff1a; 解法一&#xff1a;左右数组&#xff08;小型动态规划&#xff09; 实现思路 Java写法&#xff1a; 运行时间 C写法&#xff1a; 运行时…

虚拟现实与PD协议快充

随着虚拟现实&#xff08;VR&#xff09;技术的不断进步&#xff0c;索尼的PlayStation VR2&#xff08;简称PS VR2&#xff09;凭借其卓越的性能和沉浸式体验&#xff0c;在游戏界引起了广泛关注。为了进一步拓展PS VR2的应用范围&#xff0c;索尼推出了PS VR2适配器&#xff…

IS-ISv4/6双栈

文章目录 IS-ISv4/6双栈实验要求配置 IS-ISv4/6双栈 实验要求 配置双栈 R1、2、3、4配置 IS-ISv4 和 IS-ISv6&#xff0c;配置IPv6多拓扑 上面为Level-1类型、中间为Level-1-2、下面是Level-2类型 还有就是说ATT位置1有一定要求连接L1/2连接L1或者L2类型路由器&#xff0c;至…

简单题94. 二叉树的中序遍历 (python)20240921

问题描述&#xff1a; #### python&#xff1a; # Definition for a binary tree node. # class TreeNode(object): # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right right class Solution(…

统信服务器操作系统【搭建FTP】设置介绍

如何在操作系统上安装vsftp服务。设置匿名用户登录、设置授权用户密码访问功能,并介绍使用匿名方式、授权用户方式访问vsftp服务。本文适用于A、D、E三个服务器操作系统版本,除安装方式的差异,其他设置均相同。 文章目录 功能概述一、功能介绍二、准备环境三、安装步骤1. 在…

pg_start_backup

pg_start_backup()函数在主库上发起一个在线备份&#xff0c;命令执行成功后&#xff0c;将数据文件拷贝到备份接口中 select pg_start_backup(full0918,false,false); 以上会话不要关闭&#xff0c;复制数据目录。 cp -r /pgdata/data/postgres-f66f5f7a/ /opt/qfusion/mnt/st…

光伏开发:一分钟生成光伏项目报告

传统光伏项目报告的编制往往需要收集大量数据、进行复杂计算与分析&#xff0c;耗时长且易受人为因素影响。自动生成光伏项目报告&#xff0c;依托大数据、云计算、人工智能等先进信息技术&#xff0c;实现了对光伏项目关键参数的快速分析、评估与预测。 一、核心功能与流程 1…

Leetcode面试经典150题-39.组合总数

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限制重复被选取 。如…

汇川AM400脉冲轴控制(轴控功能块ST源代码)

汇川AM400如何和编程软件通信连接 汇川AM400PLC如何和编程软件通信连接_汇川am400读取程序-CSDN博客文章浏览阅读159次。本文介绍了如何使用CODESYS编程软件与汇川AM400PLC进行通信连接,包括扫描网络、修改IP地址、刷新日志和下载监控程序的步骤。同时,文章提到了CODESYS编程…

python-3n+1数链/233

一&#xff1a;3n1数链题目描述 在计算机科学上&#xff0c;有很多类问题是无法解决的&#xff0c;我们称之为不可解决问题。然而&#xff0c;在很多情况下我们并不知道哪一类问题可以解决&#xff0c;哪一类问题不可解决。现在我们就有这样一个问题&#xff0c;问题如下&#…

DOG:知识图谱大模型问答的迭代交互式推理,克服长路径和假阳性关系挑战

DOG&#xff1a;知识图谱大模型问答的迭代交互式推理&#xff0c;克服长路径和假阳性关系挑战 秒懂大纲提出背景解法拆解全流程优化和医学关系 创意 秒懂大纲 ├── DoG框架【主题】 │ ├── 背景【研究背景】 │ │ ├── LLMs的局限性【问题描述】 │ │ │ …

go 读取excel

一、安装依赖 go get github.com/tealeg/xlsx二、main.go package mainimport "fmt" import "github.com/tealeg/xlsx"type Student struct {Name stringSex string }func (student Student) show() {fmt.Printf("Name:%s Sex:%s\r\n", stude…

消灭病毒gamedemo

DestoryVirus 一、AudioSourceManager using System.Collections; using System.Collections.Generic; using UnityEngine;public class AudioSourceManager : MonoBehaviour {public static AudioSourceManager Instance { get; private set; }public SoundPlayer soundPla…

JavaWeb初阶 day1

目录 tomcat目录结构 tomcat:web服务器软件 项目部署的方式 直接将项目放到webapps下 配置conf/server.xml文件 在conf\Catalina\localhost创建任意名称的xml文件。在文件中编写 静态项目和动态项目 Servlet Servlet执行原理 Servlet方法&#xff08;生命周期&#x…