OAuth 2.0(四):手把手带你写代码接入 OAuth 2.0 授权服务

今天我们开始落地写代码,基于橘长之前接入过农业银行的授权,今天首先作为第三方服务来和大家分享「 手把手接入 OAuth 2.0 授权服务 」。

一、业务背景

近期团队帮银行做了一个互动营销活动,活动入口在行方的 App 上,当用户在行方 App 点击活动 banner 页跳转活动的时候参与。

在进活动之前作为业务方自然需要知道参与活动的人是谁,如何给它构建登录态。

这就是为什么橘长这边需要接入 行方 OAuth 2.0 组件的原因,本质就是获取 客户信息,回到活动业务形成登录态,进而可以参与活动。

使用的是 OAuth 2.0 中最完备的 授权许可机制 的这种接入方式,服务端发起型授权,为了方便展示,大部分采用了硬编码。

二、作为第三方软件需要做什么

1、静态注册

也可以称之为备案(注册信息) ,需要在 行方 的开放平台的管理态申请 OAuth 2.0 接入客户端,对于行方来说,确保第三方软件是可信的。

准备信息:第三方应用服务端 ip 地址、第三方应用回调通知地址、申请权限等。

String ip = "xxx.xx.xx.xx";
String callBackUrl = "https://xxx.xxx.xx/oauth/login/success"; 

等授权服务的后台人员处理之后会颁发相关配置,用来表示唯一标识 第三方软件 的相关配置。

如下是一个简单示例模板:

String appid = "appid_001";
String appSecret = "appSecret_001";
String scope = "user_info"; 

2、引导用户授权

1)第一步:用户访问第三方软件,判断凭据

如果没有携带 JWT 或 已过期,服务端响应 Http 状态码 401 给前端,前端会请求服务端的发起授权接口。

// 比方说访问活动首页接口
curl https://xxx.xx.xx/api/activity/act-01/index;// 服务端响应 401
{"message": "用户会话已过期,请重新登录!","statusCode": 401
} 

2)第二步:客户端收到 401,请求授权接口

第三方软件后端会和授权服务交互,获取授权页地址,然后 302 跳转引导用户到授权页。

  • controller 层
@Slf4j
@RequestMapping("/oauth")
@RestController
public class OAuthController {@GetMapping("/login")public RedirectView login(final String redirect) {String sceneId = IdUtil.simpleUUID();JSONObject sceneInfo = JSONUtil.createObj().putOnce(OAuthConstant.REDIRECT_FOR_FRONT, redirect);// 缓存前端通知地址cache.set(OAuthConstant.KEY_PRE_OAUTH_SCENE + sceneId, sceneInfo, 300);String oauthUrl = oAuthService.buildOauthUrl(sceneId, callbackUrl);log.info("[发起 xxx 行方 OAuth 授权] 构建OAuth url为:[{}]", oauthUrl);// 302 跳转return new RedirectView(oauthUrl);}
} 

日志打印:

xxxx [发起授权] 构建的授权地址为:[http://xxx/oauth/authorize?client_id=xxx&redirect_uri=http%3A%2F%2Fxxx%2F%2Foauth%2Flogin%2Fsuccess&state=d8cb3943cd3a45818711fa4f6a8820e9&scope=custid%2Cphone&response_type=code] 
  • service 实现
@Override
public String buildOauthUrl(final String sceneId) {// 回调地址String callbackUrl = applicationConfig.getAppUrl() + "/oauth/login/success";// 带参String notifyUrl = UrlBuilder.of(callbackUrl, CharsetUtil.CHARSET_UTF_8).addQuery("sceneId", sceneId).build(); return xxxOAuthService.buildOauthUrl(notifyUrl);
} 

service 这层根据标准 OAuth 2.0 的要求更合理做法是 请求授权服务获取,这里授权服务设计有点不合理,后续调整。

3)第三步:授权服务回调通知,分发临时授权码

@RequestMapping("/login/success")
public RedirectView loginSuccess(final String sceneId,  final String code) { log.info("[xxx 行方授权回调通知] sceneId:[{}], code:[{}]", sceneId, code);// 后续业务操作
} 

日志打印:

xxxx [oauth 服务]-回调,接收到数据为:code:[xxx], state:[xxx] 

4)第四步:第三方服务通过 code 换取 token

