mirror of
https://gitee.com/dromara/sa-token.git
synced 2025-06-28 13:16:24 +08:00
feat(sso): 单点注销支持单设备注销
This commit is contained in:
parent
c2dea166e4
commit
5aac119beb
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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("登录失败!");
|
||||
|
@ -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("单点注销回调成功");
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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 +
|
||||
'}';
|
||||
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
|
@ -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";
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
// 完成
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 构建与校验 -------------------
|
||||
|
||||
/**
|
||||
* 构建URL:Server端向Client下放ticket的地址
|
||||
* @param loginId 账号id
|
||||
* 构建 URL:sso-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);
|
||||
|
||||
// 2、SaSession 挂载的 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);
|
||||
});
|
||||
|
||||
// 3、Server 端本身注销
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL:Server端向Client下放ticket的地址
|
||||
* @param loginId 账号id
|
||||
* @param client 客户端标识
|
||||
* @param redirect Client端提供的重定向地址
|
||||
* @return see note
|
||||
* 构建 URL:sso-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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user