mirror of
https://gitee.com/dromara/sa-token.git
synced 2025-09-18 17:48:03 +08:00
增加 ticket 的 client 锁定功能
This commit is contained in:
@@ -42,7 +42,12 @@ sa.ajax = function(url, data, successFn) {
|
|||||||
// ----------------------------------- 相关事件 -----------------------------------
|
// ----------------------------------- 相关事件 -----------------------------------
|
||||||
|
|
||||||
// 检查当前是否已经登录,如果已登录则直接开始跳转,如果未登录则等待用户输入账号密码
|
// 检查当前是否已经登录,如果已登录则直接开始跳转,如果未登录则等待用户输入账号密码
|
||||||
sa.ajax("/sso/getRedirectUrl", {redirect: getParam('redirect', ''), mode: getParam('mode', '')}, function(res) {
|
var pData = {
|
||||||
|
client: getParam('client', ''),
|
||||||
|
redirect: getParam('redirect', ''),
|
||||||
|
mode: getParam('mode', '')
|
||||||
|
};
|
||||||
|
sa.ajax("/sso/getRedirectUrl", pData, function(res) {
|
||||||
if(res.code == 200) {
|
if(res.code == 200) {
|
||||||
// 已登录,并且redirect地址有效,开始跳转
|
// 已登录,并且redirect地址有效,开始跳转
|
||||||
location.href = decodeURIComponent(res.data);
|
location.href = decodeURIComponent(res.data);
|
||||||
|
@@ -23,7 +23,7 @@ public class H5Controller {
|
|||||||
* 获取 redirectUrl
|
* 获取 redirectUrl
|
||||||
*/
|
*/
|
||||||
@RequestMapping("/sso/getRedirectUrl")
|
@RequestMapping("/sso/getRedirectUrl")
|
||||||
private Object getRedirectUrl(String redirect, String mode) {
|
private Object getRedirectUrl(String redirect, String mode, String client) {
|
||||||
// 未登录情况下,返回 code=401
|
// 未登录情况下,返回 code=401
|
||||||
if(StpUtil.isLogin() == false) {
|
if(StpUtil.isLogin() == false) {
|
||||||
return SaResult.code(401);
|
return SaResult.code(401);
|
||||||
@@ -35,7 +35,7 @@ public class H5Controller {
|
|||||||
return SaResult.data(redirect);
|
return SaResult.data(redirect);
|
||||||
} else {
|
} else {
|
||||||
// 模式二或模式三
|
// 模式二或模式三
|
||||||
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), redirect);
|
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), client, redirect);
|
||||||
return SaResult.data(redirectUrl);
|
return SaResult.data(redirectUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -158,6 +158,7 @@ SaToken 中的所有异常都是继承于 `SaTokenException` 的,也就是说
|
|||||||
| 30008 | http 请求调用 提供的 `sign` 无效 |
|
| 30008 | http 请求调用 提供的 `sign` 无效 |
|
||||||
| 30009 | 本地系统没有配置 `secretkey` 字段 |
|
| 30009 | 本地系统没有配置 `secretkey` 字段 |
|
||||||
| 30010 | 本地系统没有配置 http 请求处理器 |
|
| 30010 | 本地系统没有配置 http 请求处理器 |
|
||||||
|
| 30011 | 该 ticket 不属于当前 client |
|
||||||
|
|
||||||
|
|
||||||
#### sa-token-oauth2 相关:
|
#### sa-token-oauth2 相关:
|
||||||
|
@@ -51,6 +51,11 @@ public class SaSsoConfig implements Serializable {
|
|||||||
|
|
||||||
// ----------------- Client端相关配置
|
// ----------------- Client端相关配置
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前 Client 名称标识,用于和 ticket 码的互相锁定
|
||||||
|
*/
|
||||||
|
public String client;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 配置 Server 端单点登录授权地址
|
* 配置 Server 端单点登录授权地址
|
||||||
*/
|
*/
|
||||||
@@ -98,6 +103,8 @@ public class SaSsoConfig implements Serializable {
|
|||||||
|
|
||||||
// ----------------- 其它
|
// ----------------- 其它
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 接口调用时的时间戳允许的差距(单位:ms),-1代表不校验差距
|
* 接口调用时的时间戳允许的差距(单位:ms),-1代表不校验差距
|
||||||
*/
|
*/
|
||||||
@@ -186,6 +193,21 @@ public class SaSsoConfig implements Serializable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return 当前 Client 名称标识,用于和 ticket 码的互相锁定
|
||||||
|
*/
|
||||||
|
public String getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param client 当前 Client 名称标识,用于和 ticket 码的互相锁定
|
||||||
|
*/
|
||||||
|
public SaSsoConfig setClient(String client) {
|
||||||
|
this.client = client;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return 配置的 Server 端单点登录授权地址
|
* @return 配置的 Server 端单点登录授权地址
|
||||||
*/
|
*/
|
||||||
@@ -306,6 +328,7 @@ public class SaSsoConfig implements Serializable {
|
|||||||
+ ", isSlo=" + isSlo
|
+ ", isSlo=" + isSlo
|
||||||
+ ", isHttp=" + isHttp
|
+ ", isHttp=" + isHttp
|
||||||
+ ", secretkey=" + secretkey
|
+ ", secretkey=" + secretkey
|
||||||
|
+ ", client=" + client
|
||||||
+ ", authUrl=" + authUrl
|
+ ", authUrl=" + authUrl
|
||||||
+ ", checkTicketUrl=" + checkTicketUrl
|
+ ", checkTicketUrl=" + checkTicketUrl
|
||||||
+ ", userinfoUrl=" + userinfoUrl
|
+ ", userinfoUrl=" + userinfoUrl
|
||||||
|
@@ -1,411 +0,0 @@
|
|||||||
package cn.dev33.satoken.sso;
|
|
||||||
|
|
||||||
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.sso.error.SaSsoErrorCode;
|
|
||||||
import cn.dev33.satoken.sso.exception.SaSsoException;
|
|
||||||
import cn.dev33.satoken.sso.name.ApiName;
|
|
||||||
import cn.dev33.satoken.sso.name.ParamName;
|
|
||||||
import cn.dev33.satoken.stp.StpLogic;
|
|
||||||
import cn.dev33.satoken.util.SaFoxUtil;
|
|
||||||
import cn.dev33.satoken.util.SaResult;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sa-Token-SSO 单点登录请求处理类封装
|
|
||||||
* @author kong
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public class SaSsoHandle {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 所有 API 名称
|
|
||||||
*/
|
|
||||||
public static ApiName apiName = new ApiName();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 所有参数名称
|
|
||||||
*/
|
|
||||||
public static ParamName paramName = new ParamName();
|
|
||||||
|
|
||||||
|
|
||||||
// ----------- SSO-Server 端路由分发
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理Server端所有请求
|
|
||||||
* @return 处理结果
|
|
||||||
*/
|
|
||||||
public static Object serverRequest() {
|
|
||||||
|
|
||||||
// 获取对象
|
|
||||||
SaRequest req = SaHolder.getRequest();
|
|
||||||
SaSsoConfig cfg = SaSsoManager.getConfig();
|
|
||||||
|
|
||||||
// ------------------ 路由分发 ------------------
|
|
||||||
|
|
||||||
// SSO-Server端:授权地址
|
|
||||||
if(req.isPath(apiName.ssoAuth)) {
|
|
||||||
return ssoAuth();
|
|
||||||
}
|
|
||||||
|
|
||||||
// SSO-Server端:RestAPI 登录接口
|
|
||||||
if(req.isPath(apiName.ssoDoLogin)) {
|
|
||||||
return ssoDoLogin();
|
|
||||||
}
|
|
||||||
|
|
||||||
// SSO-Server端:校验ticket 获取账号id
|
|
||||||
if(req.isPath(apiName.ssoCheckTicket) && cfg.getIsHttp()) {
|
|
||||||
return ssoCheckTicket();
|
|
||||||
}
|
|
||||||
|
|
||||||
// SSO-Server端:单点注销 [用户访问式] (不带loginId参数)
|
|
||||||
if(req.isPath(apiName.ssoSignout) && cfg.getIsSlo() && req.hasParam(paramName.loginId) == false) {
|
|
||||||
return ssoLogoutByUserVisit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// SSO-Server端:单点注销 [Client调用式] (带loginId参数 & isHttp=true)
|
|
||||||
if(req.isPath(apiName.ssoSignout) && cfg.getIsHttp() && cfg.getIsSlo() && req.hasParam(paramName.loginId)) {
|
|
||||||
return ssoLogoutByClientHttp();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 默认返回
|
|
||||||
return SaSsoConsts.NOT_HANDLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SSO-Server端:授权地址
|
|
||||||
* @return 处理结果
|
|
||||||
*/
|
|
||||||
public static Object ssoAuth() {
|
|
||||||
// 获取对象
|
|
||||||
SaRequest req = SaHolder.getRequest();
|
|
||||||
SaResponse res = SaHolder.getResponse();
|
|
||||||
SaSsoConfig cfg = SaSsoManager.getConfig();
|
|
||||||
StpLogic stpLogic = SaSsoUtil.ssoTemplate.getStpLogic();
|
|
||||||
|
|
||||||
// ---------- 此处有两种情况分开处理:
|
|
||||||
// ---- 情况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 = SaSsoManager.getConfig();
|
|
||||||
|
|
||||||
// 处理
|
|
||||||
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.getParamNotNull(paramName.ticket);
|
|
||||||
String sloCallback = req.getParam(paramName.ssoLogoutCall);
|
|
||||||
|
|
||||||
// 校验ticket,获取 loginId
|
|
||||||
Object loginId = SaSsoUtil.checkTicket(ticket);
|
|
||||||
|
|
||||||
// 注册此客户端的单点注销回调URL
|
|
||||||
SaSsoUtil.registerSloCallbackUrl(loginId, sloCallback);
|
|
||||||
|
|
||||||
// 给 client 端响应结果
|
|
||||||
if(SaFoxUtil.isEmpty(loginId)) {
|
|
||||||
return SaResult.error("无效ticket:" + ticket);
|
|
||||||
} else {
|
|
||||||
return SaResult.data(loginId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SSO-Server端:单点注销 [用户访问式]
|
|
||||||
* @return 处理结果
|
|
||||||
*/
|
|
||||||
public static Object ssoLogoutByUserVisit() {
|
|
||||||
// 获取对象
|
|
||||||
SaRequest req = SaHolder.getRequest();
|
|
||||||
SaResponse res = SaHolder.getResponse();
|
|
||||||
Object loginId = SaSsoUtil.ssoTemplate.getStpLogic().getLoginIdDefaultNull();
|
|
||||||
|
|
||||||
// 单点注销
|
|
||||||
if(SaFoxUtil.isNotEmpty(loginId)) {
|
|
||||||
SaSsoUtil.ssoLogout(loginId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 完成
|
|
||||||
return ssoLogoutBack(req, res);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SSO-Server端:单点注销 [Client调用式]
|
|
||||||
* @return 处理结果
|
|
||||||
*/
|
|
||||||
public static Object ssoLogoutByClientHttp() {
|
|
||||||
// 获取参数
|
|
||||||
SaRequest req = SaHolder.getRequest();
|
|
||||||
String loginId = req.getParam(paramName.loginId);
|
|
||||||
|
|
||||||
// step.1 校验签名
|
|
||||||
SaSsoUtil.checkSign(req);
|
|
||||||
|
|
||||||
// step.2 单点注销
|
|
||||||
SaSsoUtil.ssoLogout(loginId);
|
|
||||||
|
|
||||||
// 响应
|
|
||||||
return SaResult.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ----------- SSO-Client 端路由分发
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理Client端所有请求
|
|
||||||
* @return 处理结果
|
|
||||||
*/
|
|
||||||
public static Object clientRequest() {
|
|
||||||
|
|
||||||
// 获取对象
|
|
||||||
SaRequest req = SaHolder.getRequest();
|
|
||||||
SaSsoConfig cfg = SaSsoManager.getConfig();
|
|
||||||
|
|
||||||
// ------------------ 路由分发 ------------------
|
|
||||||
|
|
||||||
// ---------- SSO-Client端:登录地址
|
|
||||||
if(req.isPath(apiName.ssoLogin)) {
|
|
||||||
return ssoLogin();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- SSO-Client端:单点注销 [模式二]
|
|
||||||
if(req.isPath(apiName.ssoLogout) && cfg.getIsSlo() && cfg.getIsHttp() == false) {
|
|
||||||
return ssoLogoutType2();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- SSO-Client端:单点注销 [模式三]
|
|
||||||
if(req.isPath(apiName.ssoLogout) && cfg.getIsSlo() && cfg.getIsHttp()) {
|
|
||||||
return ssoLogoutType3();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- SSO-Client端:单点注销的回调 [模式三]
|
|
||||||
if(req.isPath(apiName.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 = SaSsoManager.getConfig();
|
|
||||||
StpLogic stpLogic = SaSsoUtil.ssoTemplate.getStpLogic();
|
|
||||||
|
|
||||||
// 获取参数
|
|
||||||
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,获取 loginId
|
|
||||||
Object loginId = checkTicket(ticket, apiName.ssoLogin);
|
|
||||||
|
|
||||||
// Be: 如果开发者自定义了处理逻辑
|
|
||||||
if(cfg.getTicketResultHandle() != null) {
|
|
||||||
return cfg.getTicketResultHandle().apply(loginId, back);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------- 2、如果 loginId 无值,说明 ticket 无效
|
|
||||||
if(SaFoxUtil.isEmpty(loginId)) {
|
|
||||||
throw new SaSsoException("无效ticket:" + ticket).setCode(SaSsoErrorCode.CODE_30004);
|
|
||||||
} else {
|
|
||||||
// 3、如果 loginId 有值,说明 ticket 有效,此时进行登录并重定向至back地址
|
|
||||||
stpLogic.login(loginId);
|
|
||||||
return res.redirect(back);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SSO-Client端:单点注销 [模式二]
|
|
||||||
* @return 处理结果
|
|
||||||
*/
|
|
||||||
public static Object ssoLogoutType2() {
|
|
||||||
// 获取对象
|
|
||||||
SaRequest req = SaHolder.getRequest();
|
|
||||||
SaResponse res = SaHolder.getResponse();
|
|
||||||
StpLogic stpLogic = SaSsoUtil.ssoTemplate.getStpLogic();
|
|
||||||
|
|
||||||
// 开始处理
|
|
||||||
stpLogic.logout();
|
|
||||||
|
|
||||||
// 返回
|
|
||||||
return ssoLogoutBack(req, res);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SSO-Client端:单点注销 [模式三]
|
|
||||||
* @return 处理结果
|
|
||||||
*/
|
|
||||||
public static Object ssoLogoutType3() {
|
|
||||||
// 获取对象
|
|
||||||
SaRequest req = SaHolder.getRequest();
|
|
||||||
SaResponse res = SaHolder.getResponse();
|
|
||||||
StpLogic stpLogic = SaSsoUtil.ssoTemplate.getStpLogic();
|
|
||||||
|
|
||||||
// 如果未登录,则无需注销
|
|
||||||
if(stpLogic.isLogin() == false) {
|
|
||||||
return SaResult.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调用 sso-server 认证中心单点注销API
|
|
||||||
String url = SaSsoUtil.buildSloUrl(stpLogic.getLoginId());
|
|
||||||
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 回应的消息作为异常抛出
|
|
||||||
throw new SaSsoException(result.getMsg()).setCode(SaSsoErrorCode.CODE_30006);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SSO-Client端:单点注销的回调 [模式三]
|
|
||||||
* @return 处理结果
|
|
||||||
*/
|
|
||||||
public static Object ssoLogoutCall() {
|
|
||||||
// 获取对象
|
|
||||||
SaRequest req = SaHolder.getRequest();
|
|
||||||
StpLogic stpLogic = SaSsoUtil.ssoTemplate.getStpLogic();
|
|
||||||
|
|
||||||
// 获取参数
|
|
||||||
String loginId = req.getParamNotNull(paramName.loginId);
|
|
||||||
|
|
||||||
// 注销当前应用端会话
|
|
||||||
SaSsoUtil.checkSign(req);
|
|
||||||
stpLogic.logout(loginId);
|
|
||||||
|
|
||||||
// 响应
|
|
||||||
return SaResult.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 = SaSsoManager.getConfig();
|
|
||||||
|
|
||||||
// --------- 两种模式
|
|
||||||
if(cfg.getIsHttp()) {
|
|
||||||
// q1、使用模式三:使用 http 请求从认证中心校验ticket
|
|
||||||
|
|
||||||
// 计算当前 sso-client 的单点注销回调地址
|
|
||||||
String ssoLogoutCall = null;
|
|
||||||
if(cfg.getIsSlo()) {
|
|
||||||
// 如果配置了回调地址,就使用配置的值:
|
|
||||||
if(SaFoxUtil.isNotEmpty(cfg.getSsoLogoutCall())) {
|
|
||||||
ssoLogoutCall = cfg.getSsoLogoutCall();
|
|
||||||
}
|
|
||||||
// 如果提供了当前 uri,则根据此值来计算:
|
|
||||||
else if(SaFoxUtil.isNotEmpty(currUri)) {
|
|
||||||
ssoLogoutCall = SaHolder.getRequest().getUrl().replace(currUri, apiName.ssoLogoutCall);
|
|
||||||
}
|
|
||||||
// 否则视为不注册单点注销回调地址
|
|
||||||
else {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发起请求
|
|
||||||
String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, ssoLogoutCall);
|
|
||||||
SaResult result = SaSsoUtil.request(checkUrl);
|
|
||||||
|
|
||||||
// 校验
|
|
||||||
if(result.getCode() == SaResult.CODE_SUCCESS) {
|
|
||||||
return result.getData();
|
|
||||||
} else {
|
|
||||||
// 将 sso-server 回应的消息作为异常抛出
|
|
||||||
throw new SaSsoException(result.getMsg()).setCode(SaSsoErrorCode.CODE_30005);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// q2、使用模式二:直连Redis校验ticket
|
|
||||||
return SaSsoUtil.checkTicket(ticket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -92,7 +92,7 @@ public class SaSsoProcessor {
|
|||||||
return res.redirect(redirect);
|
return res.redirect(redirect);
|
||||||
} else {
|
} else {
|
||||||
// 方式2:带着ticket参数重定向回Client端 (mode=ticket)
|
// 方式2:带着ticket参数重定向回Client端 (mode=ticket)
|
||||||
String redirectUrl = ssoTemplate.buildRedirectUrl(stpLogic.getLoginId(), req.getParam(paramName.redirect));
|
String redirectUrl = ssoTemplate.buildRedirectUrl(stpLogic.getLoginId(), req.getParam(paramName.client), req.getParam(paramName.redirect));
|
||||||
return res.redirect(redirectUrl);
|
return res.redirect(redirectUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,11 +120,12 @@ public class SaSsoProcessor {
|
|||||||
|
|
||||||
// 获取参数
|
// 获取参数
|
||||||
SaRequest req = SaHolder.getRequest();
|
SaRequest req = SaHolder.getRequest();
|
||||||
|
String client = req.getParam(paramName.client);
|
||||||
String ticket = req.getParamNotNull(paramName.ticket);
|
String ticket = req.getParamNotNull(paramName.ticket);
|
||||||
String sloCallback = req.getParam(paramName.ssoLogoutCall);
|
String sloCallback = req.getParam(paramName.ssoLogoutCall);
|
||||||
|
|
||||||
// 校验ticket,获取 loginId
|
// 校验ticket,获取 loginId
|
||||||
Object loginId = ssoTemplate.checkTicket(ticket);
|
Object loginId = ssoTemplate.checkTicket(ticket, client);
|
||||||
|
|
||||||
// 注册此客户端的单点注销回调URL
|
// 注册此客户端的单点注销回调URL
|
||||||
ssoTemplate.registerSloCallbackUrl(loginId, sloCallback);
|
ssoTemplate.registerSloCallbackUrl(loginId, sloCallback);
|
||||||
@@ -344,7 +345,7 @@ public class SaSsoProcessor {
|
|||||||
SaResult result = ssoTemplate.request(url);
|
SaResult result = ssoTemplate.request(url);
|
||||||
|
|
||||||
// 校验响应状态码
|
// 校验响应状态码
|
||||||
if(result.getCode() == SaResult.CODE_SUCCESS) {
|
if(SaResult.CODE_SUCCESS == result.getCode()) {
|
||||||
// 极端场景下,sso-server 中心的单点注销可能并不会通知到此 client 端,所以这里需要再补一刀
|
// 极端场景下,sso-server 中心的单点注销可能并不会通知到此 client 端,所以这里需要再补一刀
|
||||||
if(stpLogic.isLogin()) {
|
if(stpLogic.isLogin()) {
|
||||||
stpLogic.logout();
|
stpLogic.logout();
|
||||||
|
@@ -80,14 +80,15 @@ public class SaSsoTemplate {
|
|||||||
/**
|
/**
|
||||||
* 根据 账号id 创建一个 Ticket码
|
* 根据 账号id 创建一个 Ticket码
|
||||||
* @param loginId 账号id
|
* @param loginId 账号id
|
||||||
|
* @param client 客户端标识
|
||||||
* @return Ticket码
|
* @return Ticket码
|
||||||
*/
|
*/
|
||||||
public String createTicket(Object loginId) {
|
public String createTicket(Object loginId, String client) {
|
||||||
// 创建 Ticket
|
// 创建 Ticket
|
||||||
String ticket = randomTicket(loginId);
|
String ticket = randomTicket(loginId);
|
||||||
|
|
||||||
// 保存 Ticket
|
// 保存 Ticket
|
||||||
saveTicket(ticket, loginId);
|
saveTicket(ticket, loginId, client);
|
||||||
saveTicketIndex(ticket, loginId);
|
saveTicketIndex(ticket, loginId);
|
||||||
|
|
||||||
// 返回 Ticket
|
// 返回 Ticket
|
||||||
@@ -98,10 +99,15 @@ public class SaSsoTemplate {
|
|||||||
* 保存 Ticket
|
* 保存 Ticket
|
||||||
* @param ticket ticket码
|
* @param ticket ticket码
|
||||||
* @param loginId 账号id
|
* @param loginId 账号id
|
||||||
|
* @param client 客户端标识
|
||||||
*/
|
*/
|
||||||
public void saveTicket(String ticket, Object loginId) {
|
public void saveTicket(String ticket, Object loginId, String client) {
|
||||||
|
String value = String.valueOf(loginId);
|
||||||
|
if(SaFoxUtil.isNotEmpty(client)) {
|
||||||
|
value += "," + client;
|
||||||
|
}
|
||||||
long ticketTimeout = SaSsoManager.getConfig().getTicketTimeout();
|
long ticketTimeout = SaSsoManager.getConfig().getTicketTimeout();
|
||||||
SaManager.getSaTokenDao().set(splicingTicketSaveKey(ticket), String.valueOf(loginId), ticketTimeout);
|
SaManager.getSaTokenDao().set(splicingTicketSaveKey(ticket), value, ticketTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -145,7 +151,13 @@ public class SaSsoTemplate {
|
|||||||
if(SaFoxUtil.isEmpty(ticket)) {
|
if(SaFoxUtil.isEmpty(ticket)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return SaManager.getSaTokenDao().get(splicingTicketSaveKey(ticket));
|
String loginId = SaManager.getSaTokenDao().get(splicingTicketSaveKey(ticket));
|
||||||
|
// 如果是 "a,b" 的格式,则只取最前面的一项
|
||||||
|
if(loginId != null && loginId.indexOf(",") > -1) {
|
||||||
|
String[] arr = loginId.split(",");
|
||||||
|
loginId = arr[0];
|
||||||
|
}
|
||||||
|
return loginId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -172,16 +184,43 @@ public class SaSsoTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验ticket码,获取账号id,如果此ticket是有效的,则立即删除
|
* 校验 Ticket 码,获取账号id,如果此ticket是有效的,则立即删除
|
||||||
* @param ticket Ticket码
|
* @param ticket Ticket码
|
||||||
* @return 账号id
|
* @return 账号id
|
||||||
*/
|
*/
|
||||||
public Object checkTicket(String ticket) {
|
public Object checkTicket(String ticket) {
|
||||||
Object loginId = getLoginId(ticket);
|
return checkTicket(ticket, getSsoConfig().getClient());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验 Ticket 码,获取账号id,如果此ticket是有效的,则立即删除
|
||||||
|
* @param ticket Ticket码
|
||||||
|
* @param client client 标识
|
||||||
|
* @return 账号id
|
||||||
|
*/
|
||||||
|
public Object checkTicket(String ticket, String client) {
|
||||||
|
// 读取 loginId
|
||||||
|
String loginId = SaManager.getSaTokenDao().get(splicingTicketSaveKey(ticket));
|
||||||
|
|
||||||
if(loginId != null) {
|
if(loginId != null) {
|
||||||
|
// 如果是 "a,b" 的格式,则只取最前面的一项
|
||||||
|
if(loginId.indexOf(",") > -1) {
|
||||||
|
String[] arr = loginId.split(",");
|
||||||
|
loginId = arr[0];
|
||||||
|
|
||||||
|
// 如果指定了 client 标识,则校验一下 client 标识是否一致
|
||||||
|
if(SaFoxUtil.isNotEmpty(client) && SaFoxUtil.notEquals(client, arr[1])) {
|
||||||
|
throw new SaSsoException("该 ticket 不属于 client=" + client + ", ticket 值: " + ticket)
|
||||||
|
.setCode(SaSsoErrorCode.CODE_30011);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除 ticket 信息,使其只有一次性有效
|
||||||
deleteTicket(ticket);
|
deleteTicket(ticket);
|
||||||
deleteTicketIndex(loginId);
|
deleteTicketIndex(loginId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
return loginId;
|
return loginId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,6 +335,13 @@ public class SaSsoTemplate {
|
|||||||
// 服务端认证地址
|
// 服务端认证地址
|
||||||
String serverUrl = SaSsoManager.getConfig().splicingAuthUrl();
|
String serverUrl = SaSsoManager.getConfig().splicingAuthUrl();
|
||||||
|
|
||||||
|
// 拼接客户端标识
|
||||||
|
String client = SaSsoManager.getConfig().getClient();
|
||||||
|
if(SaFoxUtil.isNotEmpty(client)) {
|
||||||
|
serverUrl = SaFoxUtil.joinParam(serverUrl, paramName.client, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 对back地址编码
|
// 对back地址编码
|
||||||
back = (back == null ? "" : back);
|
back = (back == null ? "" : back);
|
||||||
back = SaFoxUtil.encodeUrl(back);
|
back = SaFoxUtil.encodeUrl(back);
|
||||||
@@ -318,10 +364,11 @@ public class SaSsoTemplate {
|
|||||||
/**
|
/**
|
||||||
* 构建URL:Server端向Client下放ticke的地址
|
* 构建URL:Server端向Client下放ticke的地址
|
||||||
* @param loginId 账号id
|
* @param loginId 账号id
|
||||||
|
* @param client 客户端标识
|
||||||
* @param redirect Client端提供的重定向地址
|
* @param redirect Client端提供的重定向地址
|
||||||
* @return see note
|
* @return see note
|
||||||
*/
|
*/
|
||||||
public String buildRedirectUrl(Object loginId, String redirect) {
|
public String buildRedirectUrl(Object loginId, String client, String redirect) {
|
||||||
|
|
||||||
// 校验 重定向地址 是否合法
|
// 校验 重定向地址 是否合法
|
||||||
checkRedirectUrl(redirect);
|
checkRedirectUrl(redirect);
|
||||||
@@ -330,7 +377,7 @@ public class SaSsoTemplate {
|
|||||||
deleteTicket(getTicketValue(loginId));
|
deleteTicket(getTicketValue(loginId));
|
||||||
|
|
||||||
// 创建 新Ticket
|
// 创建 新Ticket
|
||||||
String ticket = createTicket(loginId);
|
String ticket = createTicket(loginId, client);
|
||||||
|
|
||||||
// 构建 授权重定向地址 (Server端 根据此地址向 Client端 下放Ticket)
|
// 构建 授权重定向地址 (Server端 根据此地址向 Client端 下放Ticket)
|
||||||
return SaFoxUtil.joinParam(encodeBackParam(redirect), paramName.ticket, ticket);
|
return SaFoxUtil.joinParam(encodeBackParam(redirect), paramName.ticket, ticket);
|
||||||
@@ -383,6 +430,12 @@ public class SaSsoTemplate {
|
|||||||
// 裸地址
|
// 裸地址
|
||||||
String url = SaSsoManager.getConfig().splicingCheckTicketUrl();
|
String url = SaSsoManager.getConfig().splicingCheckTicketUrl();
|
||||||
|
|
||||||
|
// 拼接 client 参数
|
||||||
|
String client = getSsoConfig().getClient();
|
||||||
|
if(SaFoxUtil.isNotEmpty(client)) {
|
||||||
|
url = SaFoxUtil.joinParam(url, paramName.client, client);
|
||||||
|
}
|
||||||
|
|
||||||
// 拼接ticket参数
|
// 拼接ticket参数
|
||||||
url = SaFoxUtil.joinParam(url, paramName.ticket, ticket);
|
url = SaFoxUtil.joinParam(url, paramName.ticket, ticket);
|
||||||
|
|
||||||
|
@@ -21,10 +21,11 @@ public class SaSsoUtil {
|
|||||||
/**
|
/**
|
||||||
* 根据 账号id 创建一个 Ticket码
|
* 根据 账号id 创建一个 Ticket码
|
||||||
* @param loginId 账号id
|
* @param loginId 账号id
|
||||||
|
* @param client 客户端标识
|
||||||
* @return Ticket码
|
* @return Ticket码
|
||||||
*/
|
*/
|
||||||
public static String createTicket(Object loginId) {
|
public static String createTicket(Object loginId, String client) {
|
||||||
return ssoTemplate.createTicket(loginId);
|
return ssoTemplate.createTicket(loginId, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,13 +65,23 @@ public class SaSsoUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验ticket码,获取账号id,如果此ticket是有效的,则立即删除
|
* 校验 Ticket 码,获取账号id,如果此ticket是有效的,则立即删除
|
||||||
* @param ticket Ticket码
|
* @param ticket Ticket码
|
||||||
* @return 账号id
|
* @return 账号id
|
||||||
*/
|
*/
|
||||||
public static Object checkTicket(String ticket) {
|
public static Object checkTicket(String ticket) {
|
||||||
return ssoTemplate.checkTicket(ticket);
|
return ssoTemplate.checkTicket(ticket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验ticket码,获取账号id,如果此ticket是有效的,则立即删除
|
||||||
|
* @param ticket Ticket码
|
||||||
|
* @param client client 标识
|
||||||
|
* @return 账号id
|
||||||
|
*/
|
||||||
|
public static Object checkTicket(String ticket, String client) {
|
||||||
|
return ssoTemplate.checkTicket(ticket, client);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取:所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
|
* 获取:所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
|
||||||
@@ -152,11 +163,12 @@ public class SaSsoUtil {
|
|||||||
/**
|
/**
|
||||||
* 构建URL:Server端向Client下放ticke的地址
|
* 构建URL:Server端向Client下放ticke的地址
|
||||||
* @param loginId 账号id
|
* @param loginId 账号id
|
||||||
|
* @param client 客户端标识
|
||||||
* @param redirect Client端提供的重定向地址
|
* @param redirect Client端提供的重定向地址
|
||||||
* @return see note
|
* @return see note
|
||||||
*/
|
*/
|
||||||
public static String buildRedirectUrl(Object loginId, String redirect) {
|
public static String buildRedirectUrl(Object loginId, String client, String redirect) {
|
||||||
return ssoTemplate.buildRedirectUrl(loginId, redirect);
|
return ssoTemplate.buildRedirectUrl(loginId, client, redirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -37,5 +37,8 @@ public interface SaSsoErrorCode {
|
|||||||
|
|
||||||
/** 本地系统没有配置 http 请求处理器 */
|
/** 本地系统没有配置 http 请求处理器 */
|
||||||
public static final int CODE_30010 = 30010;
|
public static final int CODE_30010 = 30010;
|
||||||
|
|
||||||
|
/** 该 ticket 不属于当前 client */
|
||||||
|
public static final int CODE_30011 = 30011;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -23,6 +23,9 @@ public class ParamName {
|
|||||||
/** loginId参数名称 */
|
/** loginId参数名称 */
|
||||||
public String loginId = "loginId";
|
public String loginId = "loginId";
|
||||||
|
|
||||||
|
/** client参数名称 */
|
||||||
|
public String client = "client";
|
||||||
|
|
||||||
/** secretkey参数名称 */
|
/** secretkey参数名称 */
|
||||||
public String secretkey = "secretkey";
|
public String secretkey = "secretkey";
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user