private String getToken(final String clientId, final String clientSecret, final String code) {JSONObject tokenJson = this.getTokenFromOAuthServer(clientId, clientSecret, code, redirectUri);String accessToken = tokenJson.getStr("access_token");if (!JSONUtil.isNull(tokenJson) && StrUtil.isNotEmpty(accessToken)) {return accessToken;}throw new RuntimeException("token获取异常!");
}/*** 从 授权服务 获取 token** @author huangyin*/
private JSONObject getTokenFromOAuthServer(final String clientId,  final String clientSecret,  final String code,  final String redirectUri) { // 请求资源地址 String requertUrl = oauthServerConfig.getBaseUrl() + "/oauth/token";// 构建请求参数Map<String, Object> formMap = new HashMap<>(5);// 授权码许可模式formMap.put("grant_type", "authorization_code");formMap.put("client_id", clientId);formMap.put("client_secret", clientSecret);formMap.put("code", code);// http 请求JSONObject response = this.doPostFormData(requertUrl, formMap);log.info("[从授权服务获取 token] 结果为:[{}]", response);return response;
}/*** 抽离 post 请求方法,form-data 传参** @author huangyin*/
private JSONObject doPostFormData(final String sourceUrl, final Map<String, Object> formArgs) {try {// 采用 开源工具 hutoolString response = HttpRequest.post(sourceUrl).form(formArgs).timeout(3000).execute().body();log.info("[从授权服务post form请求] 请求地址:[{}],请求参数:[{}],原始响应:[{}]", sourceUrl, formArgs, response);if (JSONUtil.isJson(response)) {// 依据授权服务 api response 定义做结果处理JSONObject responseJson = JSONUtil.parseObj(response);String code = responseJson.getStr("code");JSONObject data = responseJson.getJSONObject("data");if ("0000".equals(code) && !JSONUtil.isNull(data)) {return data;}}} catch (Exception e) {log.error("[从授权服务 post form 请求] 异常,请求地址:[{}],参数:[{}],异常信息:[{}]", sourceUrl, formArgs, e.getMessage());}throw new RuntimeException("授权服务异常!");
} 

打印日志:

xxxx [授权服务 code 获取 token] 结果为:[{"access_token":"xxx","expires_in":7200}] 

5)第五步:拿到 凭据,访问业务接口

当用授权码换取到 凭据 之后,通过凭据去获取用户在受保护资源服务的数据,比方说获取用户信息。

public String getUserInfoFromOAuthServer(String token) {String sourceUrl = oauthServerConfig.getBaseUrl() + "/oauth/userInfo";try {// header 头部方式提交 凭据String response = HttpRequest.post(sourceUrl).header("Authorization", "Bearer " + accessToken).timeout(3000).execute().body();log.info("[从授权服务 post 请求获取用户信息] 请求地址:[{}],原始响应:[{}]", sourceUrl, response);if (JSONUtil.isJson(response)) {// 依据授权服务 api response 定义做结果处理JSONObject responseJson = JSONUtil.parseObj(response);String rtCode = responseJson.getStr("code");String data = responseJson.getStr("data");if ("0000".equals(rtCode) && StrUtil.isNotEmpty(data)) {return data;}}} catch (Exception e) {log.error("[从授权服务 post 请求获取用户信息] 异常,请求地址:[{}],异常信息:[{}]", sourceUrl, e.getMessage());}throw new RuntimeException("授权服务异常!");
} 

打印日志:

xxxx [从授权服务换取用户信息] 解密出来的用户信息为:[{"openid":"xxx","headImg":"xxx"}] 

6)第六步:用户信息入库,分发业务 code 给前端

拿到用户信息,写入活动服务的业务表中,然后通知前端说授权完成啦,颁发活动业务的 临时码(code)给客户端,便于客户端来换取活动业务的 JWT。

  • 用户信息入库
