概述
下载地址: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 的实现原理
项目配置
- 在需要信任的认证的项目中引入项目中引入jar包
<dependence>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-sso</artifactId>
<version>1.1.1-SNAPSHOT</version>
</dependence>
- 单独部署xxl-sso-sever 服务,xxl-sso 中的登录/登录调用xxl-sso-sever服务。 在xxl-sso-sever 中做登录/登出的业务操作,一般会调用业务系统的人员组件实现用户的登录/退出的业务操作。
目前开源的这个xxl-sso中存在的问题
- 项目的包名一般不符合一般公司的应用要求
- 项目中用户的sessionid的存储,登录/退出都是基于缓存的测试案例,没有做真正的登录业务操作
基于以上问题,一般公司拿到项目源码后会进行本地化改造,符合本公司的项目工程应用
sso-core start 组件化的实现单点登录自动配置原理
- 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)
-
需要单点登录用户的认证的服务引用,拦截用户的所有后台请求
-
判断请求路径是否在excludedPaths检验的路径中,如果是则拦截请求返回,跳过单点登录校验返回
-
检查用户是否是退出/loginout ,如果是退出操作,则
2.1 清除用户的登录缓存
2.2 判断是否是前后端分离项目,如果不是则直接重定向至退出登录页res.sendRedirect(logoutPageUrl);
重定向地址为 /xxxxxx-admin-app/logout
2.3 如果是前后端分离项目,则返回前端状态返回501,设置登录跳转的redirect_url为前端系统页面地址
重定向地址为 /xxxxxx-admin-app/logout?redirect_url= -
判断用户是否登录,根据用户请求的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)
需要单点登录用户的认证的服务引用,拦截用户的所有后台请求
- 判断请求路径是否在excludedPaths检验的路径中,如果是则拦截请求返回,跳过单点登录校验
- 检查用户是否是退出/loginout ,如果是退出操作,则清除用户的登录缓存,返回状态200,退出登录成功!
- 判断用户是否登录,根据用户请求的xxl_sso_sessionid 获取登录用户,如果获取不到怎,则返会状态501,提示sso not login
- 如果能获取到登录用户,则在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.请求关接口
包括
-
-
js css等前端静态页面
-
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)
- 根据对象请求头获取xxl_sso_sessionid
- 根据对象请求头xxl_sso_sessionid 删除缓存记录
如果是记录在mysql 中,则缓存记录表结果如下
如果是记录在mysql 中,则缓存记录表结果如下
id | bigint | 非空 | 主键id |
---|---|---|---|
cache_key | varchar | 缓存键 | |
cache_value | text | 缓存值 | |
gmt_expiration | datetime | 有效期 |
如果是记录在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());
}