From 2b46d27b877039d85f7e89e12e17646ae0da4bb2 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Sun, 18 Aug 2024 09:53:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=BD=E5=8F=96=E6=95=B0=E6=8D=AE=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E5=99=A8=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev33/satoken/oauth2/SaOAuth2Manager.java | 22 +- .../data/convert/SaOAuth2DataConverter.java | 33 ++ .../SaOAuth2DataConverterDefaultImpl.java | 82 +++++ .../data/generate/SaOAuth2DataGenerate.java | 96 +++++ .../SaOAuth2DataGenerateDefaultImpl.java | 274 +++++++++++++++ .../data/loader/SaOAuth2DataLoader.java | 63 ++++ .../SaOAuth2DataResolverDefaultImpl.java | 1 + .../processor/SaOAuth2ServerProcessor.java | 18 +- .../oauth2/template/SaOAuth2Template.java | 328 ------------------ .../satoken/oauth2/template/SaOAuth2Util.java | 77 ---- 10 files changed, 579 insertions(+), 415 deletions(-) create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerate.java create mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java index 1110aae4..0d9d1790 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java @@ -20,6 +20,8 @@ import cn.dev33.satoken.oauth2.dao.SaOAuth2Dao; import cn.dev33.satoken.oauth2.dao.SaOAuth2DaoDefaultImpl; import cn.dev33.satoken.oauth2.data.convert.SaOAuth2DataConverter; import cn.dev33.satoken.oauth2.data.convert.SaOAuth2DataConverterDefaultImpl; +import cn.dev33.satoken.oauth2.data.generate.SaOAuth2DataGenerate; +import cn.dev33.satoken.oauth2.data.generate.SaOAuth2DataGenerateDefaultImpl; import cn.dev33.satoken.oauth2.data.loader.SaOAuth2DataLoader; import cn.dev33.satoken.oauth2.data.loader.SaOAuth2DataLoaderDefaultImpl; import cn.dev33.satoken.oauth2.data.resolver.SaOAuth2DataResolver; @@ -89,7 +91,7 @@ public class SaOAuth2Manager { } /** - * OAuth2 数据格式转换器 + * OAuth2 数据格式转换器 Bean */ private static volatile SaOAuth2DataConverter dataConverter; public static SaOAuth2DataConverter getDataConverter() { @@ -106,6 +108,24 @@ public class SaOAuth2Manager { SaOAuth2Manager.dataConverter = dataConverter; } + /** + * OAuth2 数据构建器 Bean + */ + private static volatile SaOAuth2DataGenerate dataGenerate; + public static SaOAuth2DataGenerate getDataGenerate() { + if (dataGenerate == null) { + synchronized (SaOAuth2Manager.class) { + if (dataGenerate == null) { + setDataGenerate(new SaOAuth2DataGenerateDefaultImpl()); + } + } + } + return dataGenerate; + } + public static void setDataGenerate(SaOAuth2DataGenerate dataGenerate) { + SaOAuth2Manager.dataGenerate = dataGenerate; + } + /** * OAuth2 数据持久 Bean */ diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverter.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverter.java index 9a9e953b..fddeb33a 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverter.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverter.java @@ -15,6 +15,10 @@ */ package cn.dev33.satoken.oauth2.data.convert; +import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; +import cn.dev33.satoken.oauth2.data.model.CodeModel; +import cn.dev33.satoken.oauth2.data.model.RefreshTokenModel; + import java.util.List; /** @@ -46,4 +50,33 @@ public interface SaOAuth2DataConverter { */ List convertAllowUrlStringToList(String allowUrl); + + /** + * 将 Code 转换为 Access-Token + * @param cm CodeModel对象 + * @return AccessToken对象 + */ + AccessTokenModel convertCodeToAccessToken(CodeModel cm); + + /** + * 将 Access-Token 转换为 Refresh-Token + * @param at / + * @return / + */ + RefreshTokenModel convertAccessTokenToRefreshToken(AccessTokenModel at); + + /** + * 将 Refresh-Token 转换为 Access-Token + * @param rt / + * @return / + */ + AccessTokenModel convertRefreshTokenToAccessToken(RefreshTokenModel rt); + + /** + * 根据 Refresh-Token 创建一个新的 Refresh-Token + * @param rt / + * @return / + */ + RefreshTokenModel convertRefreshTokenToRefreshToken(RefreshTokenModel rt); + } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverterDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverterDefaultImpl.java index 523e3f9e..74570f11 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverterDefaultImpl.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/convert/SaOAuth2DataConverterDefaultImpl.java @@ -15,6 +15,11 @@ */ package cn.dev33.satoken.oauth2.data.convert; +import cn.dev33.satoken.oauth2.SaOAuth2Manager; +import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; +import cn.dev33.satoken.oauth2.data.model.CodeModel; +import cn.dev33.satoken.oauth2.data.model.RefreshTokenModel; +import cn.dev33.satoken.oauth2.data.model.SaClientModel; import cn.dev33.satoken.util.SaFoxUtil; import java.util.Collections; @@ -61,5 +66,82 @@ public class SaOAuth2DataConverterDefaultImpl implements SaOAuth2DataConverter { return SaFoxUtil.convertStringToList(allowUrl); } + /** + * 将 Code 转换为 Access-Token + */ + @Override + public AccessTokenModel convertCodeToAccessToken(CodeModel cm) { + AccessTokenModel at = new AccessTokenModel(); + at.accessToken = SaOAuth2Manager.getDataLoader().randomAccessToken(cm.clientId, cm.loginId, cm.scopes); + // at.refreshToken = randomRefreshToken(cm.clientId, cm.loginId, cm.scope); + at.clientId = cm.clientId; + at.loginId = cm.loginId; + at.scopes = cm.scopes; + at.openid = SaOAuth2Manager.getDataLoader().getOpenid(cm.clientId, cm.loginId); + SaClientModel clientModel = SaOAuth2Manager.getDataLoader().getClientModelNotNull(cm.clientId); + at.expiresTime = System.currentTimeMillis() + (clientModel.getAccessTokenTimeout() * 1000); + // at.refreshExpiresTime = System.currentTimeMillis() + (checkClientModel(cm.clientId).getRefreshTokenTimeout() * 1000); + return at; + } + + /** + * 将 Access-Token 转换为 Refresh-Token + * @param at . + * @return . + */ + @Override + public RefreshTokenModel convertAccessTokenToRefreshToken(AccessTokenModel at) { + RefreshTokenModel rt = new RefreshTokenModel(); + rt.refreshToken = SaOAuth2Manager.getDataLoader().randomRefreshToken(at.clientId, at.loginId, at.scopes); + rt.clientId = at.clientId; + rt.loginId = at.loginId; + rt.scopes = at.scopes; + rt.openid = at.openid; + SaClientModel clientModel = SaOAuth2Manager.getDataLoader().getClientModelNotNull(at.clientId); + rt.expiresTime = System.currentTimeMillis() + (clientModel.getRefreshTokenTimeout() * 1000); + // 改变at属性 + at.refreshToken = rt.refreshToken; + at.refreshExpiresTime = rt.expiresTime; + return rt; + } + + /** + * 将 Refresh-Token 转换为 Access-Token + * @param rt . + * @return . + */ + @Override + public AccessTokenModel convertRefreshTokenToAccessToken(RefreshTokenModel rt) { + AccessTokenModel at = new AccessTokenModel(); + at.accessToken = SaOAuth2Manager.getDataLoader().randomAccessToken(rt.clientId, rt.loginId, rt.scopes); + at.refreshToken = rt.refreshToken; + at.clientId = rt.clientId; + at.loginId = rt.loginId; + at.scopes = rt.scopes; + at.openid = rt.openid; + SaClientModel clientModel = SaOAuth2Manager.getDataLoader().getClientModelNotNull(rt.clientId); + at.expiresTime = System.currentTimeMillis() + (clientModel.getAccessTokenTimeout() * 1000); + at.refreshExpiresTime = rt.expiresTime; + return at; + } + + /** + * 根据 Refresh-Token 创建一个新的 Refresh-Token + * @param rt . + * @return . + */ + @Override + public RefreshTokenModel convertRefreshTokenToRefreshToken(RefreshTokenModel rt) { + RefreshTokenModel newRt = new RefreshTokenModel(); + newRt.refreshToken = SaOAuth2Manager.getDataLoader().randomRefreshToken(rt.clientId, rt.loginId, rt.scopes); + SaClientModel clientModel = SaOAuth2Manager.getDataLoader().getClientModelNotNull(rt.clientId); + newRt.expiresTime = System.currentTimeMillis() + (clientModel.getRefreshTokenTimeout() * 1000); + newRt.clientId = rt.clientId; + newRt.scopes = rt.scopes; + newRt.loginId = rt.loginId; + newRt.openid = rt.openid; + return newRt; + } + } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerate.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerate.java new file mode 100644 index 00000000..92a89010 --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerate.java @@ -0,0 +1,96 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.dev33.satoken.oauth2.data.generate; + +import cn.dev33.satoken.oauth2.data.model.AccessTokenModel; +import cn.dev33.satoken.oauth2.data.model.ClientTokenModel; +import cn.dev33.satoken.oauth2.data.model.CodeModel; +import cn.dev33.satoken.oauth2.data.model.RequestAuthModel; + +import java.util.List; + +/** + * Sa-Token OAuth2 数据构建器,负责相关 Model 数据构建 + * + * @author click33 + * @since 1.39.0 + */ +public interface SaOAuth2DataGenerate { + + // ------------------- generate 构建数据 + + /** + * 构建Model:Code授权码 + * @param ra 请求参数Model + * @return 授权码Model + */ + public CodeModel generateCode(RequestAuthModel ra); + + /** + * 构建Model:Access-Token + * @param code 授权码Model + * @return AccessToken Model + */ + public AccessTokenModel generateAccessToken(String code); + + /** + * 刷新Model:根据 Refresh-Token 生成一个新的 Access-Token + * @param refreshToken Refresh-Token值 + * @return 新的 Access-Token + */ + public AccessTokenModel refreshAccessToken(String refreshToken); + + /** + * 构建Model:Access-Token (根据RequestAuthModel构建,用于隐藏式 and 密码式) + * @param ra 请求参数Model + * @param isCreateRt 是否生成对应的Refresh-Token + * @return Access-Token Model + */ + public AccessTokenModel generateAccessToken(RequestAuthModel ra, boolean isCreateRt); + + /** + * 构建Model:Client-Token + * @param clientId 应用id + * @param scopes 授权范围 + * @return Client-Token Model + */ + public ClientTokenModel generateClientToken(String clientId, List scopes); + + /** + * 构建URL:下放Code URL (Authorization Code 授权码) + * @param redirectUri 下放地址 + * @param code code参数 + * @param state state参数 + * @return 构建完毕的URL + */ + public String buildRedirectUri(String redirectUri, String code, String state); + + /** + * 构建URL:下放Access-Token URL (implicit 隐藏式) + * @param redirectUri 下放地址 + * @param token token + * @param state state参数 + * @return 构建完毕的URL + */ + public String buildImplicitRedirectUri(String redirectUri, String token, String state); + + /** + * 回收 Access-Token + * @param accessToken Access-Token值 + */ + public void revokeAccessToken(String accessToken); + +} diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java new file mode 100644 index 00000000..b4a114a3 --- /dev/null +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java @@ -0,0 +1,274 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.dev33.satoken.oauth2.data.generate; + +import cn.dev33.satoken.oauth2.SaOAuth2Manager; +import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts; +import cn.dev33.satoken.oauth2.dao.SaOAuth2Dao; +import cn.dev33.satoken.oauth2.data.model.*; +import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode; +import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; +import cn.dev33.satoken.util.SaFoxUtil; + +import java.util.List; + +/** + * Sa-Token OAuth2 数据构建器,默认实现类 + * + * @author click33 + * @since 1.39.0 + */ +public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { + // ------------------- generate 构建数据 + + /** + * 构建Model:Code授权码 + * @param ra 请求参数Model + * @return 授权码Model + */ + @Override + public CodeModel generateCode(RequestAuthModel ra) { + + SaOAuth2Dao dao = SaOAuth2Manager.getDao(); + + // 删除旧Code + dao.deleteCode(dao.getCodeValue(ra.clientId, ra.loginId)); + + // 生成新Code + String codeValue = SaOAuth2Manager.getDataLoader().randomCode(ra.clientId, ra.loginId, ra.scopes); + CodeModel cm = new CodeModel(codeValue, ra.clientId, ra.scopes, ra.loginId, ra.redirectUri); + + // 保存新Code + dao.saveCode(cm); + dao.saveCodeIndex(cm); + + // 返回 + return cm; + } + + /** + * 构建Model:Access-Token + * @param code 授权码Model + * @return AccessToken Model + */ + @Override + public AccessTokenModel generateAccessToken(String code) { + + SaOAuth2Dao dao = SaOAuth2Manager.getDao(); + + // 1、先校验 + CodeModel cm = dao.getCode(code); + SaOAuth2Exception.throwBy(cm == null, "无效code", SaOAuth2ErrorCode.CODE_30110); + + // 2、删除旧Token + dao.deleteAccessToken(dao.getAccessTokenValue(cm.clientId, cm.loginId)); + dao.deleteRefreshToken(dao.getRefreshTokenValue(cm.clientId, cm.loginId)); + + // 3、生成token + AccessTokenModel at = SaOAuth2Manager.getDataConverter().convertCodeToAccessToken(cm); + RefreshTokenModel rt = SaOAuth2Manager.getDataConverter().convertAccessTokenToRefreshToken(at); + at.refreshToken = rt.refreshToken; + at.refreshExpiresTime = rt.expiresTime; + + // 4、保存token + dao.saveAccessToken(at); + dao.saveAccessTokenIndex(at); + dao.saveRefreshToken(rt); + dao.saveRefreshTokenIndex(rt); + + // 5、删除此Code + dao.deleteCode(code); + dao.deleteCodeIndex(cm.clientId, cm.loginId); + + // 6、返回 Access-Token + return at; + } + + /** + * 刷新Model:根据 Refresh-Token 生成一个新的 Access-Token + * @param refreshToken Refresh-Token值 + * @return 新的 Access-Token + */ + @Override + public AccessTokenModel refreshAccessToken(String refreshToken) { + + SaOAuth2Dao dao = SaOAuth2Manager.getDao(); + + // 获取 Refresh-Token 信息 + RefreshTokenModel rt = dao.getRefreshToken(refreshToken); + SaOAuth2Exception.throwBy(rt == null, "无效refresh_token: " + refreshToken, SaOAuth2ErrorCode.CODE_30111); + + // 如果配置了[每次刷新产生新的Refresh-Token] + SaClientModel clientModel = SaOAuth2Manager.getDataLoader().getClientModelNotNull(rt.clientId); + if(clientModel.getIsNewRefresh()) { + // 删除旧 Refresh-Token + dao.deleteRefreshToken(rt.refreshToken); + + // 创建并保持新的 Refresh-Token + rt = SaOAuth2Manager.getDataConverter().convertRefreshTokenToRefreshToken(rt); + dao.saveRefreshToken(rt); + dao.saveRefreshTokenIndex(rt); + } + + // 删除旧 Access-Token + dao.deleteAccessToken(dao.getAccessTokenValue(rt.clientId, rt.loginId)); + + // 生成新 Access-Token + AccessTokenModel at = SaOAuth2Manager.getDataConverter().convertRefreshTokenToAccessToken(rt); + + // 保存新 Access-Token + dao.saveAccessToken(at); + dao.saveAccessTokenIndex(at); + + // 返回新 Access-Token + return at; + } + + /** + * 构建Model:Access-Token (根据RequestAuthModel构建,用于隐藏式 and 密码式) + * @param ra 请求参数Model + * @param isCreateRt 是否生成对应的Refresh-Token + * @return Access-Token Model + */ + @Override + public AccessTokenModel generateAccessToken(RequestAuthModel ra, boolean isCreateRt) { + + SaOAuth2Dao dao = SaOAuth2Manager.getDao(); + + // 1、删除 旧Token + dao.deleteAccessToken(dao.getAccessTokenValue(ra.clientId, ra.loginId)); + if(isCreateRt) { + dao.deleteRefreshToken(dao.getRefreshTokenValue(ra.clientId, ra.loginId)); + } + + // 2、生成 新Access-Token + String newAtValue = SaOAuth2Manager.getDataLoader().randomAccessToken(ra.clientId, ra.loginId, ra.scopes); + AccessTokenModel at = new AccessTokenModel(newAtValue, ra.clientId, ra.loginId, ra.scopes); + at.openid = SaOAuth2Manager.getDataLoader().getOpenid(ra.clientId, ra.loginId); + SaClientModel clientModel = SaOAuth2Manager.getDataLoader().getClientModelNotNull(ra.clientId); + at.expiresTime = System.currentTimeMillis() + (clientModel.getAccessTokenTimeout() * 1000); + + // 3、生成&保存 Refresh-Token + if(isCreateRt) { + RefreshTokenModel rt = SaOAuth2Manager.getDataConverter().convertAccessTokenToRefreshToken(at); + dao.saveRefreshToken(rt); + dao.saveRefreshTokenIndex(rt); + } + + // 5、保存 新Access-Token + dao.saveAccessToken(at); + dao.saveAccessTokenIndex(at); + + // 6、返回 新Access-Token + return at; + } + + /** + * 构建Model:Client-Token + * @param clientId 应用id + * @param scopes 授权范围 + * @return Client-Token Model + */ + @Override + public ClientTokenModel generateClientToken(String clientId, List scopes) { + + SaOAuth2Dao dao = SaOAuth2Manager.getDao(); + + // 1、删掉旧 Past-Token + dao.deleteClientToken(dao.getPastTokenValue(clientId)); + + // 2、将旧Client-Token 标记为新 Past-Token + ClientTokenModel oldCt = dao.getClientToken(dao.getClientTokenValue(clientId)); + dao.savePastTokenIndex(oldCt); + + // 2.5、如果配置了 PastClientToken 的 ttl ,则需要更新一下 + SaClientModel cm = SaOAuth2Manager.getDataLoader().getClientModelNotNull(clientId); + if(oldCt != null && cm.getPastClientTokenTimeout() != -1) { + oldCt.expiresTime = System.currentTimeMillis() + (cm.getPastClientTokenTimeout() * 1000); + dao.saveClientToken(oldCt); + } + + // 3、生成新Client-Token + ClientTokenModel ct = new ClientTokenModel(SaOAuth2Manager.getDataLoader().randomClientToken(clientId, scopes), clientId, scopes); + ct.expiresTime = System.currentTimeMillis() + (cm.getClientTokenTimeout() * 1000); + + // 3、保存新Client-Token + dao.saveClientToken(ct); + dao.saveClientTokenIndex(ct); + + // 4、返回 + return ct; + } + + /** + * 构建URL:下放Code URL (Authorization Code 授权码) + * @param redirectUri 下放地址 + * @param code code参数 + * @param state state参数 + * @return 构建完毕的URL + */ + @Override + public String buildRedirectUri(String redirectUri, String code, String state) { + String url = SaFoxUtil.joinParam(redirectUri, SaOAuth2Consts.Param.code, code); + if( ! SaFoxUtil.isEmpty(state)) { + url = SaFoxUtil.joinParam(url, SaOAuth2Consts.Param.state, state); + } + return url; + } + + /** + * 构建URL:下放Access-Token URL (implicit 隐藏式) + * @param redirectUri 下放地址 + * @param token token + * @param state state参数 + * @return 构建完毕的URL + */ + @Override + public String buildImplicitRedirectUri(String redirectUri, String token, String state) { + String url = SaFoxUtil.joinSharpParam(redirectUri, SaOAuth2Consts.Param.token, token); + if( ! SaFoxUtil.isEmpty(state)) { + url = SaFoxUtil.joinSharpParam(url, SaOAuth2Consts.Param.state, state); + } + return url; + } + + /** + * 回收 Access-Token + * @param accessToken Access-Token值 + */ + @Override + public void revokeAccessToken(String accessToken) { + + SaOAuth2Dao dao = SaOAuth2Manager.getDao(); + + // 如果查不到任何东西, 直接返回 + AccessTokenModel at = dao.getAccessToken(accessToken); + if(at == null) { + return; + } + + // 删除 Access-Token + dao.deleteAccessToken(accessToken); + dao.deleteAccessTokenIndex(at.clientId, at.loginId); + + // 删除对应的 Refresh-Token + String refreshToken = dao.getRefreshTokenValue(at.clientId, at.loginId); + dao.deleteRefreshToken(refreshToken); + dao.deleteRefreshTokenIndex(at.clientId, at.loginId); + } + +} + diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/loader/SaOAuth2DataLoader.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/loader/SaOAuth2DataLoader.java index c9c5f75e..a94ee43c 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/loader/SaOAuth2DataLoader.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/loader/SaOAuth2DataLoader.java @@ -16,6 +16,10 @@ package cn.dev33.satoken.oauth2.data.loader; import cn.dev33.satoken.oauth2.data.model.SaClientModel; +import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; +import cn.dev33.satoken.util.SaFoxUtil; + +import java.util.List; /** * Sa-Token OAuth2 数据加载器 @@ -47,5 +51,64 @@ public interface SaOAuth2DataLoader { } + /** + * 根据 id 获取 Client 信息,不允许为 null + * + * @param clientId 应用id + * @return ClientModel + */ + default SaClientModel getClientModelNotNull(String clientId) { + SaClientModel clientModel = getClientModel(clientId); + if(clientModel == null) { + throw new SaOAuth2Exception("未找到对应的 Client 信息"); + } + return clientModel; + } + + + // ------------------- 创建对应 token 的算法 + + /** + * 随机一个 Code + * @param clientId 应用id + * @param loginId 账号id + * @param scopes 权限 + * @return Code + */ + default String randomCode(String clientId, Object loginId, List scopes) { + return SaFoxUtil.getRandomString(60); + } + + /** + * 随机一个 Access-Token + * @param clientId 应用id + * @param loginId 账号id + * @param scopes 权限 + * @return Access-Token + */ + default String randomAccessToken(String clientId, Object loginId, List scopes) { + return SaFoxUtil.getRandomString(60); + } + + /** + * 随机一个 Refresh-Token + * @param clientId 应用id + * @param loginId 账号id + * @param scopes 权限 + * @return Refresh-Token + */ + default String randomRefreshToken(String clientId, Object loginId, List scopes) { + return SaFoxUtil.getRandomString(60); + } + + /** + * 随机一个 Client-Token + * @param clientId 应用id + * @param scopes 权限 + * @return Client-Token + */ + default String randomClientToken(String clientId, List scopes) { + return SaFoxUtil.getRandomString(60); + } } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java index cd7641a2..b8c5286c 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java @@ -74,6 +74,7 @@ public class SaOAuth2DataResolverDefaultImpl implements SaOAuth2DataResolver { /** * 构建返回值: 获取 token */ + @Override public Map buildTokenReturnValue(AccessTokenModel at) { Map map = new LinkedHashMap<>(); map.put("token_type", TokenType.bearer); diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java index fccf246d..1a70e1c4 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java @@ -164,15 +164,15 @@ public class SaOAuth2ServerProcessor { // 6、判断授权类型 // 如果是 授权码式,则:开始重定向授权,下放code if(ResponseType.code.equals(ra.responseType)) { - CodeModel codeModel = oauth2Template.generateCode(ra); - String redirectUri = oauth2Template.buildRedirectUri(ra.redirectUri, codeModel.code, ra.state); + CodeModel codeModel = SaOAuth2Manager.getDataGenerate().generateCode(ra); + String redirectUri = SaOAuth2Manager.getDataGenerate().buildRedirectUri(ra.redirectUri, codeModel.code, ra.state); return res.redirect(redirectUri); } // 如果是 隐藏式,则:开始重定向授权,下放 token if(ResponseType.token.equals(ra.responseType)) { - AccessTokenModel at = oauth2Template.generateAccessToken(ra, false); - String redirectUri = oauth2Template.buildImplicitRedirectUri(ra.redirectUri, at.accessToken, ra.state); + AccessTokenModel at = SaOAuth2Manager.getDataGenerate().generateAccessToken(ra, false); + String redirectUri = SaOAuth2Manager.getDataGenerate().buildImplicitRedirectUri(ra.redirectUri, at.accessToken, ra.state); return res.redirect(redirectUri); } @@ -199,7 +199,7 @@ public class SaOAuth2ServerProcessor { oauth2Template.checkGainTokenParam(code, clientId, clientSecret, redirectUri); // 构建 Access-Token - AccessTokenModel accessTokenModel = oauth2Template.generateAccessToken(code); + AccessTokenModel accessTokenModel = SaOAuth2Manager.getDataGenerate().generateAccessToken(code); // 返回 return SaOAuth2Manager.getDataResolver().buildTokenReturnValue(accessTokenModel); @@ -224,7 +224,7 @@ public class SaOAuth2ServerProcessor { oauth2Template.checkRefreshTokenParam(clientId, clientSecret, refreshToken); // 获取新 Access-Token - AccessTokenModel accessTokenModel = oauth2Template.refreshAccessToken(refreshToken); + AccessTokenModel accessTokenModel = SaOAuth2Manager.getDataGenerate().refreshAccessToken(refreshToken); // 返回 return SaOAuth2Manager.getDataResolver().buildRefreshTokenReturnValue(accessTokenModel); @@ -253,7 +253,7 @@ public class SaOAuth2ServerProcessor { oauth2Template.checkAccessTokenParam(clientId, clientSecret, accessToken); // 回收 Access-Token - oauth2Template.revokeAccessToken(accessToken); + SaOAuth2Manager.getDataGenerate().revokeAccessToken(accessToken); // 返回 return SaOAuth2Manager.getDataResolver().buildRevokeTokenReturnValue(); @@ -324,7 +324,7 @@ public class SaOAuth2ServerProcessor { ra.scopes = scopes; // 5、生成 Access-Token - AccessTokenModel at = oauth2Template.generateAccessToken(ra, true); + AccessTokenModel at = SaOAuth2Manager.getDataGenerate().generateAccessToken(ra, true); // 6、返回 Access-Token return SaOAuth2Manager.getDataResolver().buildPasswordReturnValue(at); @@ -352,7 +352,7 @@ public class SaOAuth2ServerProcessor { oauth2Template.checkClientSecret(clientId, clientSecret); // 生成 - ClientTokenModel ct = oauth2Template.generateClientToken(clientId, scopes); + ClientTokenModel ct = SaOAuth2Manager.getDataGenerate().generateClientToken(clientId, scopes); // 返回 return SaOAuth2Manager.getDataResolver().buildClientTokenReturnValue(ct); diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java index df9fc99f..dda7398e 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java @@ -148,225 +148,6 @@ public class SaOAuth2Template { ra.loginId = loginId; return ra; } - /** - * 构建Model:Code授权码 - * @param ra 请求参数Model - * @return 授权码Model - */ - public CodeModel generateCode(RequestAuthModel ra) { - - SaOAuth2Dao dao = SaOAuth2Manager.getDao(); - - // 删除旧Code - String oldCodeValue = SaOAuth2Manager.getDao().getCodeValue(ra.clientId, ra.loginId); - dao.deleteCode(oldCodeValue); - - // 生成新Code - String codeValue = randomCode(ra.clientId, ra.loginId, ra.scopes); - CodeModel cm = new CodeModel(codeValue, ra.clientId, ra.scopes, ra.loginId, ra.redirectUri); - - // 保存新Code - dao.saveCode(cm); - dao.saveCodeIndex(cm); - - // 返回 - return cm; - } - /** - * 构建Model:Access-Token - * @param code 授权码Model - * @return AccessToken Model - */ - public AccessTokenModel generateAccessToken(String code) { - - SaOAuth2Dao dao = SaOAuth2Manager.getDao(); - - // 1、先校验 - CodeModel cm = dao.getCode(code); - SaOAuth2Exception.throwBy(cm == null, "无效code", SaOAuth2ErrorCode.CODE_30110); - - // 2、删除旧Token - dao.deleteAccessToken(dao.getAccessTokenValue(cm.clientId, cm.loginId)); - dao.deleteRefreshToken(dao.getRefreshTokenValue(cm.clientId, cm.loginId)); - - // 3、生成token - AccessTokenModel at = convertCodeToAccessToken(cm); - RefreshTokenModel rt = convertAccessTokenToRefreshToken(at); - at.refreshToken = rt.refreshToken; - at.refreshExpiresTime = rt.expiresTime; - - // 4、保存token - dao.saveAccessToken(at); - dao.saveAccessTokenIndex(at); - dao.saveRefreshToken(rt); - dao.saveRefreshTokenIndex(rt); - - // 5、删除此Code - dao.deleteCode(code); - dao.deleteCodeIndex(cm.clientId, cm.loginId); - - // 6、返回 Access-Token - return at; - } - /** - * 刷新Model:根据 Refresh-Token 生成一个新的 Access-Token - * @param refreshToken Refresh-Token值 - * @return 新的 Access-Token - */ - public AccessTokenModel refreshAccessToken(String refreshToken) { - - SaOAuth2Dao dao = SaOAuth2Manager.getDao(); - - // 获取 Refresh-Token 信息 - RefreshTokenModel rt = dao.getRefreshToken(refreshToken); - SaOAuth2Exception.throwBy(rt == null, "无效refresh_token: " + refreshToken, SaOAuth2ErrorCode.CODE_30111); - - // 如果配置了[每次刷新产生新的Refresh-Token] - if(checkClientModel(rt.clientId).getIsNewRefresh()) { - // 删除旧 Refresh-Token - dao.deleteRefreshToken(rt.refreshToken); - - // 创建并保持新的 Refresh-Token - rt = convertRefreshTokenToRefreshToken(rt); - dao.saveRefreshToken(rt); - dao.saveRefreshTokenIndex(rt); - } - - // 删除旧 Access-Token - dao.deleteAccessToken(dao.getAccessTokenValue(rt.clientId, rt.loginId)); - - // 生成新 Access-Token - AccessTokenModel at = convertRefreshTokenToAccessToken(rt); - - // 保存新 Access-Token - dao.saveAccessToken(at); - dao.saveAccessTokenIndex(at); - - // 返回新 Access-Token - return at; - } - /** - * 构建Model:Access-Token (根据RequestAuthModel构建,用于隐藏式 and 密码式) - * @param ra 请求参数Model - * @param isCreateRt 是否生成对应的Refresh-Token - * @return Access-Token Model - */ - public AccessTokenModel generateAccessToken(RequestAuthModel ra, boolean isCreateRt) { - - SaOAuth2Dao dao = SaOAuth2Manager.getDao(); - - // 1、删除 旧Token - dao.deleteAccessToken(dao.getAccessTokenValue(ra.clientId, ra.loginId)); - if(isCreateRt) { - dao.deleteRefreshToken(dao.getRefreshTokenValue(ra.clientId, ra.loginId)); - } - - // 2、生成 新Access-Token - String newAtValue = randomAccessToken(ra.clientId, ra.loginId, ra.scopes); - AccessTokenModel at = new AccessTokenModel(newAtValue, ra.clientId, ra.loginId, ra.scopes); - at.openid = getOpenid(ra.clientId, ra.loginId); - at.expiresTime = System.currentTimeMillis() + (checkClientModel(ra.clientId).getAccessTokenTimeout() * 1000); - - // 3、生成&保存 Refresh-Token - if(isCreateRt) { - RefreshTokenModel rt = convertAccessTokenToRefreshToken(at); - dao.saveRefreshToken(rt); - dao.saveRefreshTokenIndex(rt); - } - - // 5、保存 新Access-Token - dao.saveAccessToken(at); - dao.saveAccessTokenIndex(at); - - // 6、返回 新Access-Token - return at; - } - /** - * 构建Model:Client-Token - * @param clientId 应用id - * @param scopes 授权范围 - * @return Client-Token Model - */ - public ClientTokenModel generateClientToken(String clientId, List scopes) { - - SaOAuth2Dao dao = SaOAuth2Manager.getDao(); - - // 1、删掉旧 Past-Token - dao.deleteClientToken(dao.getPastTokenValue(clientId)); - - // 2、将旧Client-Token 标记为新 Past-Token - ClientTokenModel oldCt = dao.getClientToken(dao.getClientTokenValue(clientId)); - dao.savePastTokenIndex(oldCt); - - // 2.5、如果配置了 PastClientToken 的 ttl ,则需要更新一下 - SaClientModel cm = checkClientModel(clientId); - if(oldCt != null && cm.getPastClientTokenTimeout() != -1) { - oldCt.expiresTime = System.currentTimeMillis() + (cm.getPastClientTokenTimeout() * 1000); - dao.saveClientToken(oldCt); - } - - // 3、生成新Client-Token - ClientTokenModel ct = new ClientTokenModel(randomClientToken(clientId, scopes), clientId, scopes); - ct.expiresTime = System.currentTimeMillis() + (cm.getClientTokenTimeout() * 1000); - - // 3、保存新Client-Token - dao.saveClientToken(ct); - dao.saveClientTokenIndex(ct); - - // 4、返回 - return ct; - } - /** - * 构建URL:下放Code URL (Authorization Code 授权码) - * @param redirectUri 下放地址 - * @param code code参数 - * @param state state参数 - * @return 构建完毕的URL - */ - public String buildRedirectUri(String redirectUri, String code, String state) { - String url = SaFoxUtil.joinParam(redirectUri, Param.code, code); - if( ! SaFoxUtil.isEmpty(state)) { - url = SaFoxUtil.joinParam(url, Param.state, state); - } - return url; - } - /** - * 构建URL:下放Access-Token URL (implicit 隐藏式) - * @param redirectUri 下放地址 - * @param token token - * @param state state参数 - * @return 构建完毕的URL - */ - public String buildImplicitRedirectUri(String redirectUri, String token, String state) { - String url = SaFoxUtil.joinSharpParam(redirectUri, Param.token, token); - if( ! SaFoxUtil.isEmpty(state)) { - url = SaFoxUtil.joinSharpParam(url, Param.state, state); - } - return url; - } - /** - * 回收 Access-Token - * @param accessToken Access-Token值 - */ - public void revokeAccessToken(String accessToken) { - - SaOAuth2Dao dao = SaOAuth2Manager.getDao(); - - // 如果查不到任何东西, 直接返回 - AccessTokenModel at = dao.getAccessToken(accessToken); - if(at == null) { - return; - } - - // 删除 Access-Token - dao.deleteAccessToken(accessToken); - dao.deleteAccessTokenIndex(at.clientId, at.loginId); - - // 删除对应的 Refresh-Token - String refreshToken = dao.getRefreshTokenValue(at.clientId, at.loginId); - dao.deleteRefreshToken(refreshToken); - dao.deleteRefreshTokenIndex(at.clientId, at.loginId); - } // ------------------- check 数据校验 /** @@ -586,115 +367,6 @@ public class SaOAuth2Template { return at; } - // ------------------- convert 数据转换 - /** - * 将 Code 转换为 Access-Token - * @param cm CodeModel对象 - * @return AccessToken对象 - */ - public AccessTokenModel convertCodeToAccessToken(CodeModel cm) { - AccessTokenModel at = new AccessTokenModel(); - at.accessToken = randomAccessToken(cm.clientId, cm.loginId, cm.scopes); - // at.refreshToken = randomRefreshToken(cm.clientId, cm.loginId, cm.scope); - at.clientId = cm.clientId; - at.loginId = cm.loginId; - at.scopes = cm.scopes; - at.openid = getOpenid(cm.clientId, cm.loginId); - at.expiresTime = System.currentTimeMillis() + (checkClientModel(cm.clientId).getAccessTokenTimeout() * 1000); - // at.refreshExpiresTime = System.currentTimeMillis() + (checkClientModel(cm.clientId).getRefreshTokenTimeout() * 1000); - return at; - } - /** - * 将 Access-Token 转换为 Refresh-Token - * @param at . - * @return . - */ - public RefreshTokenModel convertAccessTokenToRefreshToken(AccessTokenModel at) { - RefreshTokenModel rt = new RefreshTokenModel(); - rt.refreshToken = randomRefreshToken(at.clientId, at.loginId, at.scopes); - rt.clientId = at.clientId; - rt.loginId = at.loginId; - rt.scopes = at.scopes; - rt.openid = at.openid; - rt.expiresTime = System.currentTimeMillis() + (checkClientModel(at.clientId).getRefreshTokenTimeout() * 1000); - // 改变at属性 - at.refreshToken = rt.refreshToken; - at.refreshExpiresTime = rt.expiresTime; - return rt; - } - /** - * 将 Refresh-Token 转换为 Access-Token - * @param rt . - * @return . - */ - public AccessTokenModel convertRefreshTokenToAccessToken(RefreshTokenModel rt) { - AccessTokenModel at = new AccessTokenModel(); - at.accessToken = randomAccessToken(rt.clientId, rt.loginId, rt.scopes); - at.refreshToken = rt.refreshToken; - at.clientId = rt.clientId; - at.loginId = rt.loginId; - at.scopes = rt.scopes; - at.openid = rt.openid; - at.expiresTime = System.currentTimeMillis() + (checkClientModel(rt.clientId).getAccessTokenTimeout() * 1000); - at.refreshExpiresTime = rt.expiresTime; - return at; - } - /** - * 根据 Refresh-Token 创建一个新的 Refresh-Token - * @param rt . - * @return . - */ - public RefreshTokenModel convertRefreshTokenToRefreshToken(RefreshTokenModel rt) { - RefreshTokenModel newRt = new RefreshTokenModel(); - newRt.refreshToken = randomRefreshToken(rt.clientId, rt.loginId, rt.scopes); - newRt.expiresTime = System.currentTimeMillis() + (checkClientModel(rt.clientId).getRefreshTokenTimeout() * 1000); - newRt.clientId = rt.clientId; - newRt.scopes = rt.scopes; - newRt.loginId = rt.loginId; - newRt.openid = rt.openid; - return newRt; - } - - // ------------------- Random数据 - /** - * 随机一个 Code - * @param clientId 应用id - * @param loginId 账号id - * @param scopes 权限 - * @return Code - */ - public String randomCode(String clientId, Object loginId, List scopes) { - return SaFoxUtil.getRandomString(60); - } - /** - * 随机一个 Access-Token - * @param clientId 应用id - * @param loginId 账号id - * @param scopes 权限 - * @return Access-Token - */ - public String randomAccessToken(String clientId, Object loginId, List scopes) { - return SaFoxUtil.getRandomString(60); - } - /** - * 随机一个 Refresh-Token - * @param clientId 应用id - * @param loginId 账号id - * @param scopes 权限 - * @return Refresh-Token - */ - public String randomRefreshToken(String clientId, Object loginId, List scopes) { - return SaFoxUtil.getRandomString(60); - } - /** - * 随机一个 Client-Token - * @param clientId 应用id - * @param scopes 权限 - * @return Client-Token - */ - public String randomClientToken(String clientId, List scopes) { - return SaFoxUtil.getRandomString(60); - } // ------------------- 包装其它 bean 的方法 diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java index beb1b3ac..44478769 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java @@ -97,84 +97,7 @@ public class SaOAuth2Util { public static RequestAuthModel generateRequestAuth(SaRequest req, Object loginId) { return SaOAuth2ServerProcessor.instance.oauth2Template.generateRequestAuth(req, loginId); } - - /** - * 构建Model:Code授权码 - * @param ra 请求参数Model - * @return 授权码Model - */ - public static CodeModel generateCode(RequestAuthModel ra) { - return SaOAuth2ServerProcessor.instance.oauth2Template.generateCode(ra); - } - - /** - * 构建Model:Access-Token - * @param code 授权码Model - * @return AccessToken Model - */ - public static AccessTokenModel generateAccessToken(String code) { - return SaOAuth2ServerProcessor.instance.oauth2Template.generateAccessToken(code); - } - /** - * 刷新Model:根据 Refresh-Token 生成一个新的 Access-Token - * @param refreshToken Refresh-Token值 - * @return 新的 Access-Token - */ - public static AccessTokenModel refreshAccessToken(String refreshToken) { - return SaOAuth2ServerProcessor.instance.oauth2Template.refreshAccessToken(refreshToken); - } - - /** - * 构建Model:Access-Token (根据RequestAuthModel构建,用于隐藏式 and 密码式) - * @param ra 请求参数Model - * @param isCreateRt 是否生成对应的Refresh-Token - * @return Access-Token Model - */ - public static AccessTokenModel generateAccessToken(RequestAuthModel ra, boolean isCreateRt) { - return SaOAuth2ServerProcessor.instance.oauth2Template.generateAccessToken(ra, isCreateRt); - } - - /** - * 构建Model:Client-Token - * @param clientId 应用id - * @param scopes 授权范围 - * @return Client-Token Model - */ - public static ClientTokenModel generateClientToken(String clientId, List scopes) { - return SaOAuth2ServerProcessor.instance.oauth2Template.generateClientToken(clientId, scopes); - } - - /** - * 构建URL:下放Code URL (Authorization Code 授权码) - * @param redirectUri 下放地址 - * @param code code参数 - * @param state state参数 - * @return 构建完毕的URL - */ - public static String buildRedirectUri(String redirectUri, String code, String state) { - return SaOAuth2ServerProcessor.instance.oauth2Template.buildRedirectUri(redirectUri, code, state); - } - - /** - * 构建URL:下放Access-Token URL (implicit 隐藏式) - * @param redirectUri 下放地址 - * @param token token - * @param state state参数 - * @return 构建完毕的URL - */ - public static String buildImplicitRedirectUri(String redirectUri, String token, String state) { - return SaOAuth2ServerProcessor.instance.oauth2Template.buildImplicitRedirectUri(redirectUri, token, state); - } - - /** - * 回收 Access-Token - * @param accessToken Access-Token值 - */ - public static void revokeAccessToken(String accessToken) { - SaOAuth2ServerProcessor.instance.oauth2Template.revokeAccessToken(accessToken); - } - // ------------------- 数据校验 /**