public RedirectView loginSuccess(String ...) {// 拷贝OauthUser oauthUser = BeanUtil.copyProperties(userInfoDto, OauthUser.class);oauthUserService.createOrUpdate(oauthUser);// 通知前端return this.redirectFrontEndUrl(state, userInfoDto);
} 
  • 服务端通知前端
private RedirectView redirectFrontEndUrl(final String sceneId,  final UserInfoDto userInfoDto) {// 生成 业务 codeString businessCode = IdUtil.simpleUUID();try {// 反查拿到前端通知地址cache.set(SmallBeanOauthConstant.SMALL_KEY_PRE_USER_INFO + businessCode, userInfoDto, 300);JSONObject sceneInfo = JSONUtil.parseObj(cache.get(SmallBeanOauthConstant.SMALL_KEY_PRE_OAUTH_SCENE + sceneId));String redirectFrontEndUrl = sceneInfo.getStr("redirectFrontEndUrl");if (StrUtil.isEmpty(redirectFrontEndUrl)) {log.warn("授权:分发业务code给前端地址为空!");// TODO 这块需要设置默认值,或者是响应前端401,重跳授权} String notifyUrl = UrlBuilder.of(redirectFrontEndUrl, StandardCharsets.UTF_8).addQuery("code", businessCode).build();// 通知前端return new RedirectView(notifyUrl);} catch (Exception e) {log.error("[OAuth 颁发 code 给客户端异常] 授权id:[{}],异常信息:[{}], [{}]", sceneId, e.getMessage(), e.getCause());}throw new RuntimeException("授权失败,请稍后重试!");
} 

7)第七步:客户端通过 code 换取 业务 jwt

/*** 业务 code to jwt:构建登录态** @param codeToJwtDto 分发的业务 code* @return ResponseEntity*/
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody CodeToJwtDto codeToJwtDto) {ValidatorUtil.validateEntity(codeToJwtDto);String code = codeToJwtDto.getCode();Object cacheUserInfo = cache.get(OAuthConstant.USER_INFO_PREFIX + code);if (null == cacheUserInfo) {throw new ParamException("code非法!", HttpStatus.UNPROCESSABLE_ENTITY.value());}// 删除掉 codecache.delete(OAuthConstant.USER_INFO_PREFIX + code);// 一系列其他业务处理等,然后生成 JWT Map<String, Object> tokenMap = this.buildJwt(activityUser);return ResponseEntity.status(HttpStatus.CREATED).body(tokenMap);
}private Map<String, Object> buildJwt(ActivityUser activityUser) {// 构建 jwt 需要相关参数Map<String, Object> claims = new HashMap<>(2);claims.put("authId", activityUser.getId());claims.put("authRole", "user");String token = JwtUtil.generateToken(claims, applicationConfig.getExpiration(), applicationConfig.getTokenSigningKey());// 构建响应前端 token 信息Map<String, Object> tokenMap = new HashMap<>(3);tokenMap.put("accessToken", token);tokenMap.put("tokenType", "Bearer");tokenMap.put("expiresIn", applicationConfig.getExpiration());return tokenMap;
} 

获取到的 JWT:

{accessToken=header.payload.signature, tokenType=Bearer, expiresIn=86400
} 

三、总结

今天橘长一步一步带着大家写代码手把手接入 OAuth 2.0 授权服务,大家需要记住几点:

1、关注 授权服务 的官方文档,开放平台接入文档是一个很重要的凭据。

2、第三方软件接入授权尽量采用 服务端发起型 授权,使用 授权码许可机制,因为这更安全、更完备。

3、强烈建议你手把手撸一遍,OAuth 2.0 接入的代码很考验基本功和代码风格,其中用到了 Redis 缓存、Hutool 工具去发起 Http 请求等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/15502.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

当推荐和搜索遇上大模型,会碰撞出什么样的火花

推荐和搜索是近年来信息分发的重要方式&#xff0c;小红书UGC社区让人印象深刻&#xff0c;其推荐系统有何特别之处&#xff1f;学界对搜推系统召回阶段有哪些主要进展和主流方法&#xff1f;面对大语言模型的兴起&#xff0c;推荐和搜索的发展有哪些机遇和挑战&#xff1f; 为…

