mirror of
https://gitee.com/dromara/sa-token.git
synced 2025-09-19 10:08:07 +08:00
将SSO模式三的接口调用改为签名式校验
This commit is contained in:
@@ -15,6 +15,8 @@ import cn.dev33.satoken.json.SaJsonTemplate;
|
||||
import cn.dev33.satoken.json.SaJsonTemplateDefaultImpl;
|
||||
import cn.dev33.satoken.listener.SaTokenListener;
|
||||
import cn.dev33.satoken.listener.SaTokenListenerDefaultImpl;
|
||||
import cn.dev33.satoken.sign.SaSignTemplate;
|
||||
import cn.dev33.satoken.sign.SaSignTemplateDefaultImpl;
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
import cn.dev33.satoken.stp.StpInterfaceDefaultImpl;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
@@ -191,6 +193,24 @@ public class SaManager {
|
||||
}
|
||||
return saJsonTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数签名 Bean
|
||||
*/
|
||||
private volatile static SaSignTemplate saSignTemplate;
|
||||
public static void setSaSignTemplate(SaSignTemplate saSignTemplate) {
|
||||
SaManager.saSignTemplate = saSignTemplate;
|
||||
}
|
||||
public static SaSignTemplate getSaSignTemplate() {
|
||||
if (saSignTemplate == null) {
|
||||
synchronized (SaManager.class) {
|
||||
if (saSignTemplate == null) {
|
||||
setSaSignTemplate(new SaSignTemplateDefaultImpl());
|
||||
}
|
||||
}
|
||||
}
|
||||
return saSignTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* StpLogic集合, 记录框架所有成功初始化的StpLogic
|
||||
|
@@ -0,0 +1,59 @@
|
||||
package cn.dev33.satoken.sign;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import cn.dev33.satoken.secure.SaSecureUtil;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
* 参数签名算法
|
||||
*
|
||||
* @author kong
|
||||
* @date: 2022-4-27
|
||||
*/
|
||||
public interface SaSignTemplate {
|
||||
|
||||
/**
|
||||
* 将所有参数连接成一个字符串,形如: k1=v1&k2=v2&k3=v3
|
||||
* @param paramsMap 参数列表
|
||||
* @return 字符串
|
||||
*/
|
||||
public default String joinParams(Map<String, Object> paramsMap) {
|
||||
// 保证字段按照字典顺序排列
|
||||
if(paramsMap instanceof TreeMap == false) {
|
||||
paramsMap = new TreeMap<>(paramsMap);
|
||||
}
|
||||
|
||||
// 按照 k1=v1&k2=v2&k3=v3 排列
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String key : paramsMap.keySet()) {
|
||||
Object value = paramsMap.get(key);
|
||||
if(SaFoxUtil.isEmpty(value) == false) {
|
||||
sb.append(key).append("=").append(value).append("&");
|
||||
}
|
||||
}
|
||||
|
||||
// 删除最后一位 &
|
||||
if(sb.length() > 0) {
|
||||
sb.deleteCharAt(sb.length() - 1);
|
||||
}
|
||||
|
||||
// .
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建签名:md5(paramsStr + keyStr)
|
||||
* @param paramsMap 参数列表
|
||||
* @param key 秘钥
|
||||
* @return
|
||||
*/
|
||||
public default String createSign(Map<String, Object> paramsMap, String key) {
|
||||
String paramsStr = joinParams(paramsMap);
|
||||
String fullStr = paramsStr + "&key=" + key;
|
||||
return SaSecureUtil.md5(fullStr);
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
package cn.dev33.satoken.sign;
|
||||
|
||||
/**
|
||||
* 参数签名算法 [默认实现类]
|
||||
*
|
||||
* @author kong
|
||||
* @date: 2022-4-27
|
||||
*/
|
||||
public class SaSignTemplateDefaultImpl implements SaSignTemplate {
|
||||
|
||||
}
|
@@ -43,6 +43,8 @@
|
||||
if(res.code == 200) {
|
||||
localStorage.setItem('satoken', res.data);
|
||||
location.href = decodeURIComponent(back);
|
||||
} else {
|
||||
alert(res.msg);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -53,8 +53,15 @@ public class SsoServerController {
|
||||
|
||||
// 配置 Http 请求处理器 (在模式三的单点注销功能下用到,如不需要可以注释掉)
|
||||
sso.setSendHttp(url -> {
|
||||
return OkHttps.sync(url).get().getBody().toString();
|
||||
try {
|
||||
// 发起 http 请求
|
||||
System.out.println("发起请求:" + url);
|
||||
return OkHttps.sync(url).get().getBody().toString();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -46,6 +46,7 @@ public class SsoClientController {
|
||||
private void configSso(SaSsoConfig sso) {
|
||||
// 配置Http请求处理器
|
||||
sso.setSendHttp(url -> {
|
||||
System.out.println("发起请求:" + url);
|
||||
return OkHttps.sync(url).get().getBody().toString();
|
||||
});
|
||||
}
|
||||
|
@@ -83,22 +83,33 @@ http://{host}:{port}/sso/checkTicket
|
||||
http://{host}:{port}/sso/logout
|
||||
```
|
||||
|
||||
接受参数:
|
||||
|
||||
| 参数 | 是否必填 | 说明 |
|
||||
| :-------- | :-------- | :-------- |
|
||||
| loginId | 否 | 要注销的账号id |
|
||||
| secretkey | 否 | 接口通信秘钥 |
|
||||
| back | 否 | 注销成功后的重定向地址 |
|
||||
|
||||
|
||||
此接口有两种调用方式
|
||||
|
||||
##### 方式一:在 Client 的前端页面引导用户直接跳转,并带有 back 参数
|
||||
例如:`http://{host}:{port}/sso/logout?back=xxx`,代表用户注销成功后返回back地址
|
||||
##### 4.1、方式一:在 Client 的前端页面引导用户直接跳转,并带有 back 参数
|
||||
例如:
|
||||
|
||||
##### 方式二:在 Client 的后端通过 http 工具来调用
|
||||
例如:`http://{host}:{port}/sso/logout?loginId={value}&secretkey={value}`,代表注销 账号=loginId 的账号,返回json数据结果,形如:
|
||||
``` url
|
||||
http://{host}:{port}/sso/logout?back=xxx
|
||||
```
|
||||
用户注销成功后将返回 back 地址
|
||||
|
||||
##### 4.2、方式二:在 Client 的后端通过 http 工具来调用
|
||||
|
||||
接受参数:
|
||||
|
||||
| 参数 | 是否必填 | 说明 |
|
||||
| :-------- | :-------- | :-------- |
|
||||
| loginId | 是 | 要注销的账号 id |
|
||||
| timestamp | 是 | 当前时间戳,13位 |
|
||||
| nonce | 是 | 随机字符串 |
|
||||
| sign | 是 | 签名,生成算法:`md5( loginId={value}&nonce={value}×tamp={value}&key={secretkey秘钥} )` |
|
||||
|
||||
例如:
|
||||
``` url
|
||||
http://{host}:{port}/sso/logout?loginId={value}×tamp={value}&nonce={value}&sign={value}
|
||||
```
|
||||
|
||||
将返回 json 数据结果,形如:
|
||||
|
||||
``` js
|
||||
{
|
||||
@@ -113,7 +124,7 @@ http://{host}:{port}/sso/logout
|
||||
``` js
|
||||
{
|
||||
"code": 500, // 200表示请求成功,非200标识请求失败
|
||||
"msg": "无效秘钥:xxx", // 失败原因
|
||||
"msg": "签名无效:xxx", // 失败原因
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
@@ -5,9 +5,11 @@
|
||||
### 问:在模式一与模式二中,Client端 必须通过 Alone-Redis 插件来访问Redis吗?
|
||||
答:不必须,只是推荐,权限缓存与业务缓存分离后会减少 `SSO-Redis` 的访问压力,且可以避免多个 `Client端` 的缓存读写冲突
|
||||
|
||||
|
||||
### 问:将旧有系统改造为单点登录时,应该注意哪些?
|
||||
答:建议不要把其中一个系统改造为SSO服务端,而是新起一个项目作为Server端,所有旧有项目全部作为Client端与此对接
|
||||
|
||||
|
||||
### 问:SSO模式二,第一个域名登录成功之后其他两个不会自动登录?
|
||||
答:系统1登录成功之后,系统二与系统三需要点击登录按钮,才会登录成功
|
||||
|
||||
@@ -15,13 +17,23 @@
|
||||
> 第二个系统,需要:点击 [登录] 按钮 -> 登录成功 <br>
|
||||
> 第三个系统,需要:点击 [登录] 按钮 -> 登录成功 (免去重复跳转登录页输入账号密码的步骤)
|
||||
|
||||
|
||||
### 追问:那我是否可以设计成不需要点登录按钮的,只要访问页面,它就能登录成功
|
||||
可以:加个过滤器检测到未登录 自动跳转就行了,详细可以参照章节:[[何时引导用户去登录]](/sso/sso-custom-login) 给出的建议进行设计
|
||||
|
||||
|
||||
### 问:我参照文档的SSO模式二搭建,一直提示:Ticket无效,请问怎么回事?
|
||||
根据群友的反馈,出现此异常概率最大的原因是因为 `Client` 与 `Server` 没有连接同一个Redis,SSO模式二中两者必须连接同一个 Redis 才可以登录成功,
|
||||
如果您排查之后不是此原因,可以加入QQ群或者在issues反馈一下
|
||||
|
||||
|
||||
### 模式一或者模式二报错:Could not write JSON: No serializer found for class com.pj.sso.SysUser and no properties discovered to create BeanSerializer
|
||||
|
||||
一般是因为在 sso-server 端往 session 上写入了某个实体类(比如 User),而在 sso-client 端没有这个实体类,导致反序列化失败。
|
||||
|
||||
解决方案:在 sso-client 也新建上这个类,而且包名需要与 sso-server 端的一致(直接从 sso-server 把实体类复制过来就好了)
|
||||
|
||||
|
||||
### 还有其它问题?
|
||||
可以加群反馈一下,比较典型的问题我们解决之后都会提交到此页面方便大家快速排查
|
||||
|
||||
|
@@ -93,14 +93,23 @@ public class SsoServerController {
|
||||
|
||||
// 配置 Http 请求处理器 (在模式三的单点注销功能下用到,如不需要可以注释掉)
|
||||
cfg.sso.setSendHttp(url -> {
|
||||
return OkHttps.sync(url).get().getBody().toString();
|
||||
try {
|
||||
// 发起 http 请求
|
||||
System.out.println("发起请求:" + url);
|
||||
return OkHttps.sync(url).get().getBody().toString();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
注:在`setDoLoginHandle`函数里如果要获取name, pwd以外的参数,可通过`SaHolder.getRequest().getParam("xxx")`来获取
|
||||
注意:
|
||||
- 在`setDoLoginHandle`函数里如果要获取name, pwd以外的参数,可通过`SaHolder.getRequest().getParam("xxx")`来获取
|
||||
- 在 `setSendHttp` 函数中,使用 `try-catch` 是为了提高整个注销流程的容错性,避免在一些极端情况下注销失败(例如:某个 Client 端上线之后又下线,导致 http 请求无法调用成功,从而阻断了整个注销流程)
|
||||
|
||||
全局异常处理:
|
||||
``` java
|
||||
|
@@ -41,6 +41,7 @@ private void configSso(SaTokenConfig cfg) {
|
||||
|
||||
// 配置 Http 请求处理器
|
||||
cfg.sso.setSendHttp(url -> {
|
||||
System.out.println("发起请求:" + url);
|
||||
return OkHttps.sync(url).get().getBody().toString();
|
||||
});
|
||||
}
|
||||
@@ -70,12 +71,12 @@ sa-token:
|
||||
``` java
|
||||
// 自定义接口:获取userinfo
|
||||
@RequestMapping("/sso/userinfo")
|
||||
public Object userinfo(String loginId, String secretkey) {
|
||||
public Object userinfo(String loginId) {
|
||||
System.out.println("---------------- 获取userinfo --------");
|
||||
|
||||
// 校验调用秘钥
|
||||
SaSsoUtil.checkSecretkey(secretkey);
|
||||
|
||||
// 校验签名,防止敏感信息外泄
|
||||
SaSsoUtil.checkSign(SaHolder.getRequest());
|
||||
|
||||
// 自定义返回结果(模拟)
|
||||
return SaResult.ok()
|
||||
.set("id", loginId)
|
||||
|
@@ -4,7 +4,6 @@ import java.util.Map;
|
||||
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.jwt.JWT;
|
||||
@@ -53,9 +52,6 @@ public class SaJwtUtil {
|
||||
*/
|
||||
public static String createToken(Object loginId, Map<String, Object> extraData, String keyt) {
|
||||
|
||||
// 秘钥不可以为空
|
||||
SaTokenException.throwByNull(keyt, "请配置jwt秘钥");
|
||||
|
||||
// 构建
|
||||
String token = JWT.create()
|
||||
.setPayload(LOGIN_ID, loginId)
|
||||
@@ -82,9 +78,6 @@ public class SaJwtUtil {
|
||||
public static String createToken(String loginType, Object loginId, String device,
|
||||
long timeout, Map<String, Object> extraData, String keyt) {
|
||||
|
||||
// 秘钥不可以为空
|
||||
SaTokenException.throwByNull(keyt, "请配置jwt秘钥");
|
||||
|
||||
// 计算有效期
|
||||
long effTime = timeout;
|
||||
if(timeout != NEVER_EXPIRE) {
|
||||
@@ -112,9 +105,7 @@ public class SaJwtUtil {
|
||||
* @return 解析后的jwt 对象
|
||||
*/
|
||||
public static JWT parseToken(String token, String keyt) {
|
||||
// 秘钥不可以为空
|
||||
SaTokenException.throwByNull(keyt, "请配置jwt秘钥");
|
||||
|
||||
|
||||
// 如果token为null
|
||||
if(token == null) {
|
||||
throw NotLoginException.newInstance(null, NotLoginException.NOT_TOKEN);
|
||||
|
@@ -7,6 +7,7 @@ import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.exception.ApiDisabledException;
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.stp.SaTokenInfo;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
@@ -38,7 +39,9 @@ public class StpLogicJwtForMix extends StpLogic {
|
||||
* @return /
|
||||
*/
|
||||
public String jwtSecretKey() {
|
||||
return getConfig().getJwtSecretKey();
|
||||
String keyt = getConfig().getJwtSecretKey();
|
||||
SaTokenException.throwByNull(keyt, "请配置jwt秘钥");
|
||||
return keyt;
|
||||
}
|
||||
|
||||
//
|
||||
|
@@ -40,7 +40,9 @@ public class StpLogicJwtForStateless extends StpLogic {
|
||||
* @return /
|
||||
*/
|
||||
public String jwtSecretKey() {
|
||||
return getConfig().getJwtSecretKey();
|
||||
String keyt = getConfig().getJwtSecretKey();
|
||||
SaTokenException.throwByNull(keyt, "请配置jwt秘钥");
|
||||
return keyt;
|
||||
}
|
||||
|
||||
//
|
||||
|
@@ -2,6 +2,7 @@ package cn.dev33.satoken.jwt;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
|
||||
@@ -32,7 +33,9 @@ public class StpLogicJwtForStyle extends StpLogic {
|
||||
* @return /
|
||||
*/
|
||||
public String jwtSecretKey() {
|
||||
return getConfig().getJwtSecretKey();
|
||||
String keyt = getConfig().getJwtSecretKey();
|
||||
SaTokenException.throwByNull(keyt, "请配置jwt秘钥");
|
||||
return keyt;
|
||||
}
|
||||
|
||||
// ------ 重写方法
|
||||
|
@@ -91,6 +91,14 @@ public class SaSsoConfig implements Serializable {
|
||||
public String ssoLogoutCall;
|
||||
|
||||
|
||||
// ----------------- 其它
|
||||
|
||||
/**
|
||||
* 接口调用时的时间戳允许的差距(单位:ms),-1代表不校验差距
|
||||
*/
|
||||
public long timestampDisparity = 1000 * 60 * 10;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@@ -253,12 +261,37 @@ public class SaSsoConfig implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 接口调用时的时间戳允许的差距(单位:ms),-1代表不校验差距
|
||||
*/
|
||||
public long getTimestampDisparity() {
|
||||
return timestampDisparity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param timestampDisparity 接口调用时的时间戳允许的差距(单位:ms),-1代表不校验差距
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setTimestampDisparity(long timestampDisparity) {
|
||||
this.timestampDisparity = timestampDisparity;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SaSsoConfig [ticketTimeout=" + ticketTimeout + ", allowUrl=" + allowUrl + ", isSlo=" + isSlo
|
||||
+ ", isHttp=" + isHttp + ", secretkey=" + secretkey + ", authUrl=" + authUrl + ", checkTicketUrl="
|
||||
+ checkTicketUrl + ", userinfoUrl=" + userinfoUrl + ", sloUrl=" + sloUrl + ", ssoLogoutCall="
|
||||
+ ssoLogoutCall + "]";
|
||||
return "SaSsoConfig ["
|
||||
+ "ticketTimeout=" + ticketTimeout
|
||||
+ ", allowUrl=" + allowUrl
|
||||
+ ", isSlo=" + isSlo
|
||||
+ ", isHttp=" + isHttp
|
||||
+ ", secretkey=" + secretkey
|
||||
+ ", authUrl=" + authUrl
|
||||
+ ", checkTicketUrl=" + checkTicketUrl
|
||||
+ ", userinfoUrl=" + userinfoUrl
|
||||
+ ", sloUrl=" + sloUrl
|
||||
+ ", ssoLogoutCall=" + ssoLogoutCall
|
||||
+ ", timestampDisparity=" + timestampDisparity
|
||||
+ "]";
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -62,6 +62,10 @@ public class SaSsoConsts {
|
||||
|
||||
public static String name = "name";
|
||||
public static String pwd = "pwd";
|
||||
|
||||
public static String timestamp = "timestamp";
|
||||
public static String nonce = "nonce";
|
||||
public static String sign = "sign";
|
||||
|
||||
}
|
||||
|
||||
|
@@ -1,8 +1,5 @@
|
||||
package cn.dev33.satoken.sso;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.config.SaSsoConfig;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
@@ -110,15 +107,13 @@ public class SaSsoHandle {
|
||||
}
|
||||
|
||||
/**
|
||||
* SSO-Server端:校验ticket 获取账号id
|
||||
* SSO-Server端:校验ticket 获取账号id [模式三]
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object ssoCheckTicket() {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
|
||||
// 获取参数
|
||||
String ticket = req.getParam(ParamName.ticket);
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
String ticket = req.getParamNotNull(ParamName.ticket);
|
||||
String sloCallback = req.getParam(ParamName.ssoLogoutCall);
|
||||
|
||||
// 校验ticket,获取 loginId
|
||||
@@ -143,15 +138,12 @@ public class SaSsoHandle {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaResponse res = SaHolder.getResponse();
|
||||
SaSsoConfig cfg = SaSsoManager.getConfig();
|
||||
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
|
||||
String loginId = req.getParam(ParamName.loginId);
|
||||
Object loginId = SaSsoUtil.saSsoTemplate.stpLogic.getLoginIdDefaultNull();
|
||||
|
||||
// step.1 遍历 Client 端注销
|
||||
SaSsoUtil.forEachSloUrl(loginId, url -> cfg.getSendHttp().apply(url));
|
||||
|
||||
// step.2 Server 端注销
|
||||
stpLogic.logout();
|
||||
// 单点注销
|
||||
if(SaFoxUtil.isNotEmpty(loginId)) {
|
||||
SaSsoUtil.ssoLogout(loginId);
|
||||
}
|
||||
|
||||
// 完成
|
||||
return ssoLogoutBack(req, res);
|
||||
@@ -162,25 +154,17 @@ public class SaSsoHandle {
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object ssoLogoutByClientHttp() {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaSsoConfig cfg = SaSsoManager.getConfig();
|
||||
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
|
||||
|
||||
// 获取参数
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
String loginId = req.getParam(ParamName.loginId);
|
||||
String secretkey = req.getParam(ParamName.secretkey);
|
||||
|
||||
// step.1 校验秘钥
|
||||
SaSsoUtil.checkSecretkey(secretkey);
|
||||
// step.1 校验签名
|
||||
SaSsoUtil.checkSign(req);
|
||||
|
||||
// step.2 遍历 Client 端注销
|
||||
SaSsoUtil.forEachSloUrl(loginId, url -> cfg.getSendHttp().apply(url));
|
||||
|
||||
// step.3 Server 端注销
|
||||
stpLogic.logout(loginId);
|
||||
|
||||
// 完成
|
||||
// step.2 单点注销
|
||||
SaSsoUtil.ssoLogout(loginId);
|
||||
|
||||
// 响应
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
@@ -302,12 +286,16 @@ public class SaSsoHandle {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 调用SSO-Server认证中心API,进行注销
|
||||
// 调用 sso-server 认证中心单点注销API
|
||||
String url = SaSsoUtil.buildSloUrl(stpLogic.getLoginId());
|
||||
SaResult result = request(url);
|
||||
SaResult result = SaSsoUtil.request(url);
|
||||
|
||||
// 校验
|
||||
// 校验响应状态码
|
||||
if(result.getCode() == SaResult.CODE_SUCCESS) {
|
||||
// 极端场景下,sso-server 中心的单点注销可能并不会通知到此 client 端,所以这里需要再补一刀
|
||||
if(stpLogic.isLogin()) {
|
||||
stpLogic.logout();
|
||||
}
|
||||
return ssoLogoutBack(req, res);
|
||||
} else {
|
||||
// 将 sso-server 回应的消息作为异常抛出
|
||||
@@ -325,11 +313,10 @@ public class SaSsoHandle {
|
||||
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
|
||||
|
||||
// 获取参数
|
||||
String loginId = req.getParam(ParamName.loginId);
|
||||
String secretkey = req.getParam(ParamName.secretkey);
|
||||
String loginId = req.getParamNotNull(ParamName.loginId);
|
||||
|
||||
// 注销当前应用端会话
|
||||
SaSsoUtil.checkSecretkey(secretkey);
|
||||
SaSsoUtil.checkSign(req);
|
||||
stpLogic.logout(loginId);
|
||||
|
||||
// 响应
|
||||
@@ -382,7 +369,7 @@ public class SaSsoHandle {
|
||||
|
||||
// 发起请求
|
||||
String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, ssoLogoutCall);
|
||||
SaResult result = request(checkUrl);
|
||||
SaResult result = SaSsoUtil.request(checkUrl);
|
||||
|
||||
// 校验
|
||||
if(result.getCode() == SaResult.CODE_SUCCESS) {
|
||||
@@ -397,15 +384,4 @@ public class SaSsoHandle {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发出请求,并返回 SaResult 结果
|
||||
* @param url 请求地址
|
||||
* @return 返回的结果
|
||||
*/
|
||||
public static SaResult request(String url) {
|
||||
String body = SaSsoManager.getConfig().getSendHttp().apply(url);
|
||||
Map<String, Object> map = SaManager.getSaJsonTemplate().parseJsonToMap(body);
|
||||
return new SaResult(map);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -3,10 +3,13 @@ package cn.dev33.satoken.sso;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.config.SaSsoConfig;
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.sso.SaSsoConsts.ParamName;
|
||||
import cn.dev33.satoken.sso.exception.SaSsoException;
|
||||
@@ -14,6 +17,7 @@ import cn.dev33.satoken.sso.exception.SaSsoExceptionCode;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO 单点登录模块
|
||||
@@ -149,6 +153,94 @@ public class SaSsoTemplate {
|
||||
return SaFoxUtil.getRandomString(64);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取:所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
|
||||
* @return see note
|
||||
*/
|
||||
public String getAllowUrl() {
|
||||
// 默认从配置文件中返回
|
||||
return SaSsoManager.getConfig().getAllowUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验重定向url合法性
|
||||
* @param url 下放ticket的url地址
|
||||
*/
|
||||
public void checkRedirectUrl(String url) {
|
||||
|
||||
// 1、是否是一个有效的url
|
||||
if(SaFoxUtil.isUrl(url) == false) {
|
||||
throw new SaSsoException("无效redirect:" + url).setCode(SaSsoExceptionCode.CODE_20001);
|
||||
}
|
||||
|
||||
// 2、截取掉?后面的部分
|
||||
int qIndex = url.indexOf("?");
|
||||
if(qIndex != -1) {
|
||||
url = url.substring(0, qIndex);
|
||||
}
|
||||
|
||||
// 3、是否在[允许地址列表]之中
|
||||
List<String> authUrlList = Arrays.asList(getAllowUrl().replaceAll(" ", "").split(","));
|
||||
if(SaStrategy.me.hasElement.apply(authUrlList, url) == false) {
|
||||
throw new SaSsoException("非法redirect:" + url).setCode(SaSsoExceptionCode.CODE_20002);
|
||||
}
|
||||
|
||||
// 校验通过 √
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// ------------------- SSO 模式三相关 -------------------
|
||||
|
||||
/**
|
||||
* 为指定账号id注册单点注销回调URL
|
||||
* @param loginId 账号id
|
||||
* @param sloCallbackUrl 单点注销时的回调URL
|
||||
*/
|
||||
public void registerSloCallbackUrl(Object loginId, String sloCallbackUrl) {
|
||||
if(SaFoxUtil.isEmpty(loginId) || SaFoxUtil.isEmpty(sloCallbackUrl)) {
|
||||
return;
|
||||
}
|
||||
SaSession session = stpLogic.getSessionByLoginId(loginId);
|
||||
Set<String> urlSet = session.get(SaSsoConsts.SLO_CALLBACK_SET_KEY, ()-> new HashSet<String>());
|
||||
urlSet.add(sloCallbackUrl);
|
||||
session.set(SaSsoConsts.SLO_CALLBACK_SET_KEY, urlSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定账号单点注销
|
||||
* @param loginId 指定账号
|
||||
*/
|
||||
public void ssoLogout(Object loginId) {
|
||||
|
||||
// 如果这个账号尚未登录,则无操作
|
||||
SaSession session = stpLogic.getSessionByLoginId(loginId, false);
|
||||
if(session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// step.1 遍历通知 Client 端注销会话
|
||||
SaSsoConfig cfg = SaSsoManager.getConfig();
|
||||
Set<String> urlSet = session.get(SaSsoConsts.SLO_CALLBACK_SET_KEY, () -> new HashSet<String>());
|
||||
for (String url : urlSet) {
|
||||
url = addSignParams(url, loginId);
|
||||
cfg.getSendHttp().apply(url);
|
||||
}
|
||||
|
||||
// step.2 Server端注销
|
||||
stpLogic.logout(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取:账号资料
|
||||
* @param loginId 账号id
|
||||
* @return 账号资料
|
||||
*/
|
||||
public Object getUserinfo(Object loginId) {
|
||||
String url = buildUserinfoUrl(loginId);
|
||||
return SaSsoManager.getConfig().getSendHttp().apply(url);
|
||||
}
|
||||
|
||||
|
||||
// ---------------------- 构建URL ----------------------
|
||||
|
||||
@@ -202,43 +294,7 @@ public class SaSsoTemplate {
|
||||
// 构建 授权重定向地址 (Server端 根据此地址向 Client端 下放Ticket)
|
||||
return SaFoxUtil.joinParam(encodeBackParam(redirect), ParamName.ticket, ticket);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验重定向url合法性
|
||||
* @param url 下放ticket的url地址
|
||||
*/
|
||||
public void checkRedirectUrl(String url) {
|
||||
|
||||
// 1、是否是一个有效的url
|
||||
if(SaFoxUtil.isUrl(url) == false) {
|
||||
throw new SaSsoException("无效redirect:" + url).setCode(SaSsoExceptionCode.CODE_20001);
|
||||
}
|
||||
|
||||
// 2、截取掉?后面的部分
|
||||
int qIndex = url.indexOf("?");
|
||||
if(qIndex != -1) {
|
||||
url = url.substring(0, qIndex);
|
||||
}
|
||||
|
||||
// 3、是否在[允许地址列表]之中
|
||||
List<String> authUrlList = Arrays.asList(getAllowUrl().replaceAll(" ", "").split(","));
|
||||
if(SaStrategy.me.hasElement.apply(authUrlList, url) == false) {
|
||||
throw new SaSsoException("非法redirect:" + url).setCode(SaSsoExceptionCode.CODE_20002);
|
||||
}
|
||||
|
||||
// 校验通过 √
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取:所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
|
||||
* @return see note
|
||||
*/
|
||||
public String getAllowUrl() {
|
||||
// 默认从配置文件中返回
|
||||
return SaSsoManager.getConfig().getAllowUrl();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 对url中的back参数进行URL编码, 解决超链接重定向后参数丢失的bug
|
||||
* @param url url
|
||||
@@ -271,27 +327,10 @@ public class SaSsoTemplate {
|
||||
* @return Server端 账号资料查询地址
|
||||
*/
|
||||
public String buildUserinfoUrl(Object loginId) {
|
||||
// 拼接
|
||||
String userinfoUrl = SaSsoManager.getConfig().getUserinfoUrl();
|
||||
userinfoUrl = SaFoxUtil.joinParam(userinfoUrl, ParamName.loginId, loginId);
|
||||
userinfoUrl = SaFoxUtil.joinParam(userinfoUrl, ParamName.secretkey, SaSsoManager.getConfig().getSecretkey());
|
||||
// 返回
|
||||
return userinfoUrl;
|
||||
return addSignParams(userinfoUrl, loginId);
|
||||
}
|
||||
|
||||
|
||||
// ------------------- SSO 模式三相关 -------------------
|
||||
|
||||
/**
|
||||
* 校验secretkey秘钥是否有效
|
||||
* @param secretkey 秘钥
|
||||
*/
|
||||
public void checkSecretkey(String secretkey) {
|
||||
if(secretkey == null || secretkey.isEmpty() || secretkey.equals(SaSsoManager.getConfig().getSecretkey()) == false) {
|
||||
throw new SaSsoException("无效秘钥:" + secretkey).setCode(SaSsoExceptionCode.CODE_20003);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL:校验ticket的URL
|
||||
* <p> 在模式三下,Client端拿到Ticket后根据此地址向Server端发送请求,获取账号id
|
||||
@@ -314,43 +353,6 @@ public class SaSsoTemplate {
|
||||
// 返回
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为指定账号id注册单点注销回调URL
|
||||
* @param loginId 账号id
|
||||
* @param sloCallbackUrl 单点注销时的回调URL
|
||||
*/
|
||||
public void registerSloCallbackUrl(Object loginId, String sloCallbackUrl) {
|
||||
if(loginId == null || sloCallbackUrl == null || sloCallbackUrl.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
SaSession session = stpLogic.getSessionByLoginId(loginId);
|
||||
Set<String> urlSet = session.get(SaSsoConsts.SLO_CALLBACK_SET_KEY, ()-> new HashSet<String>());
|
||||
urlSet.add(sloCallbackUrl);
|
||||
session.set(SaSsoConsts.SLO_CALLBACK_SET_KEY, urlSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 循环调用Client端单点注销回调
|
||||
* @param loginId 账号id
|
||||
* @param fun 调用方法
|
||||
*/
|
||||
public void forEachSloUrl(Object loginId, CallSloUrlFunction fun) {
|
||||
SaSession session = stpLogic.getSessionByLoginId(loginId, false);
|
||||
if(session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String secretkey = SaSsoManager.getConfig().getSecretkey();
|
||||
Set<String> urlSet = session.get(SaSsoConsts.SLO_CALLBACK_SET_KEY, () -> new HashSet<String>());
|
||||
for (String url : urlSet) {
|
||||
// 拼接:login参数、秘钥参数
|
||||
url = SaFoxUtil.joinParam(url, ParamName.loginId, loginId);
|
||||
url = SaFoxUtil.joinParam(url, ParamName.secretkey, secretkey);
|
||||
// 调用
|
||||
fun.run(url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL:单点注销URL
|
||||
@@ -358,42 +360,10 @@ public class SaSsoTemplate {
|
||||
* @return 单点注销URL
|
||||
*/
|
||||
public String buildSloUrl(Object loginId) {
|
||||
SaSsoConfig ssoConfig = SaSsoManager.getConfig();
|
||||
String url = ssoConfig.getSloUrl();
|
||||
url = SaFoxUtil.joinParam(url, ParamName.loginId, loginId);
|
||||
url = SaFoxUtil.joinParam(url, ParamName.secretkey, ssoConfig.getSecretkey());
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定账号单点注销
|
||||
* @param secretkey 校验秘钥
|
||||
* @param loginId 指定账号
|
||||
* @param fun 调用方法
|
||||
*/
|
||||
public void singleLogout(String secretkey, Object loginId, CallSloUrlFunction fun) {
|
||||
// step.1 校验秘钥
|
||||
checkSecretkey(secretkey);
|
||||
|
||||
// step.2 遍历通知Client端注销会话
|
||||
forEachSloUrl(loginId, fun);
|
||||
|
||||
// step.3 Server端注销
|
||||
// StpUtil.logoutByLoginId(loginId);
|
||||
stpLogic.logoutByTokenValue(stpLogic.getTokenValueByLoginId(loginId));
|
||||
String url = SaSsoManager.getConfig().getSloUrl();
|
||||
return addSignParams(url, loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取:账号资料
|
||||
* @param loginId 账号id
|
||||
* @return 账号资料
|
||||
*/
|
||||
public Object getUserinfo(Object loginId) {
|
||||
String url = buildUserinfoUrl(loginId);
|
||||
return SaSsoManager.getConfig().getSendHttp().apply(url);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ------------------- 返回相应key -------------------
|
||||
|
||||
@@ -415,17 +385,112 @@ public class SaSsoTemplate {
|
||||
return SaManager.getConfig().getTokenName() + ":id-ticket:" + id;
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 请求相关 -------------------
|
||||
|
||||
/**
|
||||
* 单点注销回调函数
|
||||
* @author kong
|
||||
* 发出请求,并返回 SaResult 结果
|
||||
* @param url 请求地址
|
||||
* @return 返回的结果
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public static interface CallSloUrlFunction{
|
||||
/**
|
||||
* 调用function
|
||||
* @param url 注销回调URL
|
||||
*/
|
||||
public void run(String url);
|
||||
public SaResult request(String url) {
|
||||
String body = SaSsoManager.getConfig().getSendHttp().apply(url);
|
||||
Map<String, Object> map = SaManager.getSaJsonTemplate().parseJsonToMap(body);
|
||||
return new SaResult(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取:接口调用秘钥
|
||||
* @return see note
|
||||
*/
|
||||
public String getSecretkey() {
|
||||
// 默认从配置文件中返回
|
||||
String secretkey = SaSsoManager.getConfig().getSecretkey();
|
||||
if(SaFoxUtil.isEmpty(secretkey)) {
|
||||
throw new SaSsoException("请配置 secretkey 参数").setCode(SaSsoExceptionCode.CODE_20009);
|
||||
}
|
||||
return secretkey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验secretkey秘钥是否有效 (API已过期,请更改为更安全的 sign 式校验)
|
||||
* @param secretkey 秘钥
|
||||
*/
|
||||
@Deprecated
|
||||
public void checkSecretkey(String secretkey) {
|
||||
if(SaFoxUtil.isEmpty(secretkey) || secretkey.equals(getSecretkey()) == false) {
|
||||
throw new SaSsoException("无效秘钥:" + secretkey).setCode(SaSsoExceptionCode.CODE_20003);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据参数计算签名
|
||||
* @param loginId 账号id
|
||||
* @param timestamp 当前时间戳,13位
|
||||
* @param nonce 随机字符串
|
||||
* @param secretkey 账号id
|
||||
* @return 签名
|
||||
*/
|
||||
public String getSign(Object loginId, String timestamp, String nonce, String secretkey) {
|
||||
Map<String, Object> map = new TreeMap<>();
|
||||
map.put(ParamName.loginId, loginId);
|
||||
map.put(ParamName.timestamp, timestamp);
|
||||
map.put(ParamName.nonce, nonce);
|
||||
return SaManager.getSaSignTemplate().createSign(map, secretkey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给 url 追加 sign 等参数
|
||||
* @param loginId
|
||||
* @return 加工后的url
|
||||
*/
|
||||
public String addSignParams(String url, Object loginId) {
|
||||
|
||||
// 时间戳、随机字符串、参数签名
|
||||
String timestamp = String.valueOf(System.currentTimeMillis());
|
||||
String nonce = SaFoxUtil.getRandomString(20);
|
||||
String sign = getSign(loginId, timestamp, nonce, getSecretkey());
|
||||
|
||||
// 追加到url
|
||||
url = SaFoxUtil.joinParam(url, ParamName.loginId, loginId);
|
||||
url = SaFoxUtil.joinParam(url, ParamName.timestamp, timestamp);
|
||||
url = SaFoxUtil.joinParam(url, ParamName.nonce, nonce);
|
||||
url = SaFoxUtil.joinParam(url, ParamName.sign, sign);
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验签名
|
||||
* @param req request
|
||||
*/
|
||||
public void checkSign(SaRequest req) {
|
||||
|
||||
// 参数签名、账号id、时间戳、随机字符串
|
||||
String sign = req.getParamNotNull(ParamName.sign);
|
||||
String loginId = req.getParamNotNull(ParamName.loginId);
|
||||
String timestamp = req.getParamNotNull(ParamName.timestamp);
|
||||
String nonce = req.getParamNotNull(ParamName.nonce);
|
||||
|
||||
// 校验时间戳
|
||||
checkTimestamp(Long.valueOf(timestamp));
|
||||
|
||||
// 校验签名
|
||||
String calcSign = getSign(loginId, timestamp, nonce, getSecretkey());
|
||||
if(calcSign.equals(sign) == false) {
|
||||
throw new SaSsoException("签名无效:" + calcSign).setCode(SaSsoExceptionCode.CODE_20008);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验时间戳与当前时间的差距是否超出限制
|
||||
* @param timestamp 时间戳
|
||||
*/
|
||||
public void checkTimestamp(long timestamp) {
|
||||
long disparity = Math.abs(System.currentTimeMillis() - timestamp);
|
||||
long allowDisparity = SaSsoManager.getConfig().getTimestampDisparity();
|
||||
if(allowDisparity != -1 && disparity > allowDisparity) {
|
||||
throw new SaSsoException("timestamp 超出允许的范围").setCode(SaSsoExceptionCode.CODE_20007);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,7 +1,8 @@
|
||||
package cn.dev33.satoken.sso;
|
||||
|
||||
import cn.dev33.satoken.sso.SaSsoTemplate.CallSloUrlFunction;
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO 单点登录模块 工具类
|
||||
@@ -71,7 +72,71 @@ public class SaSsoUtil {
|
||||
public static Object checkTicket(String ticket) {
|
||||
return saSsoTemplate.checkTicket(ticket);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取:所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
|
||||
* @return see note
|
||||
*/
|
||||
public static String getAllowUrl() {
|
||||
return saSsoTemplate.getAllowUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验重定向url合法性
|
||||
* @param url 下放ticket的url地址
|
||||
*/
|
||||
public static void checkRedirectUrl(String url) {
|
||||
saSsoTemplate.checkRedirectUrl(url);
|
||||
}
|
||||
|
||||
|
||||
// ------------------- SSO 模式三 -------------------
|
||||
|
||||
/**
|
||||
* 构建URL:校验ticket的URL
|
||||
* @param ticket ticket码
|
||||
* @param ssoLogoutCallUrl 单点注销时的回调URL
|
||||
* @return 构建完毕的URL
|
||||
*/
|
||||
public static String buildCheckTicketUrl(String ticket, String ssoLogoutCallUrl) {
|
||||
return saSsoTemplate.buildCheckTicketUrl(ticket, ssoLogoutCallUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为指定账号id注册单点注销回调URL
|
||||
* @param loginId 账号id
|
||||
* @param sloCallbackUrl 单点注销时的回调URL
|
||||
*/
|
||||
public static void registerSloCallbackUrl(Object loginId, String sloCallbackUrl) {
|
||||
saSsoTemplate.registerSloCallbackUrl(loginId, sloCallbackUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL:单点注销URL
|
||||
* @param loginId 要注销的账号id
|
||||
* @return 单点注销URL
|
||||
*/
|
||||
public static String buildSloUrl(Object loginId) {
|
||||
return saSsoTemplate.buildSloUrl(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定账号单点注销
|
||||
* @param loginId 指定账号
|
||||
*/
|
||||
public static void ssoLogout(Object loginId) {
|
||||
saSsoTemplate.ssoLogout(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取:账号资料
|
||||
* @param loginId 账号id
|
||||
* @return 账号资料
|
||||
*/
|
||||
public static Object getUserinfo(Object loginId) {
|
||||
return saSsoTemplate.getUserinfo(loginId);
|
||||
}
|
||||
|
||||
|
||||
// ---------------------- 构建URL ----------------------
|
||||
|
||||
@@ -94,22 +159,6 @@ public class SaSsoUtil {
|
||||
public static String buildRedirectUrl(Object loginId, String redirect) {
|
||||
return saSsoTemplate.buildRedirectUrl(loginId, redirect);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验重定向url合法性
|
||||
* @param url 下放ticket的url地址
|
||||
*/
|
||||
public static void checkRedirectUrl(String url) {
|
||||
saSsoTemplate.checkRedirectUrl(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取:所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
|
||||
* @return see note
|
||||
*/
|
||||
public static String getAllowUrl() {
|
||||
return saSsoTemplate.getAllowUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL:Server端 账号资料查询地址
|
||||
@@ -120,70 +169,62 @@ public class SaSsoUtil {
|
||||
return saSsoTemplate.buildUserinfoUrl(loginId);
|
||||
}
|
||||
|
||||
// ------------------- SSO 模式三 -------------------
|
||||
|
||||
// ------------------- 请求相关 -------------------
|
||||
|
||||
/**
|
||||
* 校验secretkey秘钥是否有效
|
||||
* 发出请求,并返回 SaResult 结果
|
||||
* @param url 请求地址
|
||||
* @return 返回的结果
|
||||
*/
|
||||
public static SaResult request(String url) {
|
||||
return saSsoTemplate.request(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验secretkey秘钥是否有效 (API已过期,请更改为更安全的 sign 式校验)
|
||||
* @param secretkey 秘钥
|
||||
*/
|
||||
@Deprecated
|
||||
public static void checkSecretkey(String secretkey) {
|
||||
saSsoTemplate.checkSecretkey(secretkey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL:校验ticket的URL
|
||||
* @param ticket ticket码
|
||||
* @param ssoLogoutCallUrl 单点注销时的回调URL
|
||||
* @return 构建完毕的URL
|
||||
*/
|
||||
public static String buildCheckTicketUrl(String ticket, String ssoLogoutCallUrl) {
|
||||
return saSsoTemplate.buildCheckTicketUrl(ticket, ssoLogoutCallUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为指定账号id注册单点注销回调URL
|
||||
* 根据参数计算签名
|
||||
* @param loginId 账号id
|
||||
* @param sloCallbackUrl 单点注销时的回调URL
|
||||
* @param timestamp 当前时间戳,13位
|
||||
* @param nonce 随机字符串
|
||||
* @param secretkey 账号id
|
||||
* @return 签名
|
||||
*/
|
||||
public static void registerSloCallbackUrl(Object loginId, String sloCallbackUrl) {
|
||||
saSsoTemplate.registerSloCallbackUrl(loginId, sloCallbackUrl);
|
||||
public static String getSign(Object loginId, String timestamp, String nonce, String secretkey) {
|
||||
return saSsoTemplate.getSign(loginId, timestamp, nonce, secretkey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 循环调用Client端单点注销回调
|
||||
* @param loginId 账号id
|
||||
* @param fun 调用方法
|
||||
* 给 url 追加 sign 等参数
|
||||
* @param loginId
|
||||
* @return 加工后的url
|
||||
*/
|
||||
public static void forEachSloUrl(Object loginId, CallSloUrlFunction fun) {
|
||||
saSsoTemplate.forEachSloUrl(loginId, fun);
|
||||
public static String addSignParams(String url, Object loginId) {
|
||||
return saSsoTemplate.addSignParams(url, loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL:单点注销URL
|
||||
* @param loginId 要注销的账号id
|
||||
* @return 单点注销URL
|
||||
* 校验签名
|
||||
* @param req request
|
||||
*/
|
||||
public static String buildSloUrl(Object loginId) {
|
||||
return saSsoTemplate.buildSloUrl(loginId);
|
||||
public static void checkSign(SaRequest req) {
|
||||
saSsoTemplate.checkSign(req);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定账号单点注销
|
||||
* @param secretkey 校验秘钥
|
||||
* @param loginId 指定账号
|
||||
* @param fun 调用方法
|
||||
* 校验时间戳与当前时间的差距是否超出限制
|
||||
* @param timestamp 时间戳
|
||||
*/
|
||||
public static void singleLogout(String secretkey, Object loginId, CallSloUrlFunction fun) {
|
||||
saSsoTemplate.singleLogout(secretkey, loginId, fun);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取:账号资料
|
||||
* @param loginId 账号id
|
||||
* @return 账号资料
|
||||
*/
|
||||
public static Object getUserinfo(Object loginId) {
|
||||
return saSsoTemplate.getUserinfo(loginId);
|
||||
public static void checkTimestamp(long timestamp) {
|
||||
saSsoTemplate.checkTimestamp(timestamp);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -25,5 +25,14 @@ public class SaSsoExceptionCode {
|
||||
|
||||
/** 在模式三下,sso-client 调用 sso-server 端 单点注销接口 时,得到的响应是注销失败 */
|
||||
public static final int CODE_20006 = 20006;
|
||||
|
||||
/** http 请求调用 提供的 timestamp 与当前时间的差距超出允许的范围 */
|
||||
public static final int CODE_20007 = 20007;
|
||||
|
||||
/** http 请求调用 提供的 sign 无效 */
|
||||
public static final int CODE_20008 = 20008;
|
||||
|
||||
/** 本地系统没有配置 secretkey 字段 */
|
||||
public static final int CODE_20009 = 20009;
|
||||
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@ import cn.dev33.satoken.id.SaIdTemplate;
|
||||
import cn.dev33.satoken.id.SaIdUtil;
|
||||
import cn.dev33.satoken.json.SaJsonTemplate;
|
||||
import cn.dev33.satoken.listener.SaTokenListener;
|
||||
import cn.dev33.satoken.sign.SaSignTemplate;
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
@@ -127,6 +128,16 @@ public class SaBeanInject {
|
||||
public void setSaJsonTemplate(SaJsonTemplate saJsonTemplate) {
|
||||
SaManager.setSaJsonTemplate(saJsonTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注入自定义的 参数签名 Bean
|
||||
*
|
||||
* @param saSignTemplate 参数签名 Bean
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
public void setSaSignTemplate(SaSignTemplate saSignTemplate) {
|
||||
SaManager.setSaSignTemplate(saSignTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注入自定义的 StpLogic
|
||||
|
@@ -18,7 +18,9 @@ import cn.dev33.satoken.context.second.SaTokenSecondContextCreator;
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.id.SaIdTemplate;
|
||||
import cn.dev33.satoken.id.SaIdUtil;
|
||||
import cn.dev33.satoken.json.SaJsonTemplate;
|
||||
import cn.dev33.satoken.listener.SaTokenListener;
|
||||
import cn.dev33.satoken.sign.SaSignTemplate;
|
||||
import cn.dev33.satoken.solon.integration.SaContextForSolon;
|
||||
import cn.dev33.satoken.solon.integration.SaTokenMethodInterceptor;
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
@@ -90,6 +92,16 @@ public class XPluginImp implements Plugin {
|
||||
SaBasicUtil.saBasicTemplate = bw.raw();
|
||||
});
|
||||
|
||||
// Sa-Token JSON 转换器 Bean
|
||||
Aop.getAsyn(SaJsonTemplate.class, bw->{
|
||||
SaManager.setSaJsonTemplate(bw.raw());
|
||||
});
|
||||
|
||||
// Sa-Token 参数签名算法 Bean
|
||||
Aop.getAsyn(SaSignTemplate.class, bw->{
|
||||
SaManager.setSaSignTemplate(bw.raw());
|
||||
});
|
||||
|
||||
// 自定义 StpLogic 对象
|
||||
Aop.getAsyn(StpLogic.class, bw->{
|
||||
StpUtil.setStpLogic(bw.raw());
|
||||
|
@@ -15,6 +15,7 @@ import cn.dev33.satoken.id.SaIdTemplate;
|
||||
import cn.dev33.satoken.id.SaIdUtil;
|
||||
import cn.dev33.satoken.json.SaJsonTemplate;
|
||||
import cn.dev33.satoken.listener.SaTokenListener;
|
||||
import cn.dev33.satoken.sign.SaSignTemplate;
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
@@ -127,6 +128,16 @@ public class SaBeanInject {
|
||||
public void setSaJsonTemplate(SaJsonTemplate saJsonTemplate) {
|
||||
SaManager.setSaJsonTemplate(saJsonTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注入自定义的 参数签名 Bean
|
||||
*
|
||||
* @param saSignTemplate 参数签名 Bean
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
public void setSaSignTemplate(SaSignTemplate saSignTemplate) {
|
||||
SaManager.setSaSignTemplate(saSignTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注入自定义的 StpLogic
|
||||
|
Reference in New Issue
Block a user