diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/SaLoginConfig.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/SaLoginConfig.java new file mode 100644 index 00000000..b62560a0 --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/SaLoginConfig.java @@ -0,0 +1,72 @@ +package cn.dev33.satoken.stp; + +import java.util.Map; + +/** + * + * 快速构建 调用 `StpUtil.login()` 时的 [配置参数 Model ] + * + * @author kong + * + */ +public class SaLoginConfig { + + /** + * @param device 此次登录的客户端设备标识 + * @return SaLoginModel配置对象 + */ + public static SaLoginModel setDevice(String device) { + return create().setDevice(device); + } + + /** + * @param isLastingCookie 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在) + * @return 对象自身 + */ + public static SaLoginModel setIsLastingCookie(Boolean isLastingCookie) { + return create().setIsLastingCookie(isLastingCookie); + } + + /** + * @param timeout 指定此次登录token的有效期, 单位:秒 (如未指定,自动取全局配置的timeout值) + * @return 对象自身 + */ + public static SaLoginModel setTimeout(Long timeout) { + return create().setTimeout(timeout); + } + + /** + * @param extraData 扩展信息(只在jwt模式下生效) + * @return 对象自身 + */ + public static SaLoginModel setExtraData(Map extraData) { + return create().setExtraData(extraData); + } + + /** + * @param token 预定Token(预定本次登录生成的Token值) + * @return 对象自身 + */ + public static SaLoginModel setToken(String token) { + return create().setToken(token); + } + + /** + * 写入扩展数据(只在jwt模式下生效) + * @param key 键 + * @param value 值 + * @return 对象自身 + */ + public static SaLoginModel setExtra(String key, Object value) { + return create().setExtra(key, value); + } + + /** + * 静态方法获取一个 SaLoginModel 对象 + * @return SaLoginModel 对象 + */ + public static SaLoginModel create() { + return new SaLoginModel(); + } + +} diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/SaLoginModel.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/SaLoginModel.java index 70ec71da..7c8480da 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/stp/SaLoginModel.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/SaLoginModel.java @@ -35,16 +35,21 @@ public class SaLoginModel { */ public Map extraData; + /** + * 预定Token(预定本次登录生成的Token值) + */ + public String token; + /** - * @return 参考 {@link #device} + * @return 此次登录的客户端设备标识 */ public String getDevice() { return device; } /** - * @param device 参考 {@link #device} + * @param device 此次登录的客户端设备标识 * @return 对象自身 */ public SaLoginModel setDevice(String device) { @@ -53,14 +58,14 @@ public class SaLoginModel { } /** - * @return 参考 {@link #isLastingCookie} + * @return 参考 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在) */ public Boolean getIsLastingCookie() { return isLastingCookie; } /** - * @param isLastingCookie 参考 {@link #isLastingCookie} + * @param isLastingCookie 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在) * @return 对象自身 */ public SaLoginModel setIsLastingCookie(Boolean isLastingCookie) { @@ -69,14 +74,14 @@ public class SaLoginModel { } /** - * @return 参考 {@link #timeout} + * @return 指定此次登录token的有效期, 单位:秒 (如未指定,自动取全局配置的timeout值) */ public Long getTimeout() { return timeout; } /** - * @param timeout 参考 {@link #timeout} + * @param timeout 指定此次登录token的有效期, 单位:秒 (如未指定,自动取全局配置的timeout值) * @return 对象自身 */ public SaLoginModel setTimeout(long timeout) { @@ -85,14 +90,14 @@ public class SaLoginModel { } /** - * @return 参考 {@link #extraData} + * @return 扩展信息(只在jwt模式下生效) */ public Map getExtraData() { return extraData; } /** - * @param extraData 参考 {@link #extraData} + * @param extraData 扩展信息(只在jwt模式下生效) * @return 对象自身 */ public SaLoginModel setExtraData(Map extraData) { @@ -100,11 +105,39 @@ public class SaLoginModel { return this; } + /** + * @return 预定Token(预定本次登录生成的Token值) + */ + public String getToken() { + return token; + } + + /** + * @param token 预定Token(预定本次登录生成的Token值) + * @return 对象自身 + */ + public SaLoginModel setToken(String token) { + this.token = token; + return this; + } + + /* + * toString + */ + @Override + public String toString() { + return "SaLoginModel [device=" + device + ", isLastingCookie=" + isLastingCookie + ", timeout=" + timeout + + ", extraData=" + extraData + ", token=" + token + "]"; + } + + // ------ 附加方法 + + /** * 写入扩展数据(只在jwt模式下生效) * @param key 键 * @param value 值 - * @return + * @return 对象自身 */ public SaLoginModel setExtra(String key, Object value) { if(this.extraData == null) { @@ -183,14 +216,6 @@ public class SaLoginModel { return new SaLoginModel(); } - /** - * toString - */ - @Override - public String toString() { - return "SaLoginModel [device=" + device + ", isLastingCookie=" + isLastingCookie + ", timeout=" + timeout + "]"; - } - /** * 更换为 getDeviceOrDefault() diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java index 901e8d01..92c14390 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java @@ -267,13 +267,36 @@ public class StpLogic { public void login(Object id, boolean isLastingCookie) { login(id, new SaLoginModel().setIsLastingCookie(isLastingCookie)); } - + /** * 会话登录,并指定所有登录参数Model * @param id 登录id,建议的类型:(long | int | String) * @param loginModel 此次登录的参数Model */ public void login(Object id, SaLoginModel loginModel) { + // 1、创建会话 + String token = createLoginSession(id, loginModel); + + // 2、在当前客户端注入Token + setTokenValue(token, loginModel.getCookieTimeout()); + } + + /** + * 创建指定账号id的登录会话 + * @param id 登录id,建议的类型:(long | int | String) + * @return 返回会话令牌 + */ + public String createLoginSession(Object id) { + return createLoginSession(id, new SaLoginModel()); + } + + /** + * 创建指定账号id的登录会话 + * @param id 登录id,建议的类型:(long | int | String) + * @param loginModel 此次登录的参数Model + * @return 返回会话令牌 + */ + public String createLoginSession(Object id, SaLoginModel loginModel) { SaTokenException.throwByNull(id, "账号id不能为空"); @@ -300,7 +323,11 @@ public class StpLogic { } // 如果至此,仍未成功创建tokenValue, 则开始生成一个 if(tokenValue == null) { - tokenValue = createTokenValue(id, loginModel.getDeviceOrDefault(), loginModel.getTimeout(), loginModel.getExtraData()); + if(SaFoxUtil.isEmpty(loginModel.getToken())) { + tokenValue = createTokenValue(id, loginModel.getDeviceOrDefault(), loginModel.getTimeout(), loginModel.getExtraData()); + } else { + tokenValue = loginModel.getToken(); + } } // ------ 3. 获取 User-Session , 续期 @@ -313,15 +340,15 @@ public class StpLogic { // ------ 4. 持久化其它数据 // token -> id 映射关系 saveTokenToIdMapping(tokenValue, id, loginModel.getTimeout()); - - // 在当前会话写入tokenValue - setTokenValue(tokenValue, loginModel.getCookieTimeout()); // 写入 [token-last-activity] setLastActivityToNow(tokenValue); - + // $$ 通知监听器,账号xxx 登录成功 SaManager.getSaTokenListener().doLogin(loginType, id, loginModel); + + // 返回Token + return tokenValue; } // --- 注销 diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java index 9df4df7b..7675724e 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java @@ -132,6 +132,25 @@ public class StpUtil { stpLogic.login(id, loginModel); } + /** + * 创建指定账号id的登录会话 + * @param id 登录id,建议的类型:(long | int | String) + * @return 返回会话令牌 + */ + public static String createLoginSession(Object id) { + return stpLogic.createLoginSession(id); + } + + /** + * 创建指定账号id的登录会话 + * @param id 登录id,建议的类型:(long | int | String) + * @param loginModel 此次登录的参数Model + * @return 返回会话令牌 + */ + public static String createLoginSession(Object id, SaLoginModel loginModel) { + return stpLogic.createLoginSession(id, loginModel); + } + // --- 注销 /** diff --git a/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/satoken/at/StpUserUtil.java b/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/satoken/at/StpUserUtil.java index a0fa0767..cb0fd6a2 100644 --- a/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/satoken/at/StpUserUtil.java +++ b/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/satoken/at/StpUserUtil.java @@ -136,6 +136,25 @@ public class StpUserUtil { stpLogic.login(id, loginModel); } + /** + * 创建指定账号id的登录会话 + * @param id 登录id,建议的类型:(long | int | String) + * @return 返回会话令牌 + */ + public static String createLoginSession(Object id) { + return stpLogic.createLoginSession(id); + } + + /** + * 创建指定账号id的登录会话 + * @param id 登录id,建议的类型:(long | int | String) + * @param loginModel 此次登录的参数Model + * @return 返回会话令牌 + */ + public static String createLoginSession(Object id, SaLoginModel loginModel) { + return stpLogic.createLoginSession(id, loginModel); + } + // --- 注销 /** diff --git a/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java b/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java index 565923a4..0712eb2d 100644 --- a/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java +++ b/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java @@ -235,7 +235,6 @@ public class TestController { return AjaxJson.getSuccessData("登录成功"); } - // 测试 浏览器访问: http://localhost:8081/test/test @RequestMapping("test") public AjaxJson test() { @@ -248,5 +247,5 @@ public class TestController { public AjaxJson test2() { return AjaxJson.getSuccess(); } - + } diff --git a/sa-token-doc/doc/_sidebar.md b/sa-token-doc/doc/_sidebar.md index f0419ef7..4b71ccf4 100644 --- a/sa-token-doc/doc/_sidebar.md +++ b/sa-token-doc/doc/_sidebar.md @@ -50,6 +50,7 @@ - [OAuth2-Server搭建](/oauth2/oauth2-server) - [OAuth2-Server端-API列表](/oauth2/oauth2-api) - [OAuth2-二次开发说明](/oauth2/oauth2-dev) + - [OAuth2-与登录会话实现数据互通](/oauth2/oauth2-interworking) - **微服务** - [分布式Session会话](/micro/dcs-session) diff --git a/sa-token-doc/doc/oauth2/oauth2-interworking.md b/sa-token-doc/doc/oauth2/oauth2-interworking.md new file mode 100644 index 00000000..1eef4e5a --- /dev/null +++ b/sa-token-doc/doc/oauth2/oauth2-interworking.md @@ -0,0 +1,65 @@ +# Sa-Token-OAuth2 与登录会话实现数据互通 + +--- + +### 前提 + +前提,我们: +- 把 OAuth2 模块生成的令牌称作资源令牌(access_token), +- 把 StpUtil 登录会话生成的令牌称作会话令牌(satoken)。 + +正常情况下,资源令牌 与 会话令牌 的数据是不互通的,具体表现就是:当我们拿着 access_token 去访问 satoken 令牌的接口,会被抛出异常:`无效Token:xxxxx` + +那么,有什么办法可以做到这两个模块的数据互通呢? + + + +### OAuth2-Server 端数据互通 + +很简单,你只需要在 `SaOAuth2TemplateImpl` 实现类中继续重写 Access-Token 的生成策略: + +``` java +@Component +public class SaOAuth2TemplateImpl extends SaOAuth2Template { + + // ... 其它代码 + + // 重写 Access-Token 生成策略:复用登录会话的Token + @Override + public String randomAccessToken(String clientId, Object loginId, String scope) { + String tokenValue = StpUtil.createLoginSession(loginId); + return tokenValue; + } + +} +``` + +重启项目,然后在 OAuth2 模块授权登录,现在生成的 `access_token` ,可以用来访问 `satoken` 的会话接口了。 + + +### OAuth2-Client 数据互通 +除了Server端,Client端也可以打通 `access_token` 与 `satoken` 会话。做法是在 Client 端拿到 `access_token` 后进行登录时,使用 SaLoginModel 预定登录生成的 Token 值 + +``` java +// 1. 获取到access_token +String access_token = ... + +// 2. 登录时预定生成的token +StpUtil.login(uid, SaLoginConfig.setToken(access_token)); + +// 3. 其它代码... +``` + + +### 注意点 +数据互通,让前端与后端的交互更加方便,一个 token 即可访问所有接口,但也一定程度上失去了OAuth2的 “不同 Client 不同权限” 的设计意义, +同时也默认每个 Client 都拥有了账号的会话权限(access_token 与 satoken 为同一个)。 + +应该根据自己的架构合理分析是否应该整合数据互通。 + + + + + + + diff --git a/sa-token-doc/doc/up/remember-me.md b/sa-token-doc/doc/up/remember-me.md index 55bfea37..1f2e4f1a 100644 --- a/sa-token-doc/doc/up/remember-me.md +++ b/sa-token-doc/doc/up/remember-me.md @@ -72,6 +72,7 @@ StpUtil.login(10001, new SaLoginModel() .setDevice("PC") // 此次登录的客户端设备标识, 用于[同端互斥登录]时指定此次登录的设备名称 .setIsLastingCookie(true) // 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在) .setTimeout(60 * 60 * 24 * 7) // 指定此次登录token的有效期, 单位:秒 (如未指定,自动取全局配置的timeout值) + .setToken("xxxx-xxxx-xxxx-xxxx") // 预定此次登录的生成的Token ); ```