更换单点注销接口响应格式

This commit is contained in:
click33 2022-04-27 01:53:20 +08:00
parent e320a8e45b
commit 5e3795e29e
8 changed files with 190 additions and 56 deletions

View File

@ -0,0 +1,22 @@
package cn.dev33.satoken.exception;
/**
* 一个异常代表 JSON 转换失败
*
* @author kong
*/
public class SaJsonConvertException extends SaTokenException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290134144L;
/**
* 一个异常代表 JSON 转换失败
*/
public SaJsonConvertException(Throwable e) {
super(e);
}
}

View File

@ -21,12 +21,34 @@ public class SaResult extends LinkedHashMap<String, Object> implements Serializa
public static final int CODE_SUCCESS = 200; public static final int CODE_SUCCESS = 200;
public static final int CODE_ERROR = 500; public static final int CODE_ERROR = 500;
/**
* 构建
*/
public SaResult() {
}
/**
* 构建
* @param code 状态码
* @param msg 信息
* @param data 数据
*/
public SaResult(int code, String msg, Object data) { public SaResult(int code, String msg, Object data) {
this.setCode(code); this.setCode(code);
this.setMsg(msg); this.setMsg(msg);
this.setData(data); this.setData(data);
} }
/**
* 根据 Map 快速构建
* @param map /
*/
public SaResult(Map<String, Object> map) {
for (String key: map.keySet()) {
this.set(key, map.get(key));
}
}
/** /**
* 获取code * 获取code
* @return code * @return code
@ -138,9 +160,16 @@ public class SaResult extends LinkedHashMap<String, Object> implements Serializa
public String toString() { public String toString() {
return "{" return "{"
+ "\"code\": " + this.getCode() + "\"code\": " + this.getCode()
+ ", \"msg\": \"" + this.getMsg() + "\"" + ", \"msg\": " + transValue(this.getMsg())
+ ", \"data\": \"" + this.getData() + "\"" + ", \"data\": " + transValue(this.getData())
+ "}"; + "}";
} }
private String transValue(Object value) {
if(value instanceof String) {
return "\"" + value + "\"";
}
return String.valueOf(value);
}
} }

View File

@ -37,8 +37,9 @@ http://{host}:{port}/sso/doLogin
| name | 是 | 用户名 | | name | 是 | 用户名 |
| pwd | 是 | 密码 | | pwd | 是 | 密码 |
此接口属于 RestAPI (使用ajax访问),会进入后端配置的 `setDoLoginHandle` 函数中,另外需要注意: 此接口属于 RestAPI (使用ajax访问),会进入后端配置的 `sso.setDoLoginHandle` 函数中,此函数的返回值即是此接口的响应值。
此接口并非只能携带 name、pwd 参数,因为你可以在 setDoLoginHandle 函数里通过 `SaHolder.getRequest().getParam("xxx")` 来获取其它参数。
另外需要注意:此接口并非只能携带 name、pwd 参数,因为你可以在方法里通过 `SaHolder.getRequest().getParam("xxx")` 来获取前端提交的其它参数。
### 3、Ticket 校验接口 ### 3、Ticket 校验接口
@ -52,12 +53,29 @@ http://{host}:{port}/sso/checkTicket
| 参数 | 是否必填 | 说明 | | 参数 | 是否必填 | 说明 |
| :-------- | :-------- | :-------- | | :-------- | :-------- | :-------- |
| ticket | 是 | 在步骤 5.1 中授权重定向时的 ticket 参数 | | ticket | 是 | 在步骤 1 中授权重定向时的 ticket 参数 |
| ssoLogoutCall | 否 | 单点注销时的回调通知地址只在SSO模式三单点注销时需要携带此参数| | ssoLogoutCall | 否 | 单点注销时的回调通知地址只在SSO模式三单点注销时需要携带此参数|
返回值场景: 返回值场景:
- 返回空,代表校验失败。 - 校验成功时:
- 返回具体的 loginId例如10001代表校验成功值为此 ticket 码代表的用户id。
``` js
{
"code": 200,
"msg": "ok",
"data": "10001" // 此 ticket 指向的 loginId
}
```
- 校验失败时:
``` js
{
"code": 500,
"msg": "无效ticketvESj0MtqrtSoucz4DDHJnsqU3u7AKFzbj0KH57EfJvuhkX1uAH23DuNrMYSjTnEq",
"data": null
}
```
### 4、单点注销接口 ### 4、单点注销接口
@ -90,6 +108,17 @@ http://{host}:{port}/sso/logout
} }
``` ```
如果单点注销失败,将返回:
``` js
{
"code": 500, // 200表示请求成功非200标识请求失败
"msg": "无效秘钥xxx", // 失败原因
"data": null
}
```
<br> <br>
SSO 认证中心只有这四个接口,接下来让我一起来看一下 Client 端的对接流程:[SSO模式一 共享Cookie同步会话](/sso/sso-type1) SSO 认证中心只有这四个接口,接下来让我一起来看一下 Client 端的对接流程:[SSO模式一 共享Cookie同步会话](/sso/sso-type1)

View File

@ -279,12 +279,16 @@ public class SaSsoConfig implements Serializable {
/** /**
* SSO-Server端未登录时返回的View * SSO-Server端未登录时返回的View
*/ */
public Supplier<Object> notLoginView = () -> "当前会话在SSO-Server认证中心尚未登录"; public Supplier<Object> notLoginView = () -> {
return "当前会话在SSO-Server认证中心尚未登录";
};
/** /**
* SSO-Server端登录函数 * SSO-Server端登录函数
*/ */
public BiFunction<String, String, Object> doLoginHandle = (name, pwd) -> SaResult.error(); public BiFunction<String, String, Object> doLoginHandle = (name, pwd) -> {
return SaResult.error();
};
/** /**
* SSO-Client端自定义校验Ticket返回值的处理逻辑 每次从认证中心获取校验Ticket的结果后调用 * SSO-Client端自定义校验Ticket返回值的处理逻辑 每次从认证中心获取校验Ticket的结果后调用
@ -294,7 +298,9 @@ public class SaSsoConfig implements Serializable {
/** /**
* SSO-Client端发送Http请求的处理函数 * SSO-Client端发送Http请求的处理函数
*/ */
public Function<String, Object> sendHttp = url -> {throw new SaTokenException("请配置Http处理器");}; public Function<String, String> sendHttp = url -> {
throw new SaTokenException("请配置 Http 请求处理器");
};
/** /**
@ -349,7 +355,7 @@ public class SaSsoConfig implements Serializable {
* @param sendHttp SSO-Client端发送Http请求的处理函数 * @param sendHttp SSO-Client端发送Http请求的处理函数
* @return 对象自身 * @return 对象自身
*/ */
public SaSsoConfig setSendHttp(Function<String, Object> sendHttp) { public SaSsoConfig setSendHttp(Function<String, String> sendHttp) {
this.sendHttp = sendHttp; this.sendHttp = sendHttp;
return this; return this;
} }
@ -357,7 +363,7 @@ public class SaSsoConfig implements Serializable {
/** /**
* @return 函数 SSO-Client端发送Http请求的处理函数 * @return 函数 SSO-Client端发送Http请求的处理函数
*/ */
public Function<String, Object> getSendHttp() { public Function<String, String> getSendHttp() {
return sendHttp; return sendHttp;
} }

View File

@ -1,5 +1,8 @@
package cn.dev33.satoken.sso; package cn.dev33.satoken.sso;
import java.util.Map;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaSsoConfig; import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest; import cn.dev33.satoken.context.model.SaRequest;
@ -19,6 +22,8 @@ import cn.dev33.satoken.util.SaResult;
*/ */
public class SaSsoHandle { public class SaSsoHandle {
// ----------- SSO-Server 端路由分发
/** /**
* 处理Server端所有请求 * 处理Server端所有请求
* @return 处理结果 * @return 处理结果
@ -46,14 +51,14 @@ public class SaSsoHandle {
return ssoCheckTicket(); return ssoCheckTicket();
} }
// SSO-Server端单点注销 [模式一] (不带loginId参数) // SSO-Server端单点注销 [用户访问式] (不带loginId参数)
if(req.isPath(Api.ssoLogout) && cfg.getIsSlo() && req.hasParam(ParamName.loginId) == false) { if(req.isPath(Api.ssoLogout) && cfg.getIsSlo() && req.hasParam(ParamName.loginId) == false) {
return ssoServerLogoutType1(); return ssoLogoutByUserVisit();
} }
// SSO-Server端单点注销 [模式三] (带loginId参数) // SSO-Server端单点注销 [Client调用式] (带loginId参数 & isHttp=true)
if(req.isPath(Api.ssoLogout) && cfg.getIsHttp() && cfg.getIsSlo() && req.hasParam(ParamName.loginId)) { if(req.isPath(Api.ssoLogout) && cfg.getIsHttp() && cfg.getIsSlo() && req.hasParam(ParamName.loginId)) {
return ssoServerLogout(); return ssoLogoutByClientHttp();
} }
// 默认返回 // 默认返回
@ -116,38 +121,47 @@ public class SaSsoHandle {
String ticket = req.getParam(ParamName.ticket); String ticket = req.getParam(ParamName.ticket);
String sloCallback = req.getParam(ParamName.ssoLogoutCall); String sloCallback = req.getParam(ParamName.ssoLogoutCall);
// 校验ticket获取对应的账号id // 校验ticket获取 loginId
Object loginId = SaSsoUtil.checkTicket(ticket); Object loginId = SaSsoUtil.checkTicket(ticket);
// 注册此客户端的单点注销回调URL // 注册此客户端的单点注销回调URL
SaSsoUtil.registerSloCallbackUrl(loginId, sloCallback); SaSsoUtil.registerSloCallbackUrl(loginId, sloCallback);
// 返回给Client端 // client 端响应结果
return loginId; if(SaFoxUtil.isEmpty(loginId)) {
return SaResult.error("无效ticket" + ticket);
} else {
return SaResult.data(loginId);
}
} }
/** /**
* SSO-Server端单点注销 [模式一] * SSO-Server端单点注销 [用户访问式]
* @return 处理结果 * @return 处理结果
*/ */
public static Object ssoServerLogoutType1() { public static Object ssoLogoutByUserVisit() {
// 获取对象 // 获取对象
SaRequest req = SaHolder.getRequest(); SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse(); SaResponse res = SaHolder.getResponse();
SaSsoConfig cfg = SaSsoManager.getConfig();
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic; StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
String loginId = req.getParam(ParamName.loginId);
// 开始处理 // step.1 遍历 Client 端注销
SaSsoUtil.forEachSloUrl(loginId, url -> cfg.getSendHttp().apply(url));
// step.2 Server 端注销
stpLogic.logout(); stpLogic.logout();
// 返回 // 完成
return ssoLogoutBack(req, res); return ssoLogoutBack(req, res);
} }
/** /**
* SSO-Server端单点注销 [模式三] * SSO-Server端单点注销 [Client调用式]
* @return 处理结果 * @return 处理结果
*/ */
public static Object ssoServerLogout() { public static Object ssoLogoutByClientHttp() {
// 获取对象 // 获取对象
SaRequest req = SaHolder.getRequest(); SaRequest req = SaHolder.getRequest();
SaSsoConfig cfg = SaSsoManager.getConfig(); SaSsoConfig cfg = SaSsoManager.getConfig();
@ -157,21 +171,22 @@ public class SaSsoHandle {
String loginId = req.getParam(ParamName.loginId); String loginId = req.getParam(ParamName.loginId);
String secretkey = req.getParam(ParamName.secretkey); String secretkey = req.getParam(ParamName.secretkey);
// 遍历通知Client端注销会话
// step.1 校验秘钥 // step.1 校验秘钥
SaSsoUtil.checkSecretkey(secretkey); SaSsoUtil.checkSecretkey(secretkey);
// step.2 遍历通知Client端注销会话 // step.2 遍历 Client 端注销
SaSsoUtil.forEachSloUrl(loginId, url -> cfg.getSendHttp().apply(url)); SaSsoUtil.forEachSloUrl(loginId, url -> cfg.getSendHttp().apply(url));
// step.3 Server端注销 // step.3 Server 端注销
stpLogic.logout(loginId); stpLogic.logout(loginId);
// 完成 // 完成
return SaSsoConsts.OK; return SaResult.ok();
} }
// ----------- SSO-Client 端路由分发
/** /**
* 处理Client端所有请求 * 处理Client端所有请求
* @return 处理结果 * @return 处理结果
@ -236,20 +251,21 @@ public class SaSsoHandle {
String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(SaHolder.getRequest().getUrl(), back); String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(SaHolder.getRequest().getUrl(), back);
return res.redirect(serverAuthUrl); return res.redirect(serverAuthUrl);
} else { } else {
// ------- 1校验ticket获取账号id // ------- 1校验ticket获取 loginId
Object loginId = checkTicket(ticket, Api.ssoLogin); Object loginId = checkTicket(ticket, Api.ssoLogin);
// Be: 如果开发者自定义了处理逻辑 // Be: 如果开发者自定义了处理逻辑
if(cfg.getTicketResultHandle() != null) { if(cfg.getTicketResultHandle() != null) {
return cfg.getTicketResultHandle().apply(loginId, back); return cfg.getTicketResultHandle().apply(loginId, back);
} }
// ------- 2如果loginId有值说明ticket有效进行登录并重定向至back地址
if(loginId != null ) { // ------- 2如果 loginId 无值说明 ticket 无效
if(SaFoxUtil.isEmpty(loginId)) {
throw new SaSsoException("无效ticket" + ticket).setCode(SaSsoExceptionCode.CODE_20004);
} else {
// 3如果 loginId 有值说明 ticket 有效此时进行登录并重定向至back地址
stpLogic.login(loginId); stpLogic.login(loginId);
return res.redirect(back); return res.redirect(back);
} else {
// 如果ticket无效:
throw new SaSsoException("无效ticket" + ticket).setCode(SaSsoExceptionCode.CODE_20004);
} }
} }
} }
@ -279,7 +295,6 @@ public class SaSsoHandle {
// 获取对象 // 获取对象
SaRequest req = SaHolder.getRequest(); SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse(); SaResponse res = SaHolder.getResponse();
SaSsoConfig cfg = SaSsoManager.getConfig();
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic; StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
// 如果未登录则无需注销 // 如果未登录则无需注销
@ -289,17 +304,19 @@ public class SaSsoHandle {
// 调用SSO-Server认证中心API进行注销 // 调用SSO-Server认证中心API进行注销
String url = SaSsoUtil.buildSloUrl(stpLogic.getLoginId()); String url = SaSsoUtil.buildSloUrl(stpLogic.getLoginId());
String body = String.valueOf(cfg.getSendHttp().apply(url)); SaResult result = request(url);
if(SaSsoConsts.OK.equals(body) == false) {
return SaResult.error("单点注销失败");
}
// 返回 // 校验
return ssoLogoutBack(req, res); if(result.getCode() == SaResult.CODE_SUCCESS) {
return ssoLogoutBack(req, res);
} else {
// sso-server 回应的消息作为异常抛出
throw new SaSsoException(result.getMsg()).setCode(SaSsoExceptionCode.CODE_20006);
}
} }
/** /**
* SSO-Client端单点注销的回调 [模式三] * SSO-Client端单点注销的回调 [模式三]
* @return 处理结果 * @return 处理结果
*/ */
public static Object ssoLogoutCall() { public static Object ssoLogoutCall() {
@ -311,11 +328,17 @@ public class SaSsoHandle {
String loginId = req.getParam(ParamName.loginId); String loginId = req.getParam(ParamName.loginId);
String secretkey = req.getParam(ParamName.secretkey); String secretkey = req.getParam(ParamName.secretkey);
// 注销当前应用端会话
SaSsoUtil.checkSecretkey(secretkey); SaSsoUtil.checkSecretkey(secretkey);
stpLogic.logout(loginId); stpLogic.logout(loginId);
return SaSsoConsts.OK;
// 响应
return SaResult.ok("单点注销回调成功");
} }
// ----------- 工具方法
/** /**
* 封装单点注销成功后返回结果 * 封装单点注销成功后返回结果
* @param req SaRequest对象 * @param req SaRequest对象
@ -348,20 +371,41 @@ public class SaSsoHandle {
*/ */
public static Object checkTicket(String ticket, String currUri) { public static Object checkTicket(String ticket, String currUri) {
SaSsoConfig cfg = SaSsoManager.getConfig(); SaSsoConfig cfg = SaSsoManager.getConfig();
// --------- 两种模式 // --------- 两种模式
if(cfg.getIsHttp()) { if(cfg.getIsHttp()) {
// 模式三使用http请求校验ticket // 模式三使用 http 请求从认证中心校验ticket
String ssoLogoutCall = null; String ssoLogoutCall = null;
if(cfg.getIsSlo()) { if(cfg.getIsSlo()) {
ssoLogoutCall = SaHolder.getRequest().getUrl().replace(currUri, Api.ssoLogoutCall); ssoLogoutCall = SaHolder.getRequest().getUrl().replace(currUri, Api.ssoLogoutCall);
} }
// 发起请求
String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, ssoLogoutCall); String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, ssoLogoutCall);
Object body = cfg.getSendHttp().apply(checkUrl); SaResult result = request(checkUrl);
return (SaFoxUtil.isEmpty(body) ? null : body);
// 校验
if(result.getCode() == SaResult.CODE_SUCCESS) {
return result.getData();
} else {
// sso-server 回应的消息作为异常抛出
throw new SaSsoException(result.getMsg()).setCode(SaSsoExceptionCode.CODE_20005);
}
} else { } else {
// 模式二直连Redis校验ticket // 模式二直连Redis校验ticket
return SaSsoUtil.checkTicket(ticket); return SaSsoUtil.checkTicket(ticket);
} }
} }
/**
* 发出请求并返回 SaResult 结果
* @param url 请求地址
* @return 返回的结果
*/
public static SaResult request(String url) {
String body = SaSsoManager.getConfig().getSendHttp().apply(url);
Map<String, Object> map = SaManager.getSaJsonTemplate().parseJsonToMap(body);
return new SaResult(map);
}
} }

View File

@ -20,6 +20,10 @@ public class SaSsoExceptionCode {
/** 提供的 ticket 是无效的 */ /** 提供的 ticket 是无效的 */
public static final int CODE_20004 = 20004; public static final int CODE_20004 = 20004;
/** 在模式三下sso-client 调用 sso-server 端 校验ticket接口 时,得到的响应是校验失败 */
public static final int CODE_20005 = 20005;
/** 在模式三下sso-client 调用 sso-server 端 单点注销接口 时,得到的响应是注销失败 */
public static final int CODE_20006 = 20006;
} }

