mirror of
https://gitee.com/dromara/sa-token.git
synced 2025-12-26 22:25:39 +08:00
将 sso 模块从 core 包下拆分出来,并细分 sso 模块异常
This commit is contained in:
@@ -1,364 +0,0 @@
|
||||
package cn.dev33.satoken.config;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* Sa-Token SSO 单点登录模块 配置类 Model
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class SaSsoConfig implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -6541180061782004705L;
|
||||
|
||||
|
||||
// ----------------- Server端相关配置
|
||||
|
||||
/**
|
||||
* Ticket有效期 (单位: 秒)
|
||||
*/
|
||||
public long ticketTimeout = 60 * 5;
|
||||
|
||||
/**
|
||||
* 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
|
||||
*/
|
||||
public String allowUrl = "*";
|
||||
|
||||
/**
|
||||
* 是否打开单点注销功能
|
||||
*/
|
||||
public Boolean isSlo = true;
|
||||
|
||||
/**
|
||||
* 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo)
|
||||
*/
|
||||
public Boolean isHttp = false;
|
||||
|
||||
/**
|
||||
* 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
|
||||
*/
|
||||
public String secretkey;
|
||||
|
||||
|
||||
// ----------------- Client端相关配置
|
||||
|
||||
/**
|
||||
* 配置 Server 端单点登录授权地址
|
||||
*/
|
||||
public String authUrl;
|
||||
|
||||
/**
|
||||
* 是否打开单点注销功能
|
||||
*/
|
||||
// public Boolean isSlo = true; // 同上
|
||||
|
||||
/**
|
||||
* 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo)
|
||||
*/
|
||||
// public Boolean isHttp = false; // 同上
|
||||
|
||||
/**
|
||||
* 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
|
||||
*/
|
||||
// public String secretkey; // 同上
|
||||
|
||||
/**
|
||||
* 配置 Server 端的 ticket 校验地址
|
||||
*/
|
||||
public String checkTicketUrl;
|
||||
|
||||
/**
|
||||
* 配置 Server 端查询 userinfo 地址
|
||||
*/
|
||||
public String userinfoUrl;
|
||||
|
||||
/**
|
||||
* 配置 Server 端单点注销地址
|
||||
*/
|
||||
public String sloUrl;
|
||||
|
||||
/**
|
||||
* 配置当前 Client 端的单点注销回调URL (为空时自动获取)
|
||||
*/
|
||||
public String ssoLogoutCall;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @return Ticket有效期 (单位: 秒)
|
||||
*/
|
||||
public long getTicketTimeout() {
|
||||
return ticketTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ticketTimeout Ticket有效期 (单位: 秒)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setTicketTimeout(long ticketTimeout) {
|
||||
this.ticketTimeout = ticketTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
|
||||
*/
|
||||
public String getAllowUrl() {
|
||||
return allowUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param allowUrl 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setAllowUrl(String allowUrl) {
|
||||
this.allowUrl = allowUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否打开单点注销功能
|
||||
*/
|
||||
public Boolean getIsSlo() {
|
||||
return isSlo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isSlo 是否打开单点注销功能
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setIsSlo(Boolean isSlo) {
|
||||
this.isSlo = isSlo;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return isHttp 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo)
|
||||
*/
|
||||
public Boolean getIsHttp() {
|
||||
return isHttp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isHttp 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setIsHttp(Boolean isHttp) {
|
||||
this.isHttp = isHttp;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
|
||||
*/
|
||||
public String getSecretkey() {
|
||||
return secretkey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param secretkey 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setSecretkey(String secretkey) {
|
||||
this.secretkey = secretkey;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 配置的 Server 端单点登录授权地址
|
||||
*/
|
||||
public String getAuthUrl() {
|
||||
return authUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param authUrl 配置 Server 端单点登录授权地址
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setAuthUrl(String authUrl) {
|
||||
this.authUrl = authUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 配置的 Server 端的 ticket 校验地址
|
||||
*/
|
||||
public String getCheckTicketUrl() {
|
||||
return checkTicketUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param checkTicketUrl 配置 Server 端的 ticket 校验地址
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setCheckTicketUrl(String checkTicketUrl) {
|
||||
this.checkTicketUrl = checkTicketUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 配置的 Server 端查询 userinfo 地址
|
||||
*/
|
||||
public String getUserinfoUrl() {
|
||||
return userinfoUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param userinfoUrl 配置 Server 端查询 userinfo 地址
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setUserinfoUrl(String userinfoUrl) {
|
||||
this.userinfoUrl = userinfoUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 配置 Server 端单点注销地址
|
||||
*/
|
||||
public String getSloUrl() {
|
||||
return sloUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sloUrl 配置 Server 端单点注销地址
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setSloUrl(String sloUrl) {
|
||||
this.sloUrl = sloUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 配置当前 Client 端的单点注销回调URL (为空时自动获取)
|
||||
*/
|
||||
public String getSsoLogoutCall() {
|
||||
return ssoLogoutCall;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ssoLogoutCall 配置当前 Client 端的单点注销回调URL (为空时自动获取)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setSsoLogoutCall(String ssoLogoutCall) {
|
||||
this.ssoLogoutCall = ssoLogoutCall;
|
||||
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 + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* 以数组形式写入允许的授权回调地址
|
||||
* @param url 所有集合
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setAllow(String ...url) {
|
||||
this.allowUrl = SaFoxUtil.arrayJoin(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// -------------------- SaSsoHandle 所有回调函数 --------------------
|
||||
|
||||
|
||||
/**
|
||||
* SSO-Server端:未登录时返回的View
|
||||
*/
|
||||
public Supplier<Object> notLoginView = () -> "当前会话在SSO-Server认证中心尚未登录";
|
||||
|
||||
/**
|
||||
* SSO-Server端:登录函数
|
||||
*/
|
||||
public BiFunction<String, String, Object> doLoginHandle = (name, pwd) -> SaResult.error();
|
||||
|
||||
/**
|
||||
* SSO-Client端:自定义校验Ticket返回值的处理逻辑 (每次从认证中心获取校验Ticket的结果后调用)
|
||||
*/
|
||||
public BiFunction<Object, String, Object> ticketResultHandle = null;
|
||||
|
||||
/**
|
||||
* SSO-Client端:发送Http请求的处理函数
|
||||
*/
|
||||
public Function<String, Object> sendHttp = url -> {throw new SaTokenException("请配置Http处理器");};
|
||||
|
||||
|
||||
/**
|
||||
* @param notLoginView SSO-Server端:未登录时返回的View
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setNotLoginView(Supplier<Object> notLoginView) {
|
||||
this.notLoginView = notLoginView;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 函数 SSO-Server端:未登录时返回的View
|
||||
*/
|
||||
public Supplier<Object> getNotLoginView() {
|
||||
return notLoginView;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param doLoginHandle SSO-Server端:登录函数
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setDoLoginHandle(BiFunction<String, String, Object> doLoginHandle) {
|
||||
this.doLoginHandle = doLoginHandle;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 函数 SSO-Server端:登录函数
|
||||
*/
|
||||
public BiFunction<String, String, Object> getDoLoginHandle() {
|
||||
return doLoginHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ticketResultHandle SSO-Client端:自定义校验Ticket返回值的处理逻辑 (每次从认证中心获取校验Ticket的结果后调用)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setTicketResultHandle(BiFunction<Object, String, Object> ticketResultHandle) {
|
||||
this.ticketResultHandle = ticketResultHandle;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 函数 SSO-Client端:自定义校验Ticket返回值的处理逻辑 (每次从认证中心获取校验Ticket的结果后调用)
|
||||
*/
|
||||
public BiFunction<Object, String, Object> getTicketResultHandle() {
|
||||
return ticketResultHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sendHttp SSO-Client端:发送Http请求的处理函数
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setSendHttp(Function<String, Object> sendHttp) {
|
||||
this.sendHttp = sendHttp;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 函数 SSO-Client端:发送Http请求的处理函数
|
||||
*/
|
||||
public Function<String, Object> getSendHttp() {
|
||||
return sendHttp;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -35,7 +35,7 @@ public class SaTokenConfig implements Serializable {
|
||||
/**
|
||||
* 同一账号最大登录数量,-1代表不限 (只有在 isConcurrent=true, isShare=false 时此配置才有效)
|
||||
*/
|
||||
private int maxLoginCount = 10;
|
||||
private int maxLoginCount = 12;
|
||||
|
||||
/** 是否尝试从请求体里读取token */
|
||||
private Boolean isReadBody = true;
|
||||
@@ -93,11 +93,6 @@ public class SaTokenConfig implements Serializable {
|
||||
*/
|
||||
public SaCookieConfig cookie = new SaCookieConfig();
|
||||
|
||||
/**
|
||||
* SSO单点登录配置对象
|
||||
*/
|
||||
public SaSsoConfig sso = new SaSsoConfig();
|
||||
|
||||
|
||||
/**
|
||||
* @return token名称 (同时也是cookie名称)
|
||||
@@ -439,22 +434,6 @@ public class SaTokenConfig implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SSO单点登录配置对象
|
||||
*/
|
||||
public SaSsoConfig getSso() {
|
||||
return sso;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sso SSO单点登录配置对象
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTokenConfig setSso(SaSsoConfig sso) {
|
||||
this.sso = sso;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Cookie 全局配置对象
|
||||
*/
|
||||
@@ -495,7 +474,6 @@ public class SaTokenConfig implements Serializable {
|
||||
+ ", basic=" + basic
|
||||
+ ", currDomain=" + currDomain
|
||||
+ ", checkIdToken=" + checkIdToken
|
||||
+ ", sso=" + sso
|
||||
+ ", cookie=" + cookie
|
||||
+ "]";
|
||||
}
|
||||
|
||||
@@ -16,13 +16,13 @@ public class NotPermissionException extends SaTokenException {
|
||||
private static final long serialVersionUID = 6806129545290130141L;
|
||||
|
||||
/** 权限码 */
|
||||
private String code;
|
||||
private String permission;
|
||||
|
||||
/**
|
||||
* @return 获得权限码
|
||||
* @return 获得具体缺少的权限码
|
||||
*/
|
||||
public String getCode() {
|
||||
return code;
|
||||
public String getPermission() {
|
||||
return permission;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,14 +39,23 @@ public class NotPermissionException extends SaTokenException {
|
||||
return loginType;
|
||||
}
|
||||
|
||||
public NotPermissionException(String code) {
|
||||
this(code, StpUtil.stpLogic.loginType);
|
||||
public NotPermissionException(String permission) {
|
||||
this(permission, StpUtil.stpLogic.loginType);
|
||||
}
|
||||
|
||||
public NotPermissionException(String code, String loginType) {
|
||||
super("无此权限:" + code);
|
||||
this.code = code;
|
||||
public NotPermissionException(String permission, String loginType) {
|
||||
super("无此权限:" + permission);
|
||||
this.permission = permission;
|
||||
this.loginType = loginType;
|
||||
}
|
||||
|
||||
/**
|
||||
* <h1> 警告:自 v1.30+ 版本起,获取异常权限码由 getCode() 更改为 getPermission(),请及时更换!
|
||||
* @return 获得权限码
|
||||
*/
|
||||
@Deprecated
|
||||
public int getCode() {
|
||||
return super.getCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package cn.dev33.satoken.exception;
|
||||
|
||||
/**
|
||||
* 定义所有异常细分状态码
|
||||
*
|
||||
* @author kong
|
||||
* @date: 2022-4-25
|
||||
*/
|
||||
public class SaExceptionCode {
|
||||
|
||||
/** 代表未指定异常细分状态码 */
|
||||
public static final int CODE_UNDEFINED = -1;
|
||||
|
||||
}
|
||||
@@ -16,6 +16,22 @@ public class SaTokenException extends RuntimeException {
|
||||
*/
|
||||
private static final long serialVersionUID = 6806129545290130132L;
|
||||
|
||||
/**
|
||||
* 异常细分状态码
|
||||
*/
|
||||
private int code = SaExceptionCode.CODE_UNDEFINED;
|
||||
|
||||
/**
|
||||
* 构建一个异常
|
||||
*
|
||||
* @param code 异常细分状态码
|
||||
*/
|
||||
public SaTokenException(int code) {
|
||||
super();
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建一个异常
|
||||
*
|
||||
@@ -25,6 +41,17 @@ public class SaTokenException extends RuntimeException {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建一个异常
|
||||
*
|
||||
* @param code 异常细分状态码
|
||||
* @param message 异常信息
|
||||
*/
|
||||
public SaTokenException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建一个异常
|
||||
*
|
||||
@@ -44,6 +71,23 @@ public class SaTokenException extends RuntimeException {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 异常细分状态码
|
||||
*/
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入异常细分状态码
|
||||
* @param code 异常细分状态码
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTokenException setCode(int code) {
|
||||
this.code = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果flag==true,则抛出message异常
|
||||
* @param flag 标记
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
package cn.dev33.satoken.sso;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO模块相关常量
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class SaSsoConsts {
|
||||
|
||||
/**
|
||||
* 所有API接口
|
||||
* @author kong
|
||||
*/
|
||||
public static final class Api {
|
||||
|
||||
/** SSO-Server端:授权地址 */
|
||||
public static String ssoAuth = "/sso/auth";
|
||||
|
||||
/** SSO-Server端:RestAPI 登录接口 */
|
||||
public static String ssoDoLogin = "/sso/doLogin";
|
||||
|
||||
/** SSO-Server端:校验ticket 获取账号id */
|
||||
public static String ssoCheckTicket = "/sso/checkTicket";
|
||||
|
||||
/** SSO-Server端 (and Client端):单点注销地址 */
|
||||
public static String ssoLogout = "/sso/logout";
|
||||
|
||||
/** SSO-Client端:登录地址 */
|
||||
public static String ssoLogin = "/sso/login";
|
||||
|
||||
/** SSO-Client端:单点注销的回调 */
|
||||
public static String ssoLogoutCall = "/sso/logoutCall";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有参数名称
|
||||
* @author kong
|
||||
*/
|
||||
public static final class ParamName {
|
||||
|
||||
/** redirect参数名称 */
|
||||
public static String redirect = "redirect";
|
||||
|
||||
/** ticket参数名称 */
|
||||
public static String ticket = "ticket";
|
||||
|
||||
/** back参数名称 */
|
||||
public static String back = "back";
|
||||
|
||||
/** mode参数名称 */
|
||||
public static String mode = "mode";
|
||||
|
||||
/** loginId参数名称 */
|
||||
public static String loginId = "loginId";
|
||||
|
||||
/** secretkey参数名称 */
|
||||
public static String secretkey = "secretkey";
|
||||
|
||||
/** Client端单点注销时-回调URL 参数名称 */
|
||||
public static String ssoLogoutCall = "ssoLogoutCall";
|
||||
|
||||
public static String name = "name";
|
||||
public static String pwd = "pwd";
|
||||
|
||||
}
|
||||
|
||||
/** Client端单点注销回调URL的Set集合,存储在Session中使用的key */
|
||||
public static final String SLO_CALLBACK_SET_KEY = "SLO_CALLBACK_SET_KEY_";
|
||||
|
||||
/** 表示OK的返回结果 */
|
||||
public static final String OK = "ok";
|
||||
|
||||
/** 表示自己 */
|
||||
public static final String SELF = "self";
|
||||
|
||||
/** 表示简单模式(SSO模式一) */
|
||||
public static final String MODE_SIMPLE = "simple";
|
||||
|
||||
/** 表示ticket模式(SSO模式二和模式三) */
|
||||
public static final String MODE_TICKET = "ticket";
|
||||
|
||||
/** 表示请求没有得到任何有效处理 {msg: "not handle"} */
|
||||
public static final String NOT_HANDLE = "{\"msg\": \"not handle\"}";
|
||||
|
||||
}
|
||||
@@ -1,367 +0,0 @@
|
||||
package cn.dev33.satoken.sso;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.config.SaSsoConfig;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.context.model.SaResponse;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.sso.SaSsoConsts.Api;
|
||||
import cn.dev33.satoken.sso.SaSsoConsts.ParamName;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO 单点登录请求处理类封装
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class SaSsoHandle {
|
||||
|
||||
/**
|
||||
* 处理Server端所有请求
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object serverRequest() {
|
||||
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaSsoConfig cfg = SaManager.getConfig().getSso();
|
||||
|
||||
// ------------------ 路由分发 ------------------
|
||||
|
||||
// SSO-Server端:授权地址
|
||||
if(req.isPath(Api.ssoAuth)) {
|
||||
return ssoAuth();
|
||||
}
|
||||
|
||||
// SSO-Server端:RestAPI 登录接口
|
||||
if(req.isPath(Api.ssoDoLogin)) {
|
||||
return ssoDoLogin();
|
||||
}
|
||||
|
||||
// SSO-Server端:校验ticket 获取账号id
|
||||
if(req.isPath(Api.ssoCheckTicket) && cfg.getIsHttp()) {
|
||||
return ssoCheckTicket();
|
||||
}
|
||||
|
||||
// SSO-Server端:单点注销 [模式一] (不带loginId参数)
|
||||
if(req.isPath(Api.ssoLogout) && cfg.getIsSlo() && req.hasParam(ParamName.loginId) == false) {
|
||||
return ssoServerLogoutType1();
|
||||
}
|
||||
|
||||
// SSO-Server端:单点注销 [模式三] (带loginId参数)
|
||||
if(req.isPath(Api.ssoLogout) && cfg.getIsHttp() && cfg.getIsSlo() && req.hasParam(ParamName.loginId)) {
|
||||
return ssoServerLogout();
|
||||
}
|
||||
|
||||
// 默认返回
|
||||
return SaSsoConsts.NOT_HANDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* SSO-Server端:授权地址
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object ssoAuth() {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaResponse res = SaHolder.getResponse();
|
||||
SaSsoConfig cfg = SaManager.getConfig().getSso();
|
||||
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
|
||||
|
||||
// ---------- 此处有两种情况分开处理:
|
||||
// ---- 情况1:在SSO认证中心尚未登录,需要先去登录
|
||||
if(stpLogic.isLogin() == false) {
|
||||
return cfg.getNotLoginView().get();
|
||||
}
|
||||
// ---- 情况2:在SSO认证中心已经登录,需要重定向回 Client 端,而这又分为两种方式:
|
||||
String mode = req.getParam(ParamName.mode, "");
|
||||
|
||||
// 方式1:直接重定向回Client端 (mode=simple)
|
||||
if(mode.equals(SaSsoConsts.MODE_SIMPLE)) {
|
||||
String redirect = req.getParam(ParamName.redirect);
|
||||
SaSsoUtil.checkRedirectUrl(redirect);
|
||||
return res.redirect(redirect);
|
||||
} else {
|
||||
// 方式2:带着ticket参数重定向回Client端 (mode=ticket)
|
||||
String redirectUrl = SaSsoUtil.buildRedirectUrl(stpLogic.getLoginId(), req.getParam(ParamName.redirect));
|
||||
return res.redirect(redirectUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SSO-Server端:RestAPI 登录接口
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object ssoDoLogin() {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaSsoConfig cfg = SaManager.getConfig().getSso();
|
||||
|
||||
// 处理
|
||||
return cfg.getDoLoginHandle().apply(req.getParam(ParamName.name), req.getParam(ParamName.pwd));
|
||||
}
|
||||
|
||||
/**
|
||||
* SSO-Server端:校验ticket 获取账号id
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object ssoCheckTicket() {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
|
||||
// 获取参数
|
||||
String ticket = req.getParam(ParamName.ticket);
|
||||
String sloCallback = req.getParam(ParamName.ssoLogoutCall);
|
||||
|
||||
// 校验ticket,获取对应的账号id
|
||||
Object loginId = SaSsoUtil.checkTicket(ticket);
|
||||
|
||||
// 注册此客户端的单点注销回调URL
|
||||
SaSsoUtil.registerSloCallbackUrl(loginId, sloCallback);
|
||||
|
||||
// 返回给Client端
|
||||
return loginId;
|
||||
}
|
||||
|
||||
/**
|
||||
* SSO-Server端:单点注销 [模式一]
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object ssoServerLogoutType1() {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaResponse res = SaHolder.getResponse();
|
||||
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
|
||||
|
||||
// 开始处理
|
||||
stpLogic.logout();
|
||||
|
||||
// 返回
|
||||
return ssoLogoutBack(req, res);
|
||||
}
|
||||
|
||||
/**
|
||||
* SSO-Server端:单点注销 [模式三]
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object ssoServerLogout() {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaSsoConfig cfg = SaManager.getConfig().getSso();
|
||||
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
|
||||
|
||||
// 获取参数
|
||||
String loginId = req.getParam(ParamName.loginId);
|
||||
String secretkey = req.getParam(ParamName.secretkey);
|
||||
|
||||
// 遍历通知Client端注销会话
|
||||
// step.1 校验秘钥
|
||||
SaSsoUtil.checkSecretkey(secretkey);
|
||||
|
||||
// step.2 遍历通知Client端注销会话
|
||||
SaSsoUtil.forEachSloUrl(loginId, url -> cfg.getSendHttp().apply(url));
|
||||
|
||||
// step.3 Server端注销
|
||||
stpLogic.logout(loginId);
|
||||
|
||||
// 完成
|
||||
return SaSsoConsts.OK;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理Client端所有请求
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object clientRequest() {
|
||||
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaSsoConfig cfg = SaManager.getConfig().getSso();
|
||||
|
||||
// ------------------ 路由分发 ------------------
|
||||
|
||||
// ---------- SSO-Client端:登录地址
|
||||
if(req.isPath(Api.ssoLogin)) {
|
||||
return ssoLogin();
|
||||
}
|
||||
|
||||
// ---------- SSO-Client端:单点注销 [模式二]
|
||||
if(req.isPath(Api.ssoLogout) && cfg.getIsSlo() && cfg.getIsHttp() == false) {
|
||||
return ssoLogoutType2();
|
||||
}
|
||||
|
||||
// ---------- SSO-Client端:单点注销 [模式三]
|
||||
if(req.isPath(Api.ssoLogout) && cfg.getIsSlo() && cfg.getIsHttp()) {
|
||||
return ssoLogoutType3();
|
||||
}
|
||||
|
||||
// ---------- SSO-Client端:单点注销的回调 [模式三]
|
||||
if(req.isPath(Api.ssoLogoutCall) && cfg.getIsSlo() && cfg.getIsHttp()) {
|
||||
return ssoLogoutCall();
|
||||
}
|
||||
|
||||
// 默认返回
|
||||
return SaSsoConsts.NOT_HANDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* SSO-Client端:登录地址
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object ssoLogin() {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaResponse res = SaHolder.getResponse();
|
||||
SaSsoConfig cfg = SaManager.getConfig().getSso();
|
||||
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
|
||||
|
||||
// 获取参数
|
||||
String back = req.getParam(ParamName.back, "/");
|
||||
String ticket = req.getParam(ParamName.ticket);
|
||||
|
||||
// 如果当前Client端已经登录,则无需访问SSO认证中心,可以直接返回
|
||||
if(stpLogic.isLogin()) {
|
||||
return res.redirect(back);
|
||||
}
|
||||
/*
|
||||
* 此时有两种情况:
|
||||
* 情况1:ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心
|
||||
* 情况2:ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录
|
||||
*/
|
||||
if(ticket == null) {
|
||||
String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(SaHolder.getRequest().getUrl(), back);
|
||||
return res.redirect(serverAuthUrl);
|
||||
} else {
|
||||
// ------- 1、校验ticket,获取账号id
|
||||
Object loginId = checkTicket(ticket, Api.ssoLogin);
|
||||
|
||||
// Be: 如果开发者自定义了处理逻辑
|
||||
if(cfg.getTicketResultHandle() != null) {
|
||||
return cfg.getTicketResultHandle().apply(loginId, back);
|
||||
}
|
||||
// ------- 2、如果loginId有值,说明ticket有效,进行登录并重定向至back地址
|
||||
if(loginId != null ) {
|
||||
stpLogic.login(loginId);
|
||||
return res.redirect(back);
|
||||
} else {
|
||||
// 如果ticket无效:
|
||||
throw new SaTokenException("无效ticket:" + ticket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SSO-Client端:单点注销 [模式二]
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object ssoLogoutType2() {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaResponse res = SaHolder.getResponse();
|
||||
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
|
||||
|
||||
// 开始处理
|
||||
stpLogic.logout();
|
||||
|
||||
// 返回
|
||||
return ssoLogoutBack(req, res);
|
||||
}
|
||||
|
||||
/**
|
||||
* SSO-Client端:单点注销 [模式三]
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object ssoLogoutType3() {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaResponse res = SaHolder.getResponse();
|
||||
SaSsoConfig cfg = SaManager.getConfig().getSso();
|
||||
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
|
||||
|
||||
// 如果未登录,则无需注销
|
||||
if(stpLogic.isLogin() == false) {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 调用SSO-Server认证中心API,进行注销
|
||||
String url = SaSsoUtil.buildSloUrl(stpLogic.getLoginId());
|
||||
String body = String.valueOf(cfg.getSendHttp().apply(url));
|
||||
if(SaSsoConsts.OK.equals(body) == false) {
|
||||
return SaResult.error("单点注销失败");
|
||||
}
|
||||
|
||||
// 返回
|
||||
return ssoLogoutBack(req, res);
|
||||
}
|
||||
|
||||
/**
|
||||
* SSO-Client端:单点注销的回调 [模式三]
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object ssoLogoutCall() {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
|
||||
|
||||
// 获取参数
|
||||
String loginId = req.getParam(ParamName.loginId);
|
||||
String secretkey = req.getParam(ParamName.secretkey);
|
||||
|
||||
SaSsoUtil.checkSecretkey(secretkey);
|
||||
stpLogic.logout(loginId);
|
||||
return SaSsoConsts.OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装:单点注销成功后返回结果
|
||||
* @param req SaRequest对象
|
||||
* @param res SaResponse对象
|
||||
* @return 返回结果
|
||||
*/
|
||||
public static Object ssoLogoutBack(SaRequest req, SaResponse res) {
|
||||
/*
|
||||
* 三种情况:
|
||||
* 1. 有back参数,值为SELF -> 回退一级并刷新
|
||||
* 2. 有back参数,值为url -> 跳转到此url地址
|
||||
* 3. 无back参数 -> 返回json数据
|
||||
*/
|
||||
String back = req.getParam(ParamName.back);
|
||||
if(SaFoxUtil.isNotEmpty(back)) {
|
||||
if(back.equals(SaSsoConsts.SELF)) {
|
||||
return "<script>if(document.referrer != location.href){ location.replace(document.referrer || '/'); }</script>";
|
||||
}
|
||||
return res.redirect(back);
|
||||
} else {
|
||||
return SaResult.ok("单点注销成功");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装:校验ticket,取出loginId
|
||||
* @param ticket ticket码
|
||||
* @param currUri 当前路由的uri,用于计算单点注销回调地址
|
||||
* @return loginId
|
||||
*/
|
||||
public static Object checkTicket(String ticket, String currUri) {
|
||||
SaSsoConfig cfg = SaManager.getConfig().getSso();
|
||||
// --------- 两种模式
|
||||
if(cfg.getIsHttp()) {
|
||||
// 模式三:使用http请求校验ticket
|
||||
String ssoLogoutCall = null;
|
||||
if(cfg.getIsSlo()) {
|
||||
ssoLogoutCall = SaHolder.getRequest().getUrl().replace(currUri, Api.ssoLogoutCall);
|
||||
}
|
||||
String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, ssoLogoutCall);
|
||||
Object body = cfg.getSendHttp().apply(checkUrl);
|
||||
return (SaFoxUtil.isEmpty(body) ? null : body);
|
||||
} else {
|
||||
// 模式二:直连Redis校验ticket
|
||||
return SaSsoUtil.checkTicket(ticket);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,430 +0,0 @@
|
||||
package cn.dev33.satoken.sso;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.config.SaSsoConfig;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.sso.SaSsoConsts.ParamName;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO 单点登录模块
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class SaSsoTemplate {
|
||||
|
||||
/**
|
||||
* 单点登录模块使用的 StpLogic 对象
|
||||
*/
|
||||
public StpLogic stpLogic;
|
||||
public SaSsoTemplate(StpLogic stpLogic) {
|
||||
this.stpLogic = stpLogic;
|
||||
}
|
||||
|
||||
// ---------------------- Ticket 操作 ----------------------
|
||||
|
||||
/**
|
||||
* 根据 账号id 创建一个 Ticket码
|
||||
* @param loginId 账号id
|
||||
* @return Ticket码
|
||||
*/
|
||||
public String createTicket(Object loginId) {
|
||||
// 创建 Ticket
|
||||
String ticket = randomTicket(loginId);
|
||||
|
||||
// 保存 Ticket
|
||||
saveTicket(ticket, loginId);
|
||||
saveTicketIndex(ticket, loginId);
|
||||
|
||||
// 返回 Ticket
|
||||
return ticket;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存 Ticket
|
||||
* @param ticket ticket码
|
||||
* @param loginId 账号id
|
||||
*/
|
||||
public void saveTicket(String ticket, Object loginId) {
|
||||
long ticketTimeout = SaManager.getConfig().getSso().getTicketTimeout();
|
||||
SaManager.getSaTokenDao().set(splicingTicketSaveKey(ticket), String.valueOf(loginId), ticketTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存 Ticket 索引
|
||||
* @param ticket ticket码
|
||||
* @param loginId 账号id
|
||||
*/
|
||||
public void saveTicketIndex(String ticket, Object loginId) {
|
||||
long ticketTimeout = SaManager.getConfig().getSso().getTicketTimeout();
|
||||
SaManager.getSaTokenDao().set(splicingTicketIndexKey(loginId), String.valueOf(ticket), ticketTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 Ticket
|
||||
* @param ticket Ticket码
|
||||
*/
|
||||
public void deleteTicket(String ticket) {
|
||||
if(ticket == null) {
|
||||
return;
|
||||
}
|
||||
SaManager.getSaTokenDao().delete(splicingTicketSaveKey(ticket));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 Ticket索引
|
||||
* @param loginId 账号id
|
||||
*/
|
||||
public void deleteTicketIndex(Object loginId) {
|
||||
if(loginId == null) {
|
||||
return;
|
||||
}
|
||||
SaManager.getSaTokenDao().delete(splicingTicketIndexKey(loginId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 Ticket码 获取账号id,如果Ticket码无效则返回null
|
||||
* @param ticket Ticket码
|
||||
* @return 账号id
|
||||
*/
|
||||
public Object getLoginId(String ticket) {
|
||||
if(SaFoxUtil.isEmpty(ticket)) {
|
||||
return null;
|
||||
}
|
||||
return SaManager.getSaTokenDao().get(splicingTicketSaveKey(ticket));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 Ticket码 获取账号id,并转换为指定类型
|
||||
* @param <T> 要转换的类型
|
||||
* @param ticket Ticket码
|
||||
* @param cs 要转换的类型
|
||||
* @return 账号id
|
||||
*/
|
||||
public <T> T getLoginId(String ticket, Class<T> cs) {
|
||||
return SaFoxUtil.getValueByType(getLoginId(ticket), cs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 指定账号id的 Ticket值
|
||||
* @param loginId 账号id
|
||||
* @return Ticket值
|
||||
*/
|
||||
public String getTicketValue(Object loginId) {
|
||||
if(loginId == null) {
|
||||
return null;
|
||||
}
|
||||
return SaManager.getSaTokenDao().get(splicingTicketIndexKey(loginId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验ticket码,获取账号id,如果此ticket是有效的,则立即删除
|
||||
* @param ticket Ticket码
|
||||
* @return 账号id
|
||||
*/
|
||||
public Object checkTicket(String ticket) {
|
||||
Object loginId = getLoginId(ticket);
|
||||
if(loginId != null) {
|
||||
deleteTicket(ticket);
|
||||
deleteTicketIndex(loginId);
|
||||
}
|
||||
return loginId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机一个 Ticket码
|
||||
* @param loginId 账号id
|
||||
* @return Ticket码
|
||||
*/
|
||||
public String randomTicket(Object loginId) {
|
||||
return SaFoxUtil.getRandomString(64);
|
||||
}
|
||||
|
||||
|
||||
// ---------------------- 构建URL ----------------------
|
||||
|
||||
/**
|
||||
* 构建URL:Server端 单点登录地址
|
||||
* @param clientLoginUrl Client端登录地址
|
||||
* @param back 回调路径
|
||||
* @return [SSO-Server端-认证地址 ]
|
||||
*/
|
||||
public String buildServerAuthUrl(String clientLoginUrl, String back) {
|
||||
|
||||
// 服务端认证地址
|
||||
String serverUrl = SaManager.getConfig().getSso().getAuthUrl();
|
||||
|
||||
// 对back地址编码
|
||||
back = (back == null ? "" : back);
|
||||
back = SaFoxUtil.encodeUrl(back);
|
||||
|
||||
// 开始拼接 sso 统一认证地址,形如:serverAuthUrl = http://xxx.com?redirectUrl=xxx.com?back=xxx.com
|
||||
|
||||
/*
|
||||
* 部分 Servlet 版本 request.getRequestURL() 返回的 url 带有 query 参数,形如:http://domain.com?id=1,
|
||||
* 如果不加判断会造成最终生成的 serverAuthUrl 带有双 back 参数 ,这个 if 判断正是为了解决此问题
|
||||
*/
|
||||
if(clientLoginUrl.indexOf(ParamName.back + "=" + back) == -1) {
|
||||
clientLoginUrl = SaFoxUtil.joinParam(clientLoginUrl, ParamName.back, back);
|
||||
}
|
||||
String serverAuthUrl = SaFoxUtil.joinParam(serverUrl, ParamName.redirect, clientLoginUrl);
|
||||
|
||||
// 返回
|
||||
return serverAuthUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL:Server端向Client下放ticke的地址
|
||||
* @param loginId 账号id
|
||||
* @param redirect Client端提供的重定向地址
|
||||
* @return see note
|
||||
*/
|
||||
public String buildRedirectUrl(Object loginId, String redirect) {
|
||||
|
||||
// 校验 重定向地址 是否合法
|
||||
checkRedirectUrl(redirect);
|
||||
|
||||
// 删掉 旧Ticket
|
||||
deleteTicket(getTicketValue(loginId));
|
||||
|
||||
// 创建 新Ticket
|
||||
String ticket = createTicket(loginId);
|
||||
|
||||
// 构建 授权重定向地址 (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 SaTokenException("无效redirect:" + url);
|
||||
}
|
||||
|
||||
// 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 SaTokenException("非法redirect:" + url);
|
||||
}
|
||||
|
||||
// 校验通过 √
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取:所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
|
||||
* @return see note
|
||||
*/
|
||||
public String getAllowUrl() {
|
||||
// 默认从配置文件中返回
|
||||
return SaManager.getConfig().getSso().getAllowUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对url中的back参数进行URL编码, 解决超链接重定向后参数丢失的bug
|
||||
* @param url url
|
||||
* @return 编码过后的url
|
||||
*/
|
||||
public String encodeBackParam(String url) {
|
||||
|
||||
// 获取back参数所在位置
|
||||
int index = url.indexOf("?" + ParamName.back + "=");
|
||||
if(index == -1) {
|
||||
index = url.indexOf("&" + ParamName.back + "=");
|
||||
if(index == -1) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
// 开始编码
|
||||
int length = ParamName.back.length() + 2;
|
||||
String back = url.substring(index + length);
|
||||
back = SaFoxUtil.encodeUrl(back);
|
||||
|
||||
// 放回url中
|
||||
url = url.substring(0, index + length) + back;
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL:Server端 账号资料查询地址
|
||||
* @param loginId 账号id
|
||||
* @return Server端 账号资料查询地址
|
||||
*/
|
||||
public String buildUserinfoUrl(Object loginId) {
|
||||
// 拼接
|
||||
String userinfoUrl = SaManager.getConfig().getSso().getUserinfoUrl();
|
||||
userinfoUrl = SaFoxUtil.joinParam(userinfoUrl, ParamName.loginId, loginId);
|
||||
userinfoUrl = SaFoxUtil.joinParam(userinfoUrl, ParamName.secretkey, SaManager.getConfig().getSso().getSecretkey());
|
||||
// 返回
|
||||
return userinfoUrl;
|
||||
}
|
||||
|
||||
|
||||
// ------------------- SSO 模式三相关 -------------------
|
||||
|
||||
/**
|
||||
* 校验secretkey秘钥是否有效
|
||||
* @param secretkey 秘钥
|
||||
*/
|
||||
public void checkSecretkey(String secretkey) {
|
||||
if(secretkey == null || secretkey.isEmpty() || secretkey.equals(SaManager.getConfig().getSso().getSecretkey()) == false) {
|
||||
throw new SaTokenException("无效秘钥:" + secretkey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL:校验ticket的URL
|
||||
* <p> 在模式三下,Client端拿到Ticket后根据此地址向Server端发送请求,获取账号id
|
||||
* @param ticket ticket码
|
||||
* @param ssoLogoutCallUrl 单点注销时的回调URL
|
||||
* @return 构建完毕的URL
|
||||
*/
|
||||
public String buildCheckTicketUrl(String ticket, String ssoLogoutCallUrl) {
|
||||
// 裸地址
|
||||
String url = SaManager.getConfig().getSso().getCheckTicketUrl();
|
||||
|
||||
// 拼接ticket参数
|
||||
url = SaFoxUtil.joinParam(url, ParamName.ticket, ticket);
|
||||
|
||||
// 拼接单点注销时的回调URL
|
||||
if(ssoLogoutCallUrl != null) {
|
||||
url = SaFoxUtil.joinParam(url, ParamName.ssoLogoutCall, ssoLogoutCallUrl);
|
||||
}
|
||||
|
||||
// 返回
|
||||
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 = SaManager.getConfig().getSso().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
|
||||
* @param loginId 要注销的账号id
|
||||
* @return 单点注销URL
|
||||
*/
|
||||
public String buildSloUrl(Object loginId) {
|
||||
SaSsoConfig ssoConfig = SaManager.getConfig().getSso();
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取:账号资料
|
||||
* @param loginId 账号id
|
||||
* @return 账号资料
|
||||
*/
|
||||
public Object getUserinfo(Object loginId) {
|
||||
String url = buildUserinfoUrl(loginId);
|
||||
return SaManager.getConfig().getSso().getSendHttp().apply(url);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ------------------- 返回相应key -------------------
|
||||
|
||||
/**
|
||||
* 拼接key:Ticket 查 账号Id
|
||||
* @param ticket ticket值
|
||||
* @return key
|
||||
*/
|
||||
public String splicingTicketSaveKey(String ticket) {
|
||||
return SaManager.getConfig().getTokenName() + ":ticket:" + ticket;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接key:账号Id 反查 Ticket
|
||||
* @param id 账号id
|
||||
* @return key
|
||||
*/
|
||||
public String splicingTicketIndexKey(Object id) {
|
||||
return SaManager.getConfig().getTokenName() + ":id-ticket:" + id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 单点注销回调函数
|
||||
* @author kong
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public static interface CallSloUrlFunction{
|
||||
/**
|
||||
* 调用function
|
||||
* @param url 注销回调URL
|
||||
*/
|
||||
public void run(String url);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
package cn.dev33.satoken.sso;
|
||||
|
||||
import cn.dev33.satoken.sso.SaSsoTemplate.CallSloUrlFunction;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO 单点登录模块 工具类
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class SaSsoUtil {
|
||||
|
||||
/**
|
||||
* 底层 SaSsoTemplate 对象
|
||||
*/
|
||||
public static SaSsoTemplate saSsoTemplate = new SaSsoTemplate(StpUtil.stpLogic);
|
||||
|
||||
|
||||
// ---------------------- Ticket 操作 ----------------------
|
||||
|
||||
/**
|
||||
* 根据 账号id 创建一个 Ticket码
|
||||
* @param loginId 账号id
|
||||
* @return Ticket码
|
||||
*/
|
||||
public static String createTicket(Object loginId) {
|
||||
return saSsoTemplate.createTicket(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 Ticket
|
||||
* @param ticket Ticket码
|
||||
*/
|
||||
public static void deleteTicket(String ticket) {
|
||||
saSsoTemplate.deleteTicket(ticket);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 Ticket索引
|
||||
* @param loginId 账号id
|
||||
*/
|
||||
public static void deleteTicketIndex(Object loginId) {
|
||||
saSsoTemplate.deleteTicketIndex(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 Ticket码 获取账号id,如果Ticket码无效则返回null
|
||||
* @param ticket Ticket码
|
||||
* @return 账号id
|
||||
*/
|
||||
public static Object getLoginId(String ticket) {
|
||||
return saSsoTemplate.getLoginId(ticket);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 Ticket码 获取账号id,并转换为指定类型
|
||||
* @param <T> 要转换的类型
|
||||
* @param ticket Ticket码
|
||||
* @param cs 要转换的类型
|
||||
* @return 账号id
|
||||
*/
|
||||
public static <T> T getLoginId(String ticket, Class<T> cs) {
|
||||
return saSsoTemplate.getLoginId(ticket, cs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验ticket码,获取账号id,如果此ticket是有效的,则立即删除
|
||||
* @param ticket Ticket码
|
||||
* @return 账号id
|
||||
*/
|
||||
public static Object checkTicket(String ticket) {
|
||||
return saSsoTemplate.checkTicket(ticket);
|
||||
}
|
||||
|
||||
|
||||
// ---------------------- 构建URL ----------------------
|
||||
|
||||
/**
|
||||
* 构建URL:Server端 单点登录地址
|
||||
* @param clientLoginUrl Client端登录地址
|
||||
* @param back 回调路径
|
||||
* @return [SSO-Server端-认证地址 ]
|
||||
*/
|
||||
public static String buildServerAuthUrl(String clientLoginUrl, String back) {
|
||||
return saSsoTemplate.buildServerAuthUrl(clientLoginUrl, back);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL:Server端向Client下放ticke的地址
|
||||
* @param loginId 账号id
|
||||
* @param redirect Client端提供的重定向地址
|
||||
* @return see note
|
||||
*/
|
||||
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端 账号资料查询地址
|
||||
* @param loginId 账号id
|
||||
* @return Server端 账号资料查询地址
|
||||
*/
|
||||
public static String buildUserinfoUrl(Object loginId) {
|
||||
return saSsoTemplate.buildUserinfoUrl(loginId);
|
||||
}
|
||||
|
||||
// ------------------- SSO 模式三 -------------------
|
||||
|
||||
/**
|
||||
* 校验secretkey秘钥是否有效
|
||||
* @param secretkey 秘钥
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public static void registerSloCallbackUrl(Object loginId, String sloCallbackUrl) {
|
||||
saSsoTemplate.registerSloCallbackUrl(loginId, sloCallbackUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 循环调用Client端单点注销回调
|
||||
* @param loginId 账号id
|
||||
* @param fun 调用方法
|
||||
*/
|
||||
public static void forEachSloUrl(Object loginId, CallSloUrlFunction fun) {
|
||||
saSsoTemplate.forEachSloUrl(loginId, fun);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL:单点注销URL
|
||||
* @param loginId 要注销的账号id
|
||||
* @return 单点注销URL
|
||||
*/
|
||||
public static String buildSloUrl(Object loginId) {
|
||||
return saSsoTemplate.buildSloUrl(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定账号单点注销
|
||||
* @param secretkey 校验秘钥
|
||||
* @param loginId 指定账号
|
||||
* @param fun 调用方法
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -320,10 +320,7 @@ public class StpLogic {
|
||||
if(getConfigOfIsShare()) {
|
||||
tokenValue = getTokenValueByLoginId(id, loginModel.getDeviceOrDefault());
|
||||
} else {
|
||||
// 如果配置为不共享token,需要检查会话是否超出 max-login-count
|
||||
if(config.getMaxLoginCount() != -1) {
|
||||
logoutByRetainCount(id, null, config.getMaxLoginCount() - 1);
|
||||
}
|
||||
//
|
||||
}
|
||||
} else {
|
||||
// --- 如果不允许并发登录,则将这个账号的历史登录标记为:被顶下线
|
||||
@@ -354,6 +351,11 @@ public class StpLogic {
|
||||
|
||||
// $$ 通知监听器,账号xxx 登录成功
|
||||
SaManager.getSaTokenListener().doLogin(loginType, id, loginModel);
|
||||
|
||||
// 检查此账号会话数量是否超出最大值
|
||||
if(config.getMaxLoginCount() != -1) {
|
||||
logoutByMaxLoginCount(id, session, null, config.getMaxLoginCount());
|
||||
}
|
||||
|
||||
// 返回Token
|
||||
return tokenValue;
|
||||
@@ -399,28 +401,11 @@ public class StpLogic {
|
||||
* @param device 设备类型 (填null代表注销所有设备类型)
|
||||
*/
|
||||
public void logout(Object loginId, String device) {
|
||||
logoutByRetainCount(loginId, device, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话注销,根据账号id 和 设备类型 和 保留数量
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param device 设备类型 (填null代表注销所有设备类型)
|
||||
* @param retainCount 保留最近的n次登录
|
||||
*/
|
||||
public void logoutByRetainCount(Object loginId, String device, int retainCount) {
|
||||
SaSession session = getSessionByLoginId(loginId, false);
|
||||
if(session != null) {
|
||||
List<TokenSign> list = session.tokenSignListCopyByDevice(device);
|
||||
// 遍历操作
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
// 只操作前n条
|
||||
if(i >= list.size() - retainCount) {
|
||||
continue;
|
||||
}
|
||||
for (TokenSign tokenSign: session.tokenSignListCopyByDevice(device)) {
|
||||
// 清理: token签名、token最后活跃时间
|
||||
String tokenValue = list.get(i).getValue();
|
||||
String tokenValue = tokenSign.getValue();
|
||||
session.removeTokenSign(tokenValue);
|
||||
clearLastActivity(tokenValue);
|
||||
// 删除Token-Id映射 & 清除Token-Session
|
||||
@@ -433,6 +418,41 @@ public class StpLogic {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话注销,根据账号id 和 设备类型 和 最大同时在线数量
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param session 此账号的 Session 对象,可填写null,框架将自动获取
|
||||
* @param device 设备类型 (填null代表注销所有设备类型)
|
||||
* @param maxLoginCount 保留最近的几次登录
|
||||
*/
|
||||
public void logoutByMaxLoginCount(Object loginId, SaSession session, String device, int maxLoginCount) {
|
||||
if(session == null) {
|
||||
session = getSessionByLoginId(loginId, false);
|
||||
if(session == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
List<TokenSign> list = session.tokenSignListCopyByDevice(device);
|
||||
// 遍历操作
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
// 只操作前n条
|
||||
if(i >= list.size() - maxLoginCount) {
|
||||
continue;
|
||||
}
|
||||
// 清理: token签名、token最后活跃时间
|
||||
String tokenValue = list.get(i).getValue();
|
||||
session.removeTokenSign(tokenValue);
|
||||
clearLastActivity(tokenValue);
|
||||
// 删除Token-Id映射 & 清除Token-Session
|
||||
deleteTokenToIdMapping(tokenValue);
|
||||
deleteTokenSession(tokenValue);
|
||||
SaManager.getSaTokenListener().doLogout(loginType, loginId, tokenValue);
|
||||
}
|
||||
// 注销 Session
|
||||
session.logoutByTokenSignCountToZero();
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话注销,根据指定 Token
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user