feat(sso): 单点注销支持单设备注销

This commit is contained in:
click33 2025-05-01 16:10:02 +08:00
parent c2dea166e4
commit 5aac119beb
13 changed files with 233 additions and 131 deletions

View File

@ -34,7 +34,7 @@ public class H5Controller {
return SaResult.data(redirect);
} else {
// 模式二或模式三
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), client, redirect);
String redirectUrl = SaSsoUtil.buildRedirectUrl(client, redirect, StpUtil.getLoginId(), StpUtil.getLoginDeviceId());
return SaResult.data(redirectUrl);
}
}

View File

@ -5,6 +5,8 @@ import cn.dev33.satoken.sign.SaSignUtil;
import cn.dev33.satoken.sso.config.SaSsoServerConfig;
import cn.dev33.satoken.sso.processor.SaSsoServerProcessor;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
@ -44,7 +46,8 @@ public class SsoServerController {
ssoServer.doLoginHandle = (name, pwd) -> {
// 此处仅做模拟登录真实环境应该查询数据进行登录
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
String deviceId = SaHolder.getRequest().getParam("deviceId", SaFoxUtil.getRandomString(32));
StpUtil.login(10001, SaLoginParameter.create().setDeviceId(deviceId));
return SaResult.ok("登录成功!").setData(StpUtil.getTokenValue());
}
return SaResult.error("登录失败!");

View File

@ -25,6 +25,7 @@ import cn.dev33.satoken.sso.template.SaSsoClientTemplate;
import cn.dev33.satoken.sso.template.SaSsoTemplate;
import cn.dev33.satoken.sso.util.SaSsoConsts;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.parameter.SaLogoutParameter;
import cn.dev33.satoken.util.SaResult;
/**
@ -64,9 +65,12 @@ public class SaSsoMessageLogoutCallHandle implements SaSsoMessageHandle {
// 获取参数
String loginId = req.getParamNotNull(paramName.loginId);
String deviceId = message.getString(paramName.deviceId);
// 注销当前应用端会话
stpLogic.logout(loginId);
stpLogic.logout(loginId, new SaLogoutParameter()
.setDeviceId(deviceId)
);
// 响应
return SaResult.ok("单点注销回调成功");

View File

@ -18,15 +18,15 @@ package cn.dev33.satoken.sso.message.handle.server;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.sso.SaSsoManager;
import cn.dev33.satoken.sso.config.SaSsoServerConfig;
import cn.dev33.satoken.sso.message.SaSsoMessage;
import cn.dev33.satoken.sso.message.handle.SaSsoMessageHandle;
import cn.dev33.satoken.sso.model.TicketModel;
import cn.dev33.satoken.sso.name.ParamName;
import cn.dev33.satoken.sso.template.SaSsoServerTemplate;
import cn.dev33.satoken.sso.template.SaSsoTemplate;
import cn.dev33.satoken.sso.util.SaSsoConsts;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.util.SaResult;
/**
@ -60,10 +60,12 @@ public class SaSsoMessageCheckTicketHandle implements SaSsoMessageHandle {
// 1获取参数
SaRequest req = SaHolder.getRequest();
SaSsoServerConfig ssoServerConfig = ssoServerTemplate.getServerConfig();
StpLogic stpLogic = ssoServerTemplate.getStpLogic();
String client = req.getParam(paramName.client);
String ticket = req.getParamNotNull(paramName.ticket);
String sloCallback = req.getParam(paramName.ssoLogoutCall);
// 2校验提供的client是否为非法字符
if(SaSsoConsts.CLIENT_WILDCARD.equals(client)) {
return SaResult.error("无效 client 标识:" + client);
@ -77,17 +79,21 @@ public class SaSsoMessageCheckTicketHandle implements SaSsoMessageHandle {
// }
// 4校验ticket获取 loginId
Object loginId = ssoServerTemplate.checkTicket(ticket, client);
if(SaFoxUtil.isEmpty(loginId)) {
return SaResult.error("无效ticket" + ticket);
}
TicketModel ticketModel = ssoServerTemplate.checkTicketParamAndDelete(ticket, client);
Object loginId = ticketModel.getLoginId();
// 5注册此客户端的单点注销回调URL
ssoServerTemplate.registerSloCallbackUrl(loginId, client, sloCallback);
// 6 client 端响应结果
long remainSessionTimeout = ssoServerTemplate.getStpLogic().getSessionTimeoutByLoginId(loginId);
SaResult result = SaResult.data(loginId).set(paramName.remainSessionTimeout, remainSessionTimeout);
SaResult result = SaResult.ok();
result.setData(loginId); // 兼容历史版本
result.set(paramName.loginId, loginId);
result.set(paramName.tokenValue, ticketModel.getTokenValue());
result.set(paramName.deviceId, stpLogic.getLoginDeviceIdByToken(ticketModel.getTokenValue()));
result.set(paramName.remainTokenTimeout, stpLogic.getTokenTimeout(ticketModel.getTokenValue()));
result.set(paramName.remainSessionTimeout, stpLogic.getSessionTimeoutByLoginId(loginId));
result = ssoServerConfig.checkTicketAppendData.apply(loginId, result);
return result;
}

View File

@ -24,6 +24,7 @@ import cn.dev33.satoken.sso.name.ParamName;
import cn.dev33.satoken.sso.template.SaSsoServerTemplate;
import cn.dev33.satoken.sso.template.SaSsoTemplate;
import cn.dev33.satoken.sso.util.SaSsoConsts;
import cn.dev33.satoken.stp.parameter.SaLogoutParameter;
import cn.dev33.satoken.util.SaResult;
/**
@ -61,9 +62,12 @@ public class SaSsoMessageSignoutHandle implements SaSsoMessageHandle {
// 获取参数
SaRequest req = SaHolder.getRequest();
String loginId = req.getParam(paramName.loginId);
String deviceId = req.getParam(paramName.deviceId);
// step.2 单点注销
ssoServerTemplate.ssoLogout(loginId);
SaLogoutParameter logoutParameter = ssoServerTemplate.getStpLogic().createSaLogoutParameter()
.setDeviceId(deviceId);
ssoServerTemplate.ssoLogout(loginId, logoutParameter);
// 响应
return SaResult.ok();

View File

@ -32,21 +32,31 @@ public class SaCheckTicketResult implements Serializable {
/** 账号id */
public Object loginId;
/** 在 sso-server 端的 token 值 */
public String tokenValue;
/** 登录设备 id */
public String deviceId;
/** 此账号 token 剩余有效期 */
public Long remainTokenTimeout;
/** 此账号会话剩余有效期 */
public long remainSessionTimeout;
public Long remainSessionTimeout;
/** 从 sso-server 返回的所有参数 */
public SaResult result;
public SaCheckTicketResult(Object loginId, long remainSessionTimeout, SaResult result) {
this.loginId = loginId;
this.remainSessionTimeout = remainSessionTimeout;
this.result = result;
public SaCheckTicketResult() {
}
@Override
public String toString() {
return "CheckTicketResult{" +
return "SaCheckTicketResult{" +
"loginId=" + loginId +
", tokenValue='" + tokenValue + '\'' +
", deviceId='" + deviceId + '\'' +
", remainTokenTimeout=" + remainTokenTimeout +
", remainSessionTimeout=" + remainSessionTimeout +
", result=" + result +
'}';

View File

@ -37,16 +37,21 @@ public class TicketModel implements Serializable {
*/
public String client;
/**
* 设备 id
*/
public String deviceId;
// /**
// * 设备 id
// */
// public String deviceId;
/**
* 对应 loginId
*/
public Object loginId;
/**
* 会话 token
*/
public String tokenValue;
/**
* 创建时间13位时间戳
*/
@ -64,14 +69,14 @@ public class TicketModel implements Serializable {
* @param ticket 授权码
* @param client 应用id
* @param loginId 对应的账号id
* @param deviceId 重定向地址
* @param tokenValue 会话 token
*/
public TicketModel(String ticket, String client, String deviceId, Object loginId) {
public TicketModel(String ticket, String client, Object loginId, String tokenValue) {
this();
this.ticket = ticket;
this.client = client;
this.deviceId = deviceId;
this.loginId = loginId;
this.tokenValue = tokenValue;
}
@ -117,26 +122,6 @@ public class TicketModel implements Serializable {
return this;
}
/**
* 获取 设备 id
*
* @return /
*/
public String getDeviceId() {
return this.deviceId;
}
/**
* 设置 设备 id
*
* @param deviceId /
* @return 对象自身
*/
public TicketModel setDeviceId(String deviceId) {
this.deviceId = deviceId;
return this;
}
/**
* 获取 对应 loginId
*
@ -157,6 +142,26 @@ public class TicketModel implements Serializable {
return this;
}
/**
* 获取 会话 token
*
* @return tokenValue 会话 token
*/
public String getTokenValue() {
return this.tokenValue;
}
/**
* 设置 会话 token
*
* @param tokenValue 会话 token
* @return 对象自身
*/
public TicketModel setTokenValue(String tokenValue) {
this.tokenValue = tokenValue;
return this;
}
/**
* 获取 创建时间13位时间戳
*
@ -182,8 +187,8 @@ public class TicketModel implements Serializable {
return "TicketModel{" +
"ticket='" + ticket + '\'' +
", client='" + client + '\'' +
", deviceId='" + deviceId + '\'' +
", loginId=" + loginId +
", tokenValue=" + tokenValue +
", createTime=" + createTime +
'}';
}

View File

@ -41,6 +41,12 @@ public class ParamName {
/** client参数名称 */
public String client = "client";
/** tokenValue 参数 */
public String tokenValue = "tokenValue";
/** deviceId 参数名称 */
public String deviceId = "deviceId";
/** secretkey参数名称 */
public String secretkey = "secretkey";
@ -60,4 +66,10 @@ public class ParamName {
/** Session 剩余有效期 参数名称 */
public String remainSessionTimeout = "remainSessionTimeout";
/** token 剩余有效期 参数名称 */
public String remainTokenTimeout = "remainTokenTimeout";
/** singleDeviceIdLogout 参数 */
public String singleDeviceIdLogout = "singleDeviceIdLogout";
}

View File

@ -24,11 +24,14 @@ import cn.dev33.satoken.sso.error.SaSsoErrorCode;
import cn.dev33.satoken.sso.exception.SaSsoException;
import cn.dev33.satoken.sso.message.SaSsoMessage;
import cn.dev33.satoken.sso.model.SaCheckTicketResult;
import cn.dev33.satoken.sso.model.TicketModel;
import cn.dev33.satoken.sso.name.ApiName;
import cn.dev33.satoken.sso.name.ParamName;
import cn.dev33.satoken.sso.template.SaSsoClientTemplate;
import cn.dev33.satoken.sso.util.SaSsoConsts;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.stp.parameter.SaLogoutParameter;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
@ -139,7 +142,10 @@ public class SaSsoClientProcessor {
}
// 3登录并重定向至back地址
stpLogic.login(ctr.loginId, ctr.remainSessionTimeout);
stpLogic.login(ctr.loginId, new SaLoginParameter()
.setTimeout(ctr.remainTokenTimeout)
.setDeviceId(ctr.deviceId)
);
return res.redirect(back);
}
}
@ -211,6 +217,7 @@ public class SaSsoClientProcessor {
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
StpLogic stpLogic = ssoClientTemplate.getStpLogic();
boolean singleDeviceIdLogout = req.isParam(ssoClientTemplate.paramName.singleDeviceIdLogout, "true");
// 如果未登录则无需注销
if( ! stpLogic.isLogin()) {
@ -218,7 +225,11 @@ public class SaSsoClientProcessor {
}
// 调用 sso-server 认证中心单点注销API
SaSsoMessage message = ssoClientTemplate.buildSloMessage(stpLogic.getLoginId());
SaLogoutParameter logoutParameter = stpLogic.createSaLogoutParameter();
if(singleDeviceIdLogout) {
logoutParameter.setDeviceId(stpLogic.getLoginDeviceId());
}
SaSsoMessage message = ssoClientTemplate.buildSloMessage(stpLogic.getLoginId(), logoutParameter);
SaResult result = ssoClientTemplate.pushMessageAsSaResult(message);
// 校验响应状态码
@ -248,18 +259,20 @@ public class SaSsoClientProcessor {
// 获取参数
String loginId = req.getParamNotNull(paramName.loginId);
String deviceId = req.getParam(paramName.deviceId);
// String client = req.getParam(paramName.client);
// String autoLogout = req.getParam(paramName.autoLogout);
// 校验参数签名
if(ssoConfig.getIsCheckSign()) {
ssoClientTemplate.getSignTemplate(ssoConfig.getClient()).checkRequest(req, paramName.loginId, paramName.client, paramName.autoLogout);
ssoClientTemplate.getSignTemplate(ssoConfig.getClient()).checkRequest(req);
} else {
SaSsoManager.printNoCheckSignWarningByRuntime();
}
// 注销当前应用端会话
stpLogic.logout(loginId);
SaLogoutParameter logoutParameter = ssoClientTemplate.getStpLogic().createSaLogoutParameter();
stpLogic.logout(loginId, logoutParameter.setDeviceId(deviceId));
// 响应
return SaResult.ok("单点注销回调成功");
@ -269,9 +282,10 @@ public class SaSsoClientProcessor {
/**
* 封装校验ticket取出loginId如果 ticket 无效则抛出异常 适用于模式二或模式三
*
* @param ticket ticket码
* @param currUri 当前路由的uri用于计算单点注销回调地址 如果是使用模式二可以填写null
* @return loginId
* @return SaCheckTicketResult
*/
public SaCheckTicketResult checkTicket(String ticket, String currUri) {
SaSsoClientConfig cfg = ssoClientTemplate.getClientConfig();
@ -304,18 +318,16 @@ public class SaSsoClientProcessor {
// 校验
if(result.getCode() != null && result.getCode() == SaResult.CODE_SUCCESS) {
// 取出 loginId
Object loginId = result.getData();
if(SaFoxUtil.isEmpty(loginId)) {
throw new SaSsoException("无效ticket" + ticket).setCode(SaSsoErrorCode.CODE_30004);
}
// 取出 Session 剩余有效期
Long remainSessionTimeout = result.get(paramName.remainSessionTimeout, Long.class);
if(remainSessionTimeout == null) {
remainSessionTimeout = ssoClientTemplate.getStpLogic().getConfigOrGlobal().getTimeout();
}
// 构建返回
return new SaCheckTicketResult(loginId, remainSessionTimeout, result);
SaCheckTicketResult ctr = new SaCheckTicketResult();
ctr.loginId = result.get(paramName.loginId);
ctr.tokenValue = result.get(paramName.tokenValue, String.class);
ctr.deviceId = result.get(paramName.deviceId, String.class);
ctr.remainTokenTimeout = result.get(paramName.remainTokenTimeout, Long.class);
ctr.remainSessionTimeout = result.get(paramName.remainSessionTimeout, Long.class);
ctr.result = result;
return ctr;
} else {
// sso-server 回应的消息作为异常抛出
throw new SaSsoException(result.getMsg()).setCode(SaSsoErrorCode.CODE_30005);
@ -328,15 +340,18 @@ public class SaSsoClientProcessor {
// 可能会导致调用失败注意是可能而非一定
// 解决方案为在当前 sso-client 端也按照 sso-server 端的格式重写 SaSsoClientProcessor 里的方法
// 取出 loginId
Object loginId = SaSsoServerProcessor.instance.ssoServerTemplate.checkTicket(ticket, cfg.getClient());
if(SaFoxUtil.isEmpty(loginId)) {
throw new SaSsoException("无效ticket" + ticket).setCode(SaSsoErrorCode.CODE_30004);
}
// 取出 Session 剩余有效期
long remainSessionTimeout = ssoClientTemplate.getStpLogic().getSessionTimeoutByLoginId(loginId);
// 构建返回
return new SaCheckTicketResult(loginId, remainSessionTimeout, null);
StpLogic stpLogic = ssoClientTemplate.getStpLogic();
TicketModel ticketModel = SaSsoServerProcessor.instance.ssoServerTemplate.checkTicketParamAndDelete(ticket, cfg.getClient());
SaCheckTicketResult ctr = new SaCheckTicketResult();
ctr.loginId = ticketModel.getLoginId();
ctr.tokenValue = ticketModel.getTokenValue();
ctr.deviceId = stpLogic.getLoginDeviceIdByToken(ticketModel.getTokenValue());
ctr.remainTokenTimeout = stpLogic.getTokenTimeout(ticketModel.getTokenValue());
ctr.remainSessionTimeout = stpLogic.getSessionTimeoutByLoginId(ticketModel.getLoginId());
ctr.result = null;
return ctr;
}
}

View File

@ -28,6 +28,7 @@ import cn.dev33.satoken.sso.name.ParamName;
import cn.dev33.satoken.sso.template.SaSsoServerTemplate;
import cn.dev33.satoken.sso.util.SaSsoConsts;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.parameter.SaLogoutParameter;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
@ -141,7 +142,7 @@ public class SaSsoServerProcessor {
}
// 构建并跳转
String redirectUrl = ssoServerTemplate.buildRedirectUrl(stpLogic.getLoginId(), client, redirect);
String redirectUrl = ssoServerTemplate.buildRedirectUrl(client, redirect, stpLogic.getLoginId(), stpLogic.getTokenValue());
// 构建成功说明 redirect 地址合法此时需要更新一下该账号的Session有效期
if(cfg.getAutoRenewTimeout()) {
stpLogic.renewTimeout(stpLogic.getConfigOrGlobal().getTimeout());
@ -174,10 +175,15 @@ public class SaSsoServerProcessor {
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
Object loginId = ssoServerTemplate.getStpLogic().getLoginIdDefaultNull();
boolean singleDeviceIdLogout = req.isParam(ssoServerTemplate.paramName.singleDeviceIdLogout, "true");
// 单点注销
if(SaFoxUtil.isNotEmpty(loginId)) {
ssoServerTemplate.ssoLogout(loginId);
SaLogoutParameter logoutParameter = ssoServerTemplate.getStpLogic().createSaLogoutParameter();
if(singleDeviceIdLogout) {
logoutParameter.setDeviceId(ssoServerTemplate.getStpLogic().getLoginDeviceId());
}
ssoServerTemplate.ssoLogout(loginId, logoutParameter);
}
// 完成

View File

@ -25,6 +25,7 @@ import cn.dev33.satoken.sso.exception.SaSsoException;
import cn.dev33.satoken.sso.message.SaSsoMessage;
import cn.dev33.satoken.sso.message.handle.client.SaSsoMessageLogoutCallHandle;
import cn.dev33.satoken.sso.util.SaSsoConsts;
import cn.dev33.satoken.stp.parameter.SaLogoutParameter;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
@ -160,15 +161,18 @@ public class SaSsoClientTemplate extends SaSsoTemplate {
/**
* 构建消息单点注销
*
* @param loginId 要注销的账号 id
* @param logoutParameter 单点注销
* @return 单点注销URL
*/
public SaSsoMessage buildSloMessage(Object loginId) {
public SaSsoMessage buildSloMessage(Object loginId, SaLogoutParameter logoutParameter) {
SaSsoClientConfig ssoConfig = getClientConfig();
SaSsoMessage message = new SaSsoMessage();
message.setType(SaSsoConsts.MESSAGE_SIGNOUT);
message.set(paramName.client, ssoConfig.getClient());
message.set(paramName.loginId, loginId);
message.set(paramName.deviceId, logoutParameter.getDeviceId());
return message;
}

View File

@ -30,6 +30,7 @@ import cn.dev33.satoken.sso.message.handle.server.SaSsoMessageSignoutHandle;
import cn.dev33.satoken.sso.model.SaSsoClientInfo;
import cn.dev33.satoken.sso.model.TicketModel;
import cn.dev33.satoken.sso.util.SaSsoConsts;
import cn.dev33.satoken.stp.parameter.SaLogoutParameter;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
@ -67,7 +68,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
* @param ticket ticket码
* @param loginId 账号id
*/
public void saveTicketIndex(String client, String ticket, Object loginId) {
public void saveTicketIndex(String client, Object loginId, String ticket) {
long ticketTimeout = getServerConfig().getTicketTimeout();
SaManager.getSaTokenDao().set(splicingTicketIndexKey(client, loginId), String.valueOf(ticket), ticketTimeout);
}
@ -186,56 +187,65 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
//
/**
* 根据 账号id 创建一个 Ticket
* @param loginId 账号id
* 根据参数创建一个 ticket
*
* @param client 客户端标识
* @param loginId 账号 id
* @param tokenValue 会话 Token
* @return Ticket码
*/
public String createTicket(Object loginId, String client) {
public String createTicket(String client, Object loginId, String tokenValue) {
// 创建 Ticket
String ticket = randomTicket(loginId);
TicketModel ticketModel = new TicketModel();
ticketModel.setTicket(ticket);
ticketModel.setClient(client);
ticketModel.setLoginId(loginId);
// TODO ticketModel.setDeviceId();
ticketModel.setTokenValue(tokenValue);
// 保存 Ticket
saveTicket(ticketModel);
saveTicketIndex(client, ticket, loginId);
saveTicketIndex(client, loginId, ticket);
// 返回 Ticket
return ticket;
}
/**
* 校验 Ticket 获取账号id如果此ticket是有效的则立即删除
* 校验 Ticket无效 ticket 会抛出异常
*
* @param ticket Ticket码
* @return 账号id
* @return /
*/
public Object checkTicket(String ticket) {
return checkTicket(ticket, SaSsoConsts.CLIENT_WILDCARD);
public TicketModel checkTicket(String ticket) {
TicketModel ticketModel = getTicket(ticket);
if(ticketModel == null) {
throw new SaSsoException("无效 ticket : " + ticket).setCode(SaSsoErrorCode.CODE_30004);
}
return ticketModel;
}
/**
* 校验 Ticket 获取账号id如果此ticket是有效的则立即删除
* 校验 Ticket 无效 ticket 会抛出异常如果此ticket是有效的则立即删除
* @param ticket Ticket码
* @param client client 标识
* @return 账号id
*/
public Object checkTicket(String ticket, String client) {
// 读取 loginId
TicketModel ticketModel = getTicket(ticket);
if(ticketModel == null) {
return null;
}
public TicketModel checkTicketParamAndDelete(String ticket) {
return checkTicketParamAndDelete(ticket, SaSsoConsts.CLIENT_WILDCARD);
}
Object loginId = ticketModel.getLoginId();
String ticketClient = ticketModel.getClient();
// 解析出这个 ticket 关联的 Client
/**
* 校验 Ticket无效 ticket 会抛出异常如果此ticket是有效的则立即删除
*
* @param ticket Ticket码
* @param client client 标识
* @return /
*/
public TicketModel checkTicketParamAndDelete(String ticket, String client) {
TicketModel ticketModel = checkTicket(ticket);
// 校验 client 参数是否正确创建 ticket client 和当前校验 ticket client 是否一致
String ticketClient = ticketModel.getClient();
if(SaSsoConsts.CLIENT_WILDCARD.equals(client)) {
// 如果提供的是通配符直接越过 client 校验
} else if (SaFoxUtil.isEmpty(client) && SaFoxUtil.isEmpty(ticketClient)) {
@ -249,10 +259,10 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
// 删除 ticket 信息使其只有一次性有效
deleteTicket(ticket);
deleteTicketIndex(ticket, loginId);
deleteTicketIndex(client, ticketModel.getLoginId());
//
return loginId;
return ticketModel;
}
/**
@ -346,13 +356,15 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
// ------------------- 重定向 URL 构建与校验 -------------------
/**
* 构建URLServer端向Client下放ticket的地址
* @param loginId 账号id
* 构建 URLsso-server 端向 sso-client 下放 ticket 的地址
*
* @param client 客户端标识
* @param redirect Client端提供的重定向地址
* @return see note
* @param redirect sso-client 端的重定向地址
* @param loginId 账号 id
* @param tokenValue 会话 token
* @return /
*/
public String buildRedirectUrl(Object loginId, String client, String redirect) {
public String buildRedirectUrl(String client, String redirect, Object loginId, String tokenValue) {
// 校验 重定向地址 是否合法
checkRedirectUrl(client, redirect);
@ -361,7 +373,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
deleteTicket(getTicketValue(client, loginId));
// 创建 新Ticket
String ticket = createTicket(loginId, client);
String ticket = createTicket(client, loginId, tokenValue);
// 构建 授权重定向地址 Server端 根据此地址向 Client端 下放Ticket
return SaFoxUtil.joinParam(encodeBackParam(redirect), paramName.ticket, ticket);
@ -521,7 +533,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
for (;;) {
if(scmList.size() > maxRegClient) {
SaSsoClientInfo removeScm = scmList.remove(0);
notifyClientLogout(loginId, removeScm, true);
notifyClientLogout(loginId, null, removeScm, true);
} else {
break;
}
@ -557,12 +569,24 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
/**
* 指定账号单点注销
*
* @param loginId 指定账号
*/
public void ssoLogout(Object loginId) {
ssoLogout(loginId, getStpLogic().createSaLogoutParameter());
}
/**
* 指定账号单点注销
*
* @param loginId 指定账号
* @param logoutParameter 注销参数
*/
public void ssoLogout(Object loginId, SaLogoutParameter logoutParameter) {
// 1消息推送单点注销
pushToAllClientByLogoutCall(loginId);
// TODO 需要把对应的 SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_ 记录也删掉
pushToAllClientByLogoutCall(loginId, logoutParameter);
// 2SaSession 挂载的 Client 端注销会话
SaSession session = getStpLogic().getSessionByLoginId(loginId, false);
@ -571,20 +595,21 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
}
List<SaSsoClientInfo> scmList = session.get(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, ArrayList::new);
scmList.forEach(scm -> {
notifyClientLogout(loginId, scm, false);
notifyClientLogout(loginId, logoutParameter.getDeviceId(), scm, false);
});
// 3Server 端本身注销
getStpLogic().logout(loginId);
getStpLogic().logout(loginId, logoutParameter);
}
/**
* 通知指定账号的指定客户端注销
* @param loginId 指定账号
* @param deviceId 指定设备 id
* @param scm 客户端信息对象
* @param autoLogout 是否为超过 maxRegClient 的自动注销
*/
public void notifyClientLogout(Object loginId, SaSsoClientInfo scm, boolean autoLogout) {
public void notifyClientLogout(Object loginId, String deviceId, SaSsoClientInfo scm, boolean autoLogout) {
// 如果给个null值不进行任何操作
if(scm == null || scm.mode != SaSsoConsts.SSO_MODE_3) {
@ -601,6 +626,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
Map<String, Object> paramsMap = new TreeMap<>();
paramsMap.put(paramName.client, scm.getClient());
paramsMap.put(paramName.loginId, loginId);
paramsMap.put(paramName.deviceId, deviceId);
paramsMap.put(paramName.autoLogout, autoLogout);
String signParamsStr = getSignTemplate(scm.getClient()).addSignParamsAndJoin(paramsMap);
@ -680,14 +706,16 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
* 向所有 Client 推送消息单点注销回调
*
* @param loginId /
* @param logoutParameter 注销参数
*/
public void pushToAllClientByLogoutCall(Object loginId) {
public void pushToAllClientByLogoutCall(Object loginId, SaLogoutParameter logoutParameter) {
List<SaSsoClientModel> npClients = getNeedPushClients();
for (SaSsoClientModel client : npClients) {
if(client.getIsSlo()) {
SaSsoMessage message = new SaSsoMessage();
message.setType(SaSsoConsts.MESSAGE_LOGOUT_CALL);
message.set(paramName.loginId, loginId);
message.set(paramName.deviceId, logoutParameter.getDeviceId());
pushMessage(client, message);
}
}

View File

@ -15,6 +15,7 @@
*/
package cn.dev33.satoken.sso.template;
import cn.dev33.satoken.sso.model.TicketModel;
import cn.dev33.satoken.sso.processor.SaSsoClientProcessor;
import cn.dev33.satoken.sso.processor.SaSsoServerProcessor;
@ -29,15 +30,17 @@ import java.util.Map;
public class SaSsoUtil {
// ---------------------- Ticket 操作 ----------------------
/**
* 根据 账号id 创建一个 Ticket码
* @param loginId 账号id
* @param client 客户端标识
* @return Ticket码
* 根据参数创建一个 ticket
*
* @param client 客户端标识
* @param loginId 账号 id
* @param deviceId 设备 id
* @return Ticket码
*/
public static String createTicket(Object loginId, String client) {
return SaSsoServerProcessor.instance.ssoServerTemplate.createTicket(loginId, client);
public static String createTicket(String client, Object loginId, String deviceId) {
return SaSsoServerProcessor.instance.ssoServerTemplate.createTicket(client, loginId, deviceId);
}
/**
@ -78,22 +81,22 @@ public class SaSsoUtil {
}
/**
* 校验 Ticket 获取账号id如果此ticket是有效的则立即删除
* 校验 Ticket无效 ticket 会抛出异常如果此ticket是有效的则立即删除
* @param ticket Ticket码
* @return 账号id
*/
public static Object checkTicket(String ticket) {
return SaSsoServerProcessor.instance.ssoServerTemplate.checkTicket(ticket);
public static TicketModel checkTicket(String ticket) {
return SaSsoServerProcessor.instance.ssoServerTemplate.checkTicketParamAndDelete(ticket);
}
/**
* 校验ticket码获取账号id如果此ticket是有效的则立即删除
* 校验ticket码无效 ticket 会抛出异常如果此ticket是有效的则立即删除
* @param ticket Ticket码
* @param client client 标识
* @return 账号id
*/
public static Object checkTicket(String ticket, String client) {
return SaSsoServerProcessor.instance.ssoServerTemplate.checkTicket(ticket, client);
public static TicketModel checkTicket(String ticket, String client) {
return SaSsoServerProcessor.instance.ssoServerTemplate.checkTicketParamAndDelete(ticket, client);
}
/**
@ -160,14 +163,16 @@ public class SaSsoUtil {
}
/**
* 构建URLServer端向Client下放ticket的地址
* @param loginId 账号id
* @param client 客户端标识
* @param redirect Client端提供的重定向地址
* @return see note
* 构建 URLsso-server 端向 sso-client 下放 ticket 的地址
*
* @param client 客户端标识
* @param redirect sso-client 端的重定向地址
* @param loginId 账号 id
* @param tokenValue 会话 token
* @return /
*/
public static String buildRedirectUrl(Object loginId, String client, String redirect) {
return SaSsoServerProcessor.instance.ssoServerTemplate.buildRedirectUrl(loginId, client, redirect);
public static String buildRedirectUrl(String client, String redirect, Object loginId, String tokenValue) {
return SaSsoServerProcessor.instance.ssoServerTemplate.buildRedirectUrl(client, redirect, loginId, tokenValue);
}
/**