View File

@ -5,7 +5,7 @@ import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import cn.dev33.satoken.exception.SaTokenException; import cn.dev33.satoken.exception.SaJsonConvertException;
import cn.dev33.satoken.json.SaJsonTemplate; import cn.dev33.satoken.json.SaJsonTemplate;
/** /**
@ -31,7 +31,7 @@ public class SaJsonTemplateForJackson implements SaJsonTemplate {
try { try {
return objectMapper.writeValueAsString(obj); return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new SaTokenException(e); throw new SaJsonConvertException(e);
} }
} }
@ -45,7 +45,7 @@ public class SaJsonTemplateForJackson implements SaJsonTemplate {
Map<String, Object> map = objectMapper.readValue(jsonStr, Map.class); Map<String, Object> map = objectMapper.readValue(jsonStr, Map.class);
return map; return map;
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new SaTokenException(e); throw new SaJsonConvertException(e);
} }
} }

View File

@ -5,7 +5,7 @@ import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import cn.dev33.satoken.exception.SaTokenException; import cn.dev33.satoken.exception.SaJsonConvertException;
import cn.dev33.satoken.json.SaJsonTemplate; import cn.dev33.satoken.json.SaJsonTemplate;
/** /**
@ -31,7 +31,7 @@ public class SaJsonTemplateForJackson implements SaJsonTemplate {
try { try {
return objectMapper.writeValueAsString(obj); return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new SaTokenException(e); throw new SaJsonConvertException(e);
} }
} }
@ -45,7 +45,7 @@ public class SaJsonTemplateForJackson implements SaJsonTemplate {
Map<String, Object> map = objectMapper.readValue(jsonStr, Map.class); Map<String, Object> map = objectMapper.readValue(jsonStr, Map.class);
return map; return map;
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new SaTokenException(e); throw new SaJsonConvertException(e);
} }
} }