• JustAuth扩展:支持自动获得回调域名、使用redission作为Cache


    当前使用的版本:
     

    just_auth_version = '1.16.6'
    just_auth_starter_version = '1.4.0'
    
    "me.zhyd.oauth:JustAuth:${just_auth_version}", //多渠道登录
    "com.xkcoding.justauth:justauth-spring-boot-starter:${just_auth_starter_version}" //启动器
    

    在JustAuth整合过程中,遇到两个功能扩展:
    1)JustAuth默认使用sping-data-redis作为缓存接口,当前系统没有使用该redis驱动,由于已经使用了redission,不希望引入更多的架构。故需要扩展JustAuthCache接口
    2)JustAuth配置的登录回调地址必须写完整的域名。本着从哪里来,回哪里去的原则,回调的域名地址,95%需求都会跟请求的域名地址一致。这样只需要在请求时获得请求的域名地址即可,不用在配置文件里额外配置。让配置文件的redirect-uri真正成为URI而不是URL

    自定义缓存

    自定义缓存按照文档的扩展即可,项目使用redission,如下定义一个CacheBean

    1. package org.ccframe.commons.auth;
    2. import me.zhyd.oauth.cache.AuthStateCache;
    3. import org.ccframe.config.GlobalEx;
    4. import org.redisson.api.RBucket;
    5. import org.redisson.api.RedissonClient;
    6. import org.redisson.client.codec.StringCodec;
    7. import java.util.concurrent.TimeUnit;
    8. /**
    9. * JustAuth三方登录缓存管理.
    10. * @author Jim 2024/03/08
    11. */
    12. public class CcAuthStateCache implements AuthStateCache {
    13. private RedissonClient redissonClient;
    14. public CcAuthStateCache(RedissonClient redissonClient){
    15. this.redissonClient = redissonClient;
    16. }
    17. /**
    18. * 存入缓存
    19. *
    20. * @param key 缓存key
    21. * @param value 缓存内容
    22. */
    23. @Override
    24. public void cache(String key, String value) {
    25. RBucket bucket = redissonClient.getBucket(GlobalEx.CACHEREGION_THIRD_AUTH + key);
    26. bucket.set(value, 10, TimeUnit.MINUTES); // 10分钟还无法三方绑定的登录,则无法进行绑定
    27. }
    28. /**
    29. * 存入缓存
    30. *
    31. * @param key 缓存key
    32. * @param value 缓存内容
    33. * @param timeout 指定缓存过期时间(毫秒)
    34. */
    35. @Override
    36. public void cache(String key, String value, long timeout) {
    37. RBucket bucket = redissonClient.getBucket(GlobalEx.CACHEREGION_THIRD_AUTH + key, StringCodec.INSTANCE);
    38. bucket.set(value, timeout, TimeUnit.MILLISECONDS);
    39. }
    40. /**
    41. * 获取缓存内容
    42. *
    43. * @param key 缓存key
    44. * @return 缓存内容
    45. */
    46. @Override
    47. public String get(String key) {
    48. RBucket bucket = redissonClient.getBucket(GlobalEx.CACHEREGION_THIRD_AUTH + key, StringCodec.INSTANCE);
    49. return bucket.get();
    50. }
    51. /**
    52. * 是否存在key,如果对应key的value值已过期,也返回false
    53. *
    54. * @param key 缓存key
    55. * @return true:存在key,并且value没过期;false:key不存在或者已过期
    56. */
    57. @Override
    58. public boolean containsKey(String key) {
    59. return redissonClient.getBucket(GlobalEx.CACHEREGION_THIRD_AUTH + key).isExists();
    60. }
    61. }

    实现真正的REDIRECT URI

    先看看实现后的效果,整合后,CcFrame的公共配置只需要如下配置:
     

    1. justauth:
    2. enabled: false #原来的关闭掉,因为自己写了个新的CcAuthRequestFactory,使用下面的开关
    3. cc-enabled: true
    4. cache:
    5. type: custom
    6. type:
    7. QQ: #前台登录
    8. client-id: ${app.third-oauth.QQ.client-id:}
    9. client-secret: ${app.third-oauth.QQ.client-secret:}
    10. redirect-uri: /api/common/thirdOauthCallback/qq
    11. union-id: false
    12. GITEE: #后台登录
    13. client-id: ${app.third-oauth.GITEE.client-id:}
    14. client-secret: ${app.third-oauth.GITEE.client-secret:}
    15. redirect-uri: /admin/common/thirdOauthCallback/gitee
    16. DINGTALK: #后台登录
    17. client-id: ${app.third-oauth.DINGTALK.client-id:}
    18. client-secret: ${app.third-oauth.DINGTALK.client-secret:}
    19. redirect-uri: /admin/common/thirdOauthCallback/dingtalk

    redirect-uri不关心回调地址从哪里来,这样便省去了不同项目域名地址之间差异,我只关心URI

    而项目里的配置则更简单:

    1. app:
    2. third-oauth:
    3. GITEE:
    4. client-id: ??
    5. client-secret: ??

    即可针对不同的项目及环境实现接入。

    扩展点如下:
    针对不支持URI的问题,本打算extend AuthRequestFactory的,结果发现大部分方法都private了,不方便扩展。再检查下用法发现引入的地方是在用户代码部分,因此索性复制了重写了

    1. /*
    2. * Copyright (c) 2019-2029, xkcoding & Yangkai.Shen & 沈扬凯 (237497819@qq.com & xkcoding.com).
    3. *

    4. * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
    5. * you may not use this file except in compliance with the License.
    6. * You may obtain a copy of the License at
    7. *

    8. * http://www.gnu.org/licenses/lgpl.html
    9. *

    10. * Unless required by applicable law or agreed to in writing, software
    11. * distributed under the License is distributed on an "AS IS" BASIS,
    12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13. * See the License for the specific language governing permissions and
    14. * limitations under the License.
    15. *
    16. */
    17. package org.ccframe.commons.auth;
    18. import cn.hutool.core.collection.CollUtil;
    19. import cn.hutool.core.util.EnumUtil;
    20. import cn.hutool.core.util.ReflectUtil;
    21. import cn.hutool.core.util.StrUtil;
    22. import com.xkcoding.http.config.HttpConfig;
    23. import com.xkcoding.justauth.autoconfigure.ExtendProperties;
    24. import com.xkcoding.justauth.autoconfigure.JustAuthProperties;
    25. import lombok.RequiredArgsConstructor;
    26. import lombok.extern.slf4j.Slf4j;
    27. import me.zhyd.oauth.cache.AuthStateCache;
    28. import me.zhyd.oauth.config.AuthConfig;
    29. import me.zhyd.oauth.config.AuthDefaultSource;
    30. import me.zhyd.oauth.config.AuthSource;
    31. import me.zhyd.oauth.enums.AuthResponseStatus;
    32. import me.zhyd.oauth.exception.AuthException;
    33. import me.zhyd.oauth.request.*;
    34. import org.springframework.beans.BeanUtils;
    35. import org.springframework.util.CollectionUtils;
    36. import java.net.InetSocketAddress;
    37. import java.net.Proxy;
    38. import java.util.ArrayList;
    39. import java.util.HashMap;
    40. import java.util.List;
    41. import java.util.Map;
    42. import java.util.stream.Collectors;
    43. /**
    44. * private引入太多,直接复制了改了.
    45. *
    46. *

    47. * AuthRequest工厂类
    48. *

    49. *
    50. * @author yangkai.shen
    51. * @date Created in 2019-07-22 14:21
    52. */
    53. @Slf4j
    54. @RequiredArgsConstructor
    55. public class CcAuthRequestFactory {
    56. private final JustAuthProperties properties;
    57. private final AuthStateCache authStateCache;
    58. /**
    59. * 返回当前Oauth列表
    60. *
    61. * @return Oauth列表
    62. */
    63. @SuppressWarnings({"unchecked", "rawtypes"})
    64. public List oauthList() {
    65. // 默认列表
    66. List defaultList = new ArrayList<>(properties.getType().keySet());
    67. // 扩展列表
    68. List extendList = new ArrayList<>();
    69. ExtendProperties extend = properties.getExtend();
    70. if (null != extend) {
    71. Class enumClass = extend.getEnumClass();
    72. List names = EnumUtil.getNames(enumClass);
    73. // 扩展列表
    74. extendList = extend.getConfig()
    75. .keySet()
    76. .stream()
    77. .filter(x -> names.contains(x.toUpperCase()))
    78. .map(String::toUpperCase)
    79. .collect(Collectors.toList());
    80. }
    81. // 合并
    82. return (List) CollUtil.addAll(defaultList, extendList);
    83. }
    84. /**
    85. * 返回AuthRequest对象
    86. *
    87. * @param source {@link AuthSource}
    88. * @param requestBaseUrl redirectUri 请求URI的前缀,这样配置里只需要写URI了
    89. * @return {@link AuthRequest}
    90. */
    91. public AuthRequest get(String source, String requestBaseUrl) {
    92. if (StrUtil.isBlank(source)) {
    93. throw new AuthException(AuthResponseStatus.NO_AUTH_SOURCE);
    94. }
    95. // 获取 JustAuth 中已存在的
    96. AuthRequest authRequest = getDefaultRequest(source, requestBaseUrl);
    97. // 如果获取不到则尝试取自定义的
    98. if (authRequest == null) {
    99. authRequest = getExtendRequest(properties.getExtend().getEnumClass(), source);
    100. }
    101. if (authRequest == null) {
    102. throw new AuthException(AuthResponseStatus.UNSUPPORTED);
    103. }
    104. return authRequest;
    105. }
    106. /**
    107. * 获取自定义的 request
    108. *
    109. * @param clazz 枚举类 {@link AuthSource}
    110. * @param source {@link AuthSource}
    111. * @return {@link AuthRequest}
    112. */
    113. @SuppressWarnings({"unchecked", "rawtypes"})
    114. private AuthRequest getExtendRequest(Class clazz, String source) {
    115. String upperSource = source.toUpperCase();
    116. try {
    117. EnumUtil.fromString(clazz, upperSource);
    118. } catch (IllegalArgumentException e) {
    119. // 无自定义匹配
    120. return null;
    121. }
    122. Map extendConfig = properties.getExtend().getConfig();
    123. // key 转大写
    124. Map upperConfig = new HashMap<>(6);
    125. extendConfig.forEach((k, v) -> upperConfig.put(k.toUpperCase(), v));
    126. ExtendProperties.ExtendRequestConfig extendRequestConfig = upperConfig.get(upperSource);
    127. if (extendRequestConfig != null) {
    128. // 配置 http config
    129. configureHttpConfig(upperSource, extendRequestConfig, properties.getHttpConfig());
    130. Classextends AuthRequest> requestClass = extendRequestConfig.getRequestClass();
    131. if (requestClass != null) {
    132. // 反射获取 Request 对象,所以必须实现 2 个参数的构造方法
    133. return ReflectUtil.newInstance(requestClass, (AuthConfig) extendRequestConfig, authStateCache);
    134. }
    135. }
    136. return null;
    137. }
    138. /**
    139. * 获取默认的 Request
    140. *
    141. * @param source {@link AuthSource}
    142. * @return {@link AuthRequest}
    143. */
    144. private AuthRequest getDefaultRequest(String source, String requestBaseUrl) {
    145. AuthDefaultSource authDefaultSource;
    146. try {
    147. authDefaultSource = EnumUtil.fromString(AuthDefaultSource.class, source.toUpperCase());
    148. } catch (IllegalArgumentException e) {
    149. // 无自定义匹配
    150. return null;
    151. }
    152. AuthConfig config = properties.getType().get(authDefaultSource.name());
    153. // 找不到对应关系,直接返回空
    154. if (config == null) {
    155. return null;
    156. }
    157. if(!config.getRedirectUri().startsWith("http")){ // 克隆并修改回调地址
    158. AuthConfig newConfig = new AuthConfig();
    159. BeanUtils.copyProperties(config, newConfig);
    160. newConfig.setRedirectUri(requestBaseUrl + newConfig.getRedirectUri());
    161. config = newConfig;
    162. }
    163. // 配置 http config
    164. configureHttpConfig(authDefaultSource.name(), config, properties.getHttpConfig());
    165. switch (authDefaultSource) {
    166. case GITHUB:
    167. return new AuthGithubRequest(config, authStateCache);
    168. case WEIBO:
    169. return new AuthWeiboRequest(config, authStateCache);
    170. case GITEE:
    171. return new AuthGiteeRequest(config, authStateCache);
    172. case DINGTALK:
    173. return new AuthDingTalkRequest(config, authStateCache);
    174. case DINGTALK_ACCOUNT:
    175. return new AuthDingTalkAccountRequest(config, authStateCache);
    176. case BAIDU:
    177. return new AuthBaiduRequest(config, authStateCache);
    178. case CSDN:
    179. return new AuthCsdnRequest(config, authStateCache);
    180. case CODING:
    181. return new AuthCodingRequest(config, authStateCache);
    182. case OSCHINA:
    183. return new AuthOschinaRequest(config, authStateCache);
    184. case ALIPAY:
    185. return new AuthAlipayRequest(config, authStateCache);
    186. case QQ:
    187. return new AuthQqRequest(config, authStateCache);
    188. case WECHAT_OPEN:
    189. return new AuthWeChatOpenRequest(config, authStateCache);
    190. case WECHAT_MP:
    191. return new AuthWeChatMpRequest(config, authStateCache);
    192. case WECHAT_ENTERPRISE:
    193. return new AuthWeChatEnterpriseQrcodeRequest(config, authStateCache);
    194. case WECHAT_ENTERPRISE_WEB:
    195. return new AuthWeChatEnterpriseWebRequest(config, authStateCache);
    196. case TAOBAO:
    197. return new AuthTaobaoRequest(config, authStateCache);
    198. case GOOGLE:
    199. return new AuthGoogleRequest(config, authStateCache);
    200. case FACEBOOK:
    201. return new AuthFacebookRequest(config, authStateCache);
    202. case DOUYIN:
    203. return new AuthDouyinRequest(config, authStateCache);
    204. case LINKEDIN:
    205. return new AuthLinkedinRequest(config, authStateCache);
    206. case MICROSOFT:
    207. return new AuthMicrosoftRequest(config, authStateCache);
    208. case MI:
    209. return new AuthMiRequest(config, authStateCache);
    210. case TOUTIAO:
    211. return new AuthToutiaoRequest(config, authStateCache);
    212. case TEAMBITION:
    213. return new AuthTeambitionRequest(config, authStateCache);
    214. case RENREN:
    215. return new AuthRenrenRequest(config, authStateCache);
    216. case PINTEREST:
    217. return new AuthPinterestRequest(config, authStateCache);
    218. case STACK_OVERFLOW:
    219. return new AuthStackOverflowRequest(config, authStateCache);
    220. case HUAWEI:
    221. return new AuthHuaweiRequest(config, authStateCache);
    222. case GITLAB:
    223. return new AuthGitlabRequest(config, authStateCache);
    224. case KUJIALE:
    225. return new AuthKujialeRequest(config, authStateCache);
    226. case ELEME:
    227. return new AuthElemeRequest(config, authStateCache);
    228. case MEITUAN:
    229. return new AuthMeituanRequest(config, authStateCache);
    230. case TWITTER:
    231. return new AuthTwitterRequest(config, authStateCache);
    232. case FEISHU:
    233. return new AuthFeishuRequest(config, authStateCache);
    234. case JD:
    235. return new AuthJdRequest(config, authStateCache);
    236. case ALIYUN:
    237. return new AuthAliyunRequest(config, authStateCache);
    238. case XMLY:
    239. return new AuthXmlyRequest(config, authStateCache);
    240. case AMAZON:
    241. return new AuthAmazonRequest(config, authStateCache);
    242. case SLACK:
    243. return new AuthSlackRequest(config, authStateCache);
    244. case LINE:
    245. return new AuthLineRequest(config, authStateCache);
    246. case OKTA:
    247. return new AuthOktaRequest(config, authStateCache);
    248. default:
    249. return null;
    250. }
    251. }
    252. /**
    253. * 配置 http 相关的配置
    254. *
    255. * @param authSource {@link AuthSource}
    256. * @param authConfig {@link AuthConfig}
    257. */
    258. private void configureHttpConfig(String authSource, AuthConfig authConfig, JustAuthProperties.JustAuthHttpConfig httpConfig) {
    259. if (null == httpConfig) {
    260. return;
    261. }
    262. Map proxyConfigMap = httpConfig.getProxy();
    263. if (CollectionUtils.isEmpty(proxyConfigMap)) {
    264. return;
    265. }
    266. JustAuthProperties.JustAuthProxyConfig proxyConfig = proxyConfigMap.get(authSource);
    267. if (null == proxyConfig) {
    268. return;
    269. }
    270. authConfig.setHttpConfig(HttpConfig.builder()
    271. .timeout(httpConfig.getTimeout())
    272. .proxy(new Proxy(Proxy.Type.valueOf(proxyConfig.getType()), new InetSocketAddress(proxyConfig.getHostname(), proxyConfig.getPort())))
    273. .build());
    274. }
    275. }

    这样一来,实际调用的时候,Autowire CcAuthRequestFactory而不是AuthRequestFactory。而在get方法的时候,增加了requestBaseUrl调用用来自动添加前缀地址。requestBaseUrl则是从Controller请求自动从前端请求抓取而来:
     

    1. @Autowired
    2. private CcAuthRequestFactory factory;
    3. @GetMapping(value = "thirdOauth")
    4. @ApiOperation(value = "三方登录并跳转")
    5. @SneakyThrows
    6. public void thirdOauth(String type,@ApiIgnore RequestSite adminSite, HttpServletResponse response){ //跳转三方登录,换取code等
    7. AuthRequest authRequest = factory.get(type, adminSite.getRequestBaseUrl());
    8. response.setStatus(302); //支持HTTP重定向到HTTPS
    9. response.sendRedirect(authRequest.authorize(AuthStateUtils.createState()));
    10. }

    BTW.三方登录的请求开关也被改了,使用cc-enabled而不是enabled,避免将原来的RequestFactory也自动初始化

    至于RequestSite adminSite如何自动注入的请看我的另一篇文章,你注入ServletHttpRequest去实现一样。

    这样便支持了前面配置文件里的直接书写URI,URL则是调用Controller的请求域名及IP:
    redirect-uri: /api/common/thirdOauthCallback/qq

    配置省事了,省一点算一点,优秀的系统必须是一点点简化而来的

  • 相关阅读:
    【康耐视国产案例】智能AI相机机器视觉精准快速实现包裹标签的智能粘贴
    vue-自适应布局-postcss-pxtorem
    【Webpack】CSS 处理
    ChatGPT 最近一年的发展情况回顾、以及我们关心的数据安全问题
    【Python机器学习】零基础掌握ShrunkCovariance协方差估计
    C++ getline():从文件中读取一行字符串
    Upstream Consistent Hash
    驱动开发:内核封装WSK网络通信接口
    Maven:一个下载jar依赖失败的问题解决方案
    Vue中实现div的任意移动
  • 原文地址:https://blog.csdn.net/applebomb/article/details/136599256