feat: sso 模块新增 allowAnonClient 决定是否启用匿名 client & 新增 clients 配置项,用于单独配置每个 client 的授权信息

This commit is contained in:
click33 2025-04-25 15:39:11 +08:00
parent 2d777fbd22
commit 79113c5848
10 changed files with 407 additions and 46 deletions

View File

@ -21,7 +21,7 @@ public class H5Controller {
* 获取 redirectUrl
*/
@RequestMapping("/sso/getRedirectUrl")
public SaResult getRedirectUrl(String redirect, String mode, String client) {
public SaResult getRedirectUrl(String client, String redirect, String mode) {
// 未登录情况下返回 code=401
if(StpUtil.isLogin() == false) {
return SaResult.code(401);
@ -30,7 +30,7 @@ public class H5Controller {
redirect = SaFoxUtil.decoderUrl(redirect);
if(SaSsoConsts.MODE_SIMPLE.equals(mode)) {
// 模式一
SaSsoUtil.checkRedirectUrl(redirect);
SaSsoUtil.checkRedirectUrl(client, redirect);
return SaResult.data(redirect);
} else {
// 模式二或模式三

View File

@ -19,6 +19,14 @@ sa-token:
# ------- SSO-模式三相关配置 下面的配置在使用SSO模式三时打开
# 是否打开模式三
is-http: true
allow-anon-client: false
clients:
sso-client2:
client: sso-client2
allow-url: http://sa-sso-client1.com:9002/sso/login
sign:
# API 接口调用秘钥
secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor

View File

@ -10,6 +10,9 @@ sa-token:
server-url: http://sa-sso-server.com:9000
# 在 sso-server 端前后端分离时打开这个上面的不要注释auth-url 配置项和 server-url 要同时存在)
# auth-url: http://127.0.0.1:8848/sa-token-demo-sso-server-h5/sso-auth.html
client: sso-client2
sign:
# API 接口调用秘钥
secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor

View File

@ -0,0 +1,128 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.sso.config;
import cn.dev33.satoken.sso.template.SaSsoServerTemplate;
import cn.dev33.satoken.util.SaFoxUtil;
import java.io.Serializable;
import java.util.List;
/**
* Sa-Token SSO 客户端信息配置
*
* @author click33
* @since 1.42.0
*/
public class SaSsoClientModel implements Serializable {
private static final long serialVersionUID = -6541180061782004705L;
/**
* 当前 Client 名称标识用于和 ticket 码的互相锁定
*/
public String client;
/**
* 所有允许的授权回调地址多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
*/
public String allowUrl = "*";
/**
* 是否打开单点注销功能
*/
public Boolean isSlo = true;
// 额外方法
/**
* 以数组形式写入允许的授权回调地址
* @param url 所有集合
* @return 对象自身
*/
public SaSsoClientModel setAllow(String ...url) {
this.setAllowUrl(SaFoxUtil.arrayJoin(url));
return this;
}
// get set
/**
* @return 当前 Client 名称标识
*/
public String getClient() {
return client;
}
/**
* @param client 当前 Client 名称标识
*/
public SaSsoClientModel setClient(String client) {
this.client = client;
return this;
}
/**
* @return 所有允许的授权回调地址多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
*/
public String getAllowUrl() {
return allowUrl;
}
/**
* @param allowUrl 所有允许的授权回调地址多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
* @return 对象自身
*/
public SaSsoClientModel setAllowUrl(String allowUrl) {
// 提前校验一下配置的 allowUrl 是否合法让开发者尽早发现错误
if(SaFoxUtil.isNotEmpty(allowUrl)) {
List<String> allowUrlList = SaFoxUtil.convertStringToList(allowUrl);
SaSsoServerTemplate.checkAllowUrlListStaticMethod(allowUrlList);
}
this.allowUrl = allowUrl;
return this;
}
/**
* @return 是否打开单点注销功能
*/
public Boolean getIsSlo() {
return isSlo;
}
/**
* @param isSlo 是否打开单点注销功能
* @return 对象自身
*/
public SaSsoClientModel setIsSlo(Boolean isSlo) {
this.isSlo = isSlo;
return this;
}
@Override
public String toString() {
return "SaSsoClientModel ["
+ "client=" + client
+ ", allowUrl=" + allowUrl
+ ", isSlo=" + isSlo
+ "]";
}
}

View File

@ -27,7 +27,9 @@ import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Sa-Token SSO 单点登录模块 配置类 Server端
@ -82,11 +84,44 @@ public class SaSsoServerConfig implements Serializable {
*/
public int maxRegClient = 32;
/**
* 是否允许匿名 Client 接入
*/
public Boolean allowAnonClient = true;
/**
* 是否校验参数签名方便本地调试用的一个配置项生产环境请务必为true
*/
public Boolean isCheckSign = true;
/**
* Client 信息配置列表
*/
public Map<String, SaSsoClientModel> clients = new LinkedHashMap<>();
// 额外方法
/**
* 以数组形式写入允许的授权回调地址
* @param url 所有集合
* @return 对象自身
*/
public SaSsoServerConfig setAllow(String ...url) {
this.setAllowUrl(SaFoxUtil.arrayJoin(url));
return this;
}
/**
* 添加一个应用
* @param client /
* @return 对象自身
*/
public SaSsoServerConfig addClient(SaSsoClientModel client) {
this.clients.put(client.getClient(), client);
return this;
}
// get set
@ -245,12 +280,41 @@ public class SaSsoServerConfig implements Serializable {
}
/**
* 以数组形式写入允许的授权回调地址
* @param url 所有集合
* 获取 是否允许匿名 Client 接入
*
* @return /
*/
public Boolean getAllowAnonClient() {
return this.allowAnonClient;
}
/**
* 设置 是否允许匿名 Client 接入
*
* @param allowAnonClient /
*/
public SaSsoServerConfig setAllowAnonClient(Boolean allowAnonClient) {
this.allowAnonClient = allowAnonClient;
return this;
}
/**
* 获取 Client 信息配置列表
*
* @return clients Client 信息配置列表
*/
public Map<String, SaSsoClientModel> getClients() {
return this.clients;
}
/**
* 设置 Client 信息配置列表
*
* @param clients Client 信息配置列表
* @return 对象自身
*/
public SaSsoServerConfig setAllow(String ...url) {
this.setAllowUrl(SaFoxUtil.arrayJoin(url));
public SaSsoServerConfig setClients(Map<String, SaSsoClientModel> clients) {
this.clients = clients;
return this;
}
@ -266,6 +330,8 @@ public class SaSsoServerConfig implements Serializable {
+ ", autoRenewTimeout=" + autoRenewTimeout
+ ", maxRegClient=" + maxRegClient
+ ", isCheckSign=" + isCheckSign
+ ", allowAnonClient=" + allowAnonClient
+ ", clients=" + clients
+ "]";
}
@ -301,4 +367,76 @@ public class SaSsoServerConfig implements Serializable {
throw new SaSsoException("请配置 Http 请求处理器").setCode(SaSsoErrorCode.CODE_30010);
};
/**
* 获取 SSO-Server端未登录时返回的View
*
* @return notLoginView SSO-Server端未登录时返回的View
*/
public NotLoginViewFunction getNotLoginView() {
return this.notLoginView;
}
/**
* 设置 SSO-Server端未登录时返回的View
*
* @param notLoginView SSO-Server端未登录时返回的View
*/
public void setNotLoginView(NotLoginViewFunction notLoginView) {
this.notLoginView = notLoginView;
}
/**
* 获取 SSO-Server端登录函数
*
* @return doLoginHandle SSO-Server端登录函数
*/
public DoLoginHandleFunction getDoLoginHandle() {
return this.doLoginHandle;
}
/**
* 设置 SSO-Server端登录函数
*
* @param doLoginHandle SSO-Server端登录函数
*/
public void setDoLoginHandle(DoLoginHandleFunction doLoginHandle) {
this.doLoginHandle = doLoginHandle;
}
/**
* 获取 SSO-Server端在校验 ticket sso-client 端追加返回信息的函数
*
* @return checkTicketAppendData SSO-Server端在校验 ticket sso-client 端追加返回信息的函数
*/
public CheckTicketAppendDataFunction getCheckTicketAppendData() {
return this.checkTicketAppendData;
}
/**
* 设置 SSO-Server端在校验 ticket sso-client 端追加返回信息的函数
*
* @param checkTicketAppendData SSO-Server端在校验 ticket sso-client 端追加返回信息的函数
*/
public void setCheckTicketAppendData(CheckTicketAppendDataFunction checkTicketAppendData) {
this.checkTicketAppendData = checkTicketAppendData;
}
/**
* 获取 SSO-Server端发送Http请求的处理函数
*
* @return sendHttp SSO-Server端发送Http请求的处理函数
*/
public SendHttpFunction getSendHttp() {
return this.sendHttp;
}
/**
* 设置 SSO-Server端发送Http请求的处理函数
*
* @param sendHttp SSO-Server端发送Http请求的处理函数
*/
public void setSendHttp(SendHttpFunction sendHttp) {
this.sendHttp = sendHttp;
}
}

View File

@ -21,12 +21,12 @@ import cn.dev33.satoken.sso.util.SaSsoConsts;
import java.io.Serializable;
/**
* Sa-Token SSO Model
* Sa-Token SSO 应用信息
*
* @author click33
* @since 1.38.0
*/
public class SaSsoClientModel implements Serializable {
public class SaSsoClientInfo implements Serializable {
private static final long serialVersionUID = 1406115065849845073L;
@ -65,13 +65,13 @@ public class SaSsoClientModel implements Serializable {
*/
public int index;
public SaSsoClientModel() {
public SaSsoClientInfo() {
}
/**
* 模式三构建
*/
public SaSsoClientModel(String client, String sloCallbackUrl, int index) {
public SaSsoClientInfo(String client, String sloCallbackUrl, int index) {
this.mode = SaSsoConsts.SSO_MODE_3;
this.client = client;
this.sloCallbackUrl = sloCallbackUrl;
@ -95,7 +95,7 @@ public class SaSsoClientModel implements Serializable {
* @param mode client 登录模式1=模式一2=模式二3=模式三
* @return /
*/
public SaSsoClientModel setMode(int mode) {
public SaSsoClientInfo setMode(int mode) {
this.mode = mode;
return this;
}
@ -115,7 +115,7 @@ public class SaSsoClientModel implements Serializable {
* @param client 客户端标识
* @return /
*/
public SaSsoClientModel setClient(String client) {
public SaSsoClientInfo setClient(String client) {
this.client = client;
return this;
}
@ -155,7 +155,7 @@ public class SaSsoClientModel implements Serializable {
* @param sloCallbackUrl 单点注销回调url
* @return /
*/
public SaSsoClientModel setSloCallbackUrl(String sloCallbackUrl) {
public SaSsoClientInfo setSloCallbackUrl(String sloCallbackUrl) {
this.sloCallbackUrl = sloCallbackUrl;
return this;
}
@ -175,7 +175,7 @@ public class SaSsoClientModel implements Serializable {
* @param regTime client 注册信息的时间13位时间戳
* @return /
*/
public SaSsoClientModel setRegTime(long regTime) {
public SaSsoClientInfo setRegTime(long regTime) {
this.regTime = regTime;
return this;
}
@ -195,7 +195,7 @@ public class SaSsoClientModel implements Serializable {
* @param index 此账号有记录以来为第几次登录默认从0开始递增
* @return /
*/
public SaSsoClientModel setIndex(int index) {
public SaSsoClientInfo setIndex(int index) {
this.index = index;
return this;
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.sso.model;
/**
* Sa-Token SSO Model
*
* @author click33
* @since 1.38.0
*/
@Deprecated
public class SaSsoClientModel extends SaSsoClientInfo {
}

View File

@ -105,8 +105,9 @@ public class SaSsoServerProcessor {
return cfg.notLoginView.get();
}
// ---- 情况2在SSO认证中心已经登录需要重定向回 Client 而这又分为两种方式
String mode = req.getParam(paramName.mode, "");
String mode = req.getParam(paramName.mode, SaSsoConsts.MODE_TICKET);
String redirect = req.getParam(paramName.redirect);
String client = req.getParam(paramName.client);
// 方式1直接重定向回Client端 (mode=simple)
if(mode.equals(SaSsoConsts.MODE_SIMPLE)) {
@ -118,16 +119,15 @@ public class SaSsoServerProcessor {
}
return res.redirect(cfg.getHomeRoute());
}
ssoServerTemplate.checkRedirectUrl(redirect);
ssoServerTemplate.checkRedirectUrl(client, redirect);
return res.redirect(redirect);
} else {
// 方式2带着ticket参数重定向回Client端 (mode=ticket)
// 方式2带着 ticket 参数重定向回Client端 (mode=ticket)
// 校验提供的client是否为非法字符
String client = req.getParam(paramName.client);
if(SaSsoConsts.CLIENT_WILDCARD.equals(client)) {
throw new SaSsoException("无效 client 标识:" + client).setCode(SaSsoErrorCode.CODE_30013);
}
// if(SaSsoConsts.CLIENT_WILDCARD.equals(client)) {
// throw new SaSsoException("无效 client 标识:" + client).setCode(SaSsoErrorCode.CODE_30013);
// }
// redirect 为空则选择 homeRoute homeRoute 也为空则抛出异常
if(SaFoxUtil.isEmpty(redirect)) {

View File

@ -18,10 +18,11 @@ package cn.dev33.satoken.sso.template;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.sso.SaSsoManager;
import cn.dev33.satoken.sso.config.SaSsoClientModel;
import cn.dev33.satoken.sso.config.SaSsoServerConfig;
import cn.dev33.satoken.sso.error.SaSsoErrorCode;
import cn.dev33.satoken.sso.exception.SaSsoException;
import cn.dev33.satoken.sso.model.SaSsoClientModel;
import cn.dev33.satoken.sso.model.SaSsoClientInfo;
import cn.dev33.satoken.sso.util.SaSsoConsts;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
@ -239,18 +240,61 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
/**
* 获取所有允许的授权回调地址多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
* @return see note
*
* @param client /
* @return /
*/
public String getAllowUrl() {
// 默认从配置文件中返回
return getServerConfig().getAllowUrl();
// public String getAllowUrl(String client) {
// if(SaFoxUtil.isEmpty(client)) {
// return getServerConfig().getAllowUrl();
// } else {
// return getClientNotNull(client).getAllowUrl();
// }
// }
/**
* 获取应用信息无效 client 则抛出异常
*
* @param client /
* @return /
*/
public SaSsoClientModel getClientNotNull(String client) {
if(SaFoxUtil.isEmpty(client)) {
SaSsoServerConfig serverConfig = getServerConfig();
if(serverConfig.getAllowAnonClient()) {
SaSsoClientModel scm = new SaSsoClientModel();
scm.setAllowUrl(serverConfig.getAllowUrl());
scm.setIsSlo(serverConfig.getIsSlo());
return scm;
} else {
throw new SaSsoException("client 标识不可为空");
}
} else {
SaSsoClientModel scm = getClient(client);
if(scm == null) {
throw new SaSsoException("未能获取应用信息client=" + client).setCode(SaSsoErrorCode.CODE_30013);
}
return scm;
}
}
/**
* 获取应用信息无效 client 返回 null
*
* @param client /
* @return /
*/
public SaSsoClientModel getClient(String client) {
return getServerConfig().getClients().get(client);
}
/**
* 校验重定向url合法性
*
* @param client 应用标识
* @param url 下放ticket的url地址
*/
public void checkRedirectUrl(String url) {
public void checkRedirectUrl(String client, String url) {
// 1是否是一个有效的url
if( ! SaFoxUtil.isUrl(url) ) {
@ -289,7 +333,8 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
}
// 4判断是否在 [ 允许的地址列表 ] 之中
List<String> allowUrlList = Arrays.asList(getAllowUrl().replaceAll(" ", "").split(","));
String allowUrlString = getClientNotNull(client).getAllowUrl();
List<String> allowUrlList = Arrays.asList(allowUrlString.replaceAll(" ", "").split(","));
checkAllowUrlList(allowUrlList);
if( ! SaStrategy.instance.hasElement.apply(allowUrlList, url) ) {
throw new SaSsoException("非法redirect" + url).setCode(SaSsoErrorCode.CODE_30002);
@ -356,7 +401,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
}
// step.1 遍历通知 Client 端注销会话
List<SaSsoClientModel> scmList = session.get(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, ArrayList::new);
List<SaSsoClientInfo> scmList = session.get(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, ArrayList::new);
scmList.forEach(scm -> {
notifyClientLogout(loginId, scm, false);
});
@ -370,7 +415,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
* @param scmList /
* @return /
*/
public int calcNextIndex(List<SaSsoClientModel> scmList) {
public int calcNextIndex(List<SaSsoClientInfo> scmList) {
// 如果目前还没有任何登录记录则直接返回0
if(scmList == null || scmList.isEmpty()) {
return 0;
@ -403,10 +448,10 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
SaSession session = getStpLogic().getSessionByLoginId(loginId);
// 取出原来的
List<SaSsoClientModel> scmList = session.get(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, ArrayList::new);
List<SaSsoClientInfo> scmList = session.get(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, ArrayList::new);
// 新登录client 加入到集合中
SaSsoClientModel scm = new SaSsoClientModel(client, sloCallbackUrl, calcNextIndex(scmList));
SaSsoClientInfo scm = new SaSsoClientInfo(client, sloCallbackUrl, calcNextIndex(scmList));
scmList.add(scm);
// 如果登录的client数量超过了限制则从最早的一个登录开始清退
@ -414,7 +459,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
if(maxRegClient != -1) {
for (;;) {
if(scmList.size() > maxRegClient) {
SaSsoClientModel removeScm = scmList.remove(0);
SaSsoClientInfo removeScm = scmList.remove(0);
notifyClientLogout(loginId, removeScm, true);
} else {
break;
@ -432,7 +477,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
* @param scm 客户端信息对象
* @param autoLogout 是否为超过 maxRegClient 的自动注销
*/
public void notifyClientLogout(Object loginId, SaSsoClientModel scm, boolean autoLogout) {
public void notifyClientLogout(Object loginId, SaSsoClientInfo scm, boolean autoLogout) {
// 如果给个null值不进行任何操作
if(scm == null || scm.mode != SaSsoConsts.SSO_MODE_3) {
@ -471,7 +516,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
public String buildRedirectUrl(Object loginId, String client, String redirect) {
// 校验 重定向地址 是否合法
checkRedirectUrl(redirect);
checkRedirectUrl(client, redirect);
// 删掉 旧Ticket
deleteTicket(getTicketValue(loginId));
@ -510,6 +555,13 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
}
// ---------------------- 重构 ----------------------
// ------------------- 返回相应key -------------------
/**

View File

@ -95,20 +95,24 @@ public class SaSsoUtil {
return SaSsoServerProcessor.instance.ssoServerTemplate.checkTicket(ticket, client);
}
/**
* 获取所有允许的授权回调地址多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
* @return see note
*/
public static String getAllowUrl() {
return SaSsoServerProcessor.instance.ssoServerTemplate.getAllowUrl();
}
// /**
// * 获取所有允许的授权回调地址多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
// *
// * @param client 应用标识
// * @return /
// */
// public static String getAllowUrl(String client) {
// return SaSsoServerProcessor.instance.ssoServerTemplate.getAllowUrl(client);
// }
/**
* 校验重定向url合法性
* @param url 下放ticket的url地址
*
* @param client 应用标识
* @param url 下放ticket的url地址
*/
public static void checkRedirectUrl(String url) {
SaSsoServerProcessor.instance.ssoServerTemplate.checkRedirectUrl(url);
public static void checkRedirectUrl(String client, String url) {
SaSsoServerProcessor.instance.ssoServerTemplate.checkRedirectUrl(client, url);
}