今天,ChatGPT「代码解释器」正式解禁!30秒图片变视频,动嘴做表 | 十大惊人魔法全集...

【导读】坐等3个多月&#xff0c;ChatGPT「代码解释器」终于全面开放了&#xff0c;一大波魔法来袭。 家人们&#xff0c;终于来了&#xff01; 刚刚&#xff0c;ChatGPT「代码解释器」测试版正式向所有Plus用户开放。 Karpathy 在他的博客文章中介绍了 OpenAI 的代码解释器测试…

贾跃亭“杀回”FF核心管理层;近四成程序员只爱远程工作;小米回应被华为起诉专利侵权 | EA周报...

EA周报 2023年3月3日 每个星期1分钟&#xff0c;元宝带你喝一杯IT人的浓缩咖啡&#xff0c;了解天下事、掌握IT核心技术。 周报看点 1、复旦 Moss 团队&#xff1a;Moss 参数规模约是 ChatGPT 的 1/10 2、贾跃亭“杀回”FF核心管理层&#xff0c;重掌大权且权力范围扩大 3、科大…

2023CSDI算力+智能:数字时代的进化升级

引言 &#xff1a;在未来的产业布局中&#xff0c;云网融合、软硬一体化&#xff0c;硬件智能化、软件的泛化以及数据无处不在&#xff0c;基本是未来的趋势。新一轮的信息科技基本都长在云上&#xff0c;未来大部分的科技也都在信息技术之上&#xff0c;这也是互联网平台最大的…

用AI把自己画进动漫里,3天揽获150万+播放量,职业动画师:有被吓到

萧箫 发自 凹非寺量子位 | 公众号 QbitAI 只需随手录制一段视频&#xff0c;AI就能把你完美放进动漫中&#xff01; 无论是线条、色彩还是光影呈现&#xff0c;都与美漫中的写实风格如出一辙&#xff0c;动画也是细腻流畅&#xff0c;帧数显然不低&#xff1a; 制作这样一部包含…

干货 | 如何用ChatGPT30秒内读完2小时课程视频,提升100倍学习力

Hi! 大家好&#xff0c;我是专注于AI项目实战的赤辰&#xff0c;本期继续给大家带来解放100倍生产力的工具教程&#xff01; 目前信息量爆炸&#xff0c;我们每天都面临着海量的在线视频。不论是教育、新闻还是娱乐&#xff0c;视频内容无处不在&#xff0c;由于时间有限和信息…

GPT-4炸圈--多模态大模型

前言 在chatGPT如火如荼的时候&#xff0c;OpenAI又上演了王者归来的戏码&#xff0c;重磅发布了GPT-4。GPT-4是作为“帮你写代码”和你“肆意聊天”的chatGPT的基础模型GPT-3的升级版&#xff0c;是一个新的里程碑。 GPT-4 是一个大型多模态模型&#xff0c;虽然很多能力还不…

新bing可以在你指出它给出的代码的错误后马上改正

我在新bing中问了一个问题&#xff1a; dart中怎么用正则表达式寻找字符串中第一个数值的位置。 在运行bing给出的代码后&#xff0c;发现是错误的&#xff0c;经过检查&#xff0c;我找出了bing错误的代码行。我把改正后的内容告诉bing后&#xff0c;bing马上明白自己错哪儿了…

ChatGPT | Bing | Google Bard | 讯飞星火 | 到底哪家强?实测

最近AIGC战场依然热闹&#xff0c;微软的new bing、Google的Bard、国内的讯飞星火认知大模型&#xff0c;都接连上阵&#xff0c;我们对比ChatGPT一起来看看&#xff0c;我把实际使用测试结果发出&#xff0c;供大家参考。有些测试结果可能会出乎大家的预料哦… 今天我们暂时主…

new bing 对比 chatgpt

使用CombineFileInputFormat将多个小文件合并为一个输入分片 -

ChatGPT 和 Bing Chat两者之间的比较,看完你就懂了

目录 一、ChatGPT 1.1 介绍 1.2 特点 1.3 使用场景 二、 Bing Chat 2.1 介绍 2.2 功能特点 2.3 使用场景 三、对比 一、ChatGPT 1.1 介绍 ChatGPT是一款基于人工智能技术的语言模型应用&#xff0c;由美国人工智能研究实验室OpenAI在2022年11月30日推出。该模型是一种…

chatgpt赋能python:Python图片改名:优化您的网站图片SEO

Python图片改名&#xff1a;优化您的网站图片SEO 如果您是一个有网站运营需求的开发者&#xff0c;那么您一定知道如何优化您的网站来获得更好的访问量和流量。但是&#xff0c;您是否考虑过优化您的网站图片以提高SEO排名呢&#xff1f; 在这篇文章中&#xff0c;我们将介绍…

【ChatGPT的小妙招】结合Excel的vbs开发者工具达成对Excel文件的处理

【ChatGPT的小妙招】结合Excel的vbs开发者工具达成对Excel文件的处理 使用ChatGPT处理Excel文件的原理例子&#xff08;翻译整合&#xff09;操作方法1. 对整份Excel工作簿进行翻译2. 进入ChatGPT对话框&#xff0c;描述需求3. 打开两个Excel表格4. 打开Excel的开发工具5. 打开…

chatgpt赋能python:Python函数改名:为什么需要改名以及如何改名

Python函数改名&#xff1a;为什么需要改名以及如何改名 在Python编程中&#xff0c;函数是非常常见和重要的代码语句&#xff0c;用于完成特定的任务或操作。然而&#xff0c;在实际开发中&#xff0c;我们可能需要对已有函数进行改名&#xff0c;这个过程可能并不简单&#…

chatgpt赋能python:Python文件夹怎么改名:终极指南

Python文件夹怎么改名&#xff1a;终极指南 如果你是一名Python程序员&#xff0c;你一定会不断地创建、修改和管理文件夹。而有时候&#xff0c;你可能需要给文件夹改名&#xff0c;比如重命名一个项目文件夹&#xff0c;或者把文件夹名字改得更加符合你的工作流程。本文将为…

chatgpt赋能python:Python更名到底是否必要?

Python更名到底是否必要&#xff1f; 在社交媒体上&#xff0c;Python社区爆发了一系列的关于Python是否更名的讨论。这一讨论的背景是&#xff1a;是否称呼"Python"的原则可能对一些人造成冒犯。因此&#xff0c;Python的创始人们开始考虑是否需要对Python进行更名…

用不好ChatGPT、sd画图太挫?请收藏好这份Prompt大全

有人说&#xff1a;“也许&#xff0c;未来我们都是Prompt工程师&#xff01;” 这句话还是有一定道理的&#xff0c;在AI技术如火如荼的今天&#xff0c;最大程度利用好AI能够帮助我们提升不少效率。 群里经常有小伙伴对ChatGPT表示不屑&#xff0c;“不过如此”等等&#xf…

chatgpt赋能python:在画布中间画图的Python技巧

在画布中间画图的Python技巧 在Python中&#xff0c;绘图是数据可视化和图形表示的一种重要方式。然而&#xff0c;在绘制图表时&#xff0c;我们需要让图表的中心点位于画布的正中心&#xff0c;而不是依靠手动计算像素值来实现。这不仅让图表更易读&#xff0c;还提高了可视…

GPT-4不披露技术细节,马斯克批判其背离初心,OpenAI不“open“了

编&#xff5c;蛋酱 源&#xff5c;机器之心 除了行业竞争层面的担忧&#xff0c;OpenAI 首席科学家透露了不开源的另外一个原因&#xff1a;模型越强&#xff0c;安全隐患也越多。 3月15日凌晨&#xff0c;OpenAI 出人意料地发布了 GPT-4。 这次发布令科技界颇感意外&#xff…

一个人也可以是【大厂】,三年程序员的生活规划心路分享!

自从工作之后&#xff0c;我就经常思考以下这些问题&#xff1a; 还有多久退休&#xff1f;明天可以退休吗&#xff1f;地球什么时候爆炸&#xff1f;我什么时候可以暴富辞职&#xff1f;我真的需要这份工作吗&#xff1f; 要问是从什么时候开始有这些问题的&#xff0c;大概…