SpringBoot实现OAuth客户端

      背景

        5 月份的时候,我实践并整理了一篇博客:SpringBoot搭建OAuth2,该博客完成之后,本以为能对OAuth2的认证机制更加清晰,但我却觉得自己更“迷惘”了。
        抛开我在项目中积累的浅薄经验不谈,单从在网上找到的OAuth2资料来看,它更适合应用于“资源所有者,客户端,授权服务器,资源服务器”四方角色存在的场景。那么,在企业级的微服务架构中,它也是这么应用的吗?

        一般的企业分布式微服务架构中,常有认证服务OAuth2、基础平台服务(负责用户信息,权限,菜单等管理),网关服务(负责负载,网关转发),业务资源服务(提供业务服务)等,这些服务在互相调用时的流程是怎么样的?怎么做的授权?用的是OAuth2中的哪种授权模式?服务之间,哪个是客户端,哪个是资服服务……等等,怎么越想脑子越乱呢?   

        于是,我打算结合企业微服务架构中对于OAuth的实际应用整理一篇博客,把自己不懂的全弄清楚。也借此和各位大佬们探讨下,OAuth应用于企业服务需要做哪些调整。

      代码实践

        结合公司对OAuth的实际使用情况,以及网上查阅到的资料,我发现要实现OAuth客户端,有两种方案。一种是官方推建的使用spring-boot-starter-oauth2-client的方式,另一种是公司DIY的网关代理的模式,这两种方式的实现我在这里都会写一下。

      一、spring-boot-starter-oauth2-client方式

        这是网上推荐的OAuth2客户端实现方式,它与OAuth Server的交互时序图如下:

        

        代码实现如下:

        1、pom.xml引入依赖包

    <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.2.RELEASE</version> <!-- lookup parent from repository --></parent><groupId>com.client.auth2</groupId><artifactId>auth-client</artifactId><version>1.0</version><name>auth-client</name><description>auth-client</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-client</artifactId></dependency></dependencies>

        2、yml添加添加OAuth服务端的配置

server:port: 19210servlet:context-path: /leixisession:cookie:# 需要更换存放sessionId的cookie名字,否则认证服务和客户端的sessionId会相互覆盖name: JSESSIONID-2max-http-header-size: 102400spring:security:oauth2:client:registration:leixi-client:provider: auth-server    #与下面provider里的配置呼应client-id: client        #在OAuth Server服务端里注测的客户端Idclient-secret: 123456    #在OAuth Server服务端里注测的客户端Secretauthorization-grant-type: authorization_code   #客户端访问的授权模式redirect-uri: '{baseUrl}/{action}/oauth2/code/{registrationId}'  #客户端获得code后的回调地址,默认该地址不变scope: read, write  #授权的scopeclient-name: client  #客户端名称provider:auth-server:authorization-uri: http://127.0.0.1:19200/oauth/authorize    #OAuth Server授权码模式地址token-uri: http://127.0.0.1:19200/oauth/token   #OAuth Server获取Token的地址user-info-uri: http://127.0.0.1:19200/user/info   #OAuth获取用户信息的地址user-name-attribute: name

        3、添加WebSecurityConfig配置

package com.client.auth2.config;import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().logout().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS).and().oauth2Client().and().oauth2Login();}
}

        4、编写一个Controller方法用于测试

/**** @author leixiyueqi* @since 2023/12/5 19:39*/
@RestController
@Slf4j
public class DemoController {@GetMapping("/demo")public Object demo() {Authentication authentication = SecurityContextHolder.getContext().getAuthentication();log.info("authentication: {}", authentication);return "Hello World";}
}

        5、结合OAuth Server进行测试,为方便测试,咱们使用上篇博客中构建的直接跳转登陆页的OAuth Server服务。访问客户端地址:http://127.0.0.1:19210/leixi/demo,测试结果如下:

        

        输入用户名,密码之后,正常跳转到了客户端的请求:

        

      二、网关代理集成方式

        网关代理集成方式是公司在应用OAuth Server时,结合公司的架构进行的一些个性化设计处理,它与其他服务的交互时序图如下:

        以下是一个简化版的实现,其实现逻辑如下:
        1) 通过浏览器访问客户端服务,客户端通过Filter检查请求的cookie中是否有Token,
        2) 如果没有Token或Token校验不通过,则重定向到OAuth Server的登陆页面。
        3) OAuth Server登陆授权后,跳转到客户端的回调方法,回调方法中拿到Code,调用oauth/token来获得token.
        4) 将token封装到cookie中,再重新调用客户端服务,本次Filter检查到有Token,正常放行。
        5) 返回客户端服务的结果到浏览器。

        下面是代码实践:

        1、添加pom.xml依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.83</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.2</version> </dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.0</version></dependency><!--huTool工具箱大全--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.3.0</version></dependency>

        2、添加检查cookie中Token的过滤器

/**** @author leixiyueqi* @since 2024/9/18 19:39*/
@Slf4j
@Component
public class TokenAuthFilter implements Ordered, Filter {private static final String authUri = "http://127.0.0.1:19200/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://127.0.0.1:19210/leixi/callback&state=";@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {HttpServletRequest httpRequest = (HttpServletRequest) request;HttpServletResponse httpResponse = (HttpServletResponse) response;//String token = httpRequest.getParameter("token");  通过url的方式获取Token// 获取请求的URIString requestURI = CommonUtils.getRequestUriByHttpRequest(httpRequest);String token = getCookieFromRequest(httpRequest,"token");if (!requestURI.contains("callback")) {if (StringUtils.isEmpty(token)) {log.info("请求中未携带token信息");httpResponse.sendRedirect(authUri + requestURI);} else {Claims claim = CommonUtils.parseJwt(token);if (claim == null) {log.info("token解析失败");httpResponse.sendRedirect(authUri + requestURI);}}}chain.doFilter(request, response);}private String getCookieFromRequest(HttpServletRequest request,String cookieName) {Cookie[] cookies = request.getCookies();if (cookies != null) {for (Cookie cookie : cookies) {// 进行其他处理,比如验证Cookieif (cookieName.equals(cookie.getName())) {return cookie.getValue();}}} else {log.info("No cookies found in the request.");}return null;}@Overridepublic int getOrder() {return 2;}
}

        3、添加调用/oauth/token请求的工具类

/*** 认证服务交互工具类,用于访问OAuth Server,获得Token** @author leixiyueqi* @since 2024/9/18 19:39*/public class AuthorizationUtils {private static String oauthTokenUrl = "http://127.0.0.1:19200/oauth/token";private static final String clientId ="client";private static final String clientSecret ="123456";public static Map getAccessToken(String code, String redirectUri) {try {// 发送请求String body = HttpUtil.createPost(oauthTokenUrl).contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE).header("Authorization", generateAuthHeader()).form("code", code).form("redirect_uri", redirectUri).form("grant_type", "authorization_code").execute().body();System.out.println("DibAuthorization.getAccessToken:tokenBody " +body);Map<String, Object> map = JSON.parseObject(body, HashMap.class);return map;} catch (Exception ex) {System.out.println("get access token failed : {}"+ ex.getMessage());throw ex;}}private static String generateAuthHeader() {String credentials = clientId + ":" + clientSecret;String encoded = new String(Base64.getEncoder().encode(credentials.getBytes()));return "Basic " + encoded;}}

        4、添加测试Controller


/**** @author leixiyueqi* @since 2024/9/18 19:39*/
@RestController
@Slf4j
public class DemoController {@GetMapping("/demo")public Object demo() {;return "Hello World";}/*** 客户端的回调方法,用于获得code后,通过code获得** @param code* @param state* @param httpRequest* @param response*/@GetMapping("/callback")public void callback(@RequestParam String code,@RequestParam(required = false) String state,ServletRequest httpRequest, ServletResponse response) {try {log.info("进入方法,callback");String localUri = CommonUtils.getRequestUriByHttpRequest((HttpServletRequest)httpRequest);Map<String, Object> map = AuthorizationUtils.getAccessToken(code, localUri);String jwtStr = CommonUtils.createJwt(map);HttpServletResponse httpResponse = (HttpServletResponse) response;//redirectUrl = String.format("%s%s=%s", redirectUrl, redirectUrl.contains("?") ? "&token" : "?token", jwtStr);  //将token拼装到url中。addCookieForToken(httpResponse, jwtStr);httpResponse.sendRedirect(state);} catch (Exception e) {log.error("AuthorizationCodeTokenController.callback exception:{}", e.getMessage());}}/*** 将token以cookie的形式添加到response中** @param response* @param token* @throws Exception*/private void addCookieForToken(HttpServletResponse response, String token) throws Exception {Cookie cookie = new Cookie("token", token);// 设置Cookie的有效期(以秒为单位)cookie.setMaxAge(60 * 60); // 有效期为1小时// 设置Cookie的路径cookie.setPath("/");// 设置Cookie是否只能通过HTTPS协议传输cookie.setSecure(true); // 如果你的应用支持HTTPS,设置为true// 设置Cookie是否可以通过JavaScript脚本访问cookie.setHttpOnly(true); // 设置为true,增加安全性// 添加Cookie到响应中response.addCookie(cookie);// 输出一些文本,以便查看响应response.setContentType("text/html;charset=UTF-8");response.getWriter().println("<h1>Cookie has been set.</h1>");}
}

        5、补充下工具类

/*** 工具类** @author leixiyueqi* @since 2024/9/18 19:39*/
public class CommonUtils {private static final String secretKey = "leixi_2024";public static String createJwt(Map<String, Object> map) {return Jwts.builder().setClaims(map).setExpiration(new Date(System.currentTimeMillis() + 28800000L)).signWith(SignatureAlgorithm.HS256, secretKey).compact();}public static Claims parseJwt(String jwtString) {try {return (Claims)Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwtString).getBody();} catch (JwtException var2) {return null;}}public static String getRequestUriByHttpRequest(HttpServletRequest httpRequest) {String requestURI = httpRequest.getRequestURI();// 获取服务器的端口号int serverPort = httpRequest.getServerPort();// 获取请求的协议(HTTP或HTTPS)String scheme = httpRequest.getScheme();// 构建完整的URLStringBuilder fullUrl = new StringBuilder(scheme).append("://").append(httpRequest.getServerName()).append(":").append(serverPort).append(requestURI);return fullUrl.toString();}
}

        6、测试,与上文一致,输入:http://127.0.0.1:19210/leixi/demo,通过Filter重定向跳转到/oauth/authorize,重定向到登陆页。

        

        输入用户名,密码后,经过上文所述的认证,callback,重定向,再Filter,最终进入客户端请求。

        

      后记与致谢

        完成了这篇博客后,我终于对OAuth Server的使用,企业中的应用、与客户端的交互有了一个全盘的理解。道阻且长,行将则至,我也没想到时隔近五个月,我才把OAuth相关的知识链给跑通。参考了网上其他的博客,很多大佬在一篇博客里就把认证,自定义页面,客户端给写好了,但我自认没有能力写得那么简单直白,另一方面也想深入的剖析下它的实现,所以写得有点啰嗦了,请各位看官人多多包涵。

        现在回过头看OAuth Server的四种授权模式,可知本篇博客中的两种实现都是授权码模式,那么,对于在企业内部应用OAuth,是不是可以使用其他模式呢?如更简捷的“简单模式”,这个课题,大家可以结合自己的需要进行实践。

        在实践这篇博客时,我也在网上找到了很多二货,以下是我觉得对我帮助极大的,拜谢大佬!

        SpringBoot+SpringSecurity OAuth2 认证服务搭建实战 (六)OAuth2经典场景~授权码模式

        SpringBoot整合OAuth 2.0

        超级简单的springboot整合springsecurity oauth2第三方登录

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

智慧园区的发展趋势

在数字经济高速发展、前瞻技术加速创新和社会需求革命的驱动下&#xff0c;江园科技智慧园区的未来发展将呈现数智化、融合化、人本化、韧性化和绿色化五大趋势。 趋势一&#xff1a;数智化 以万兆联接、数字平台为特征的基础设施将成为园区的核心底座。以 5.5G/NET5.5G/F5.5G…

深度图可视化显示(kitti)

文章目录 前言一、读取深度值与图像1、深度值读取2、图像读取 二、深度图可视化1、深度图可视化代码2、深度图可视化结果展示 三、深度图在图像上可视化1、可视化代码2、可视化坐标显示 四、完整代码 前言 kitti数据是一个通用数据&#xff0c;有关kitti的深度图像内容我已有博…

常见中间件漏洞靶场(tomcat)

1.CVE-2017-12615 开启环境 查看端口 查看IP 在哥斯拉里生成一个木马 访问页面修改文件后缀和文件内容 放包拿去连接 2.后台弱⼝令部署war包 打开环境 将前边的1.jsp压缩成1.zip然后改名为1.war 访问页面进行上传 在拿去连接 3.CVE-2020-1938 打开环境 访问一下 来到kali …

FRIDA-JSAPI:Java使用

Frida Frida.version 包含当前Frida版本信息的属性&#xff0c;以字符串形式表示。setImmediate(function (){console.log(Frida.version) })Java Java.perform(fn) 确保当前线程已附加到虚拟机&#xff0c;并调用 fn。 setImmediate(function (){Java.perform(function (){c…

时代变了,MySQL 早已不是最流行的数据库了

以下文章来源于古时的风筝 &#xff0c;作者风筝 在StackOverflow 上看到2024年技术趋势&#xff0c;关于数据库的部分&#xff0c;PostgreSQL 是开发人员使用最多的数据库&#xff0c;超过 MySQL 了。虽然在国内好像不是这样。 PostgreSQL 在 2018 年的开发者调查中首次亮相…

云韧性,现代云服务不可或缺的组成部分

韧性&#xff0c;一个物理学概念&#xff0c;表示材料在变形或者破裂过程中吸收能量的能力。韧性越好&#xff0c;则发生脆性断裂的可能性越小。 如今&#xff0c;韧性也延伸到企业特质、产品特征等之中&#xff0c;用于形容企业、产品乃至服务的优劣。同样&#xff0c;随着云…

电脑视频编辑常用软件:12个在线视频剪辑方法,这份免费攻略真实在!

您是否曾为视频剪辑而感到困惑&#xff0c;不知从何入手&#xff1f;面对众多的视频编辑软件和复杂的操作流程&#xff0c;怎样才能快速上手&#xff0c;制作出高质量的视频呢&#xff1f;许多内容创作者在编辑或上传较长视频时&#xff0c;常常遭遇到时间和质量的困扰。为了解…

私域直播平台带源码

源码地址:https://gitee.com/godsdodo/tencent-live.git 简介: #腾讯云直播 #腾讯云im #腾讯云白板 # 私域直播 #高并发直播分发; 基于腾讯云K8S搭建的私域直播培训平台,直播功能: 主播推流,智能直播,OBS推流 ## 助理平台: 场控控制,直播间管理,直播间数据统计 ## 用户端: 观看…

Double-Fetch漏洞检测工具的部署、使用与原理分析

文章目录 前言1、概述1.1、简介1.2、工作原理1.2.1、内核空间与用户空间的信息传递1.2.2、Double-Fetch漏洞产生的原因1.2.3、产生Double-Fetch漏洞的情况1.2.4、一个Double-Fetch漏洞示例1.2.5、Double-Fetch漏洞检测工具原理 1.3、模式匹配原理分析1.3.1、Coccinelle介绍1.3.…

使用 Bedrock 模型进行 SQL 查询生成:高效自动化的全新体验!

引言 在当今高度重视可持续发展的时代&#xff0c;亚马逊通过其 Bedrock 模型&#xff0c;展示了公司在运营和增长方面的战略愿景。同时&#xff0c;Amazon SageMaker 为机器学习领域的专业人士提供了强大的工具&#xff0c;加速了模型的开发和部署。 探索亚马逊的 Bedrock 模…

动态SQL中的foreach标签【后端 21】

动态SQL中的foreach标签 在Java开发中&#xff0c;特别是在使用MyBatis进行数据库操作时&#xff0c;动态SQL是一项非常强大的功能。MyBatis的<foreach>标签就是动态SQL中最为常用的一个&#xff0c;主要用于处理包含IN子句的查询或者批量插入等操作。本文将详细介绍<…

对接金蝶云星空调用即时库存信息查询API(附JAVA实现)

文章目录 前言准备工作获取第三方授权权限与授权配置信息集成金蝶云SDK调用实现备注前言 对于有自己商品信息管理后台并且使用金蝶ERP系统管理物料的商家来说,将金蝶上物料的库存信息同步到管理后台就可以不用去金蝶上确认库存了,可以大大简化管理后台的库存变更工作,这篇文…

【北京迅为】《STM32MP157开发板使用手册》- 第四十一章 计数信号量实验

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7单核cortex-M4异构处理器&#xff0c;既可用Linux、又可以用于STM32单片机开发。开发板采用核心板底板结构&#xff0c;主频650M、1G内存、8G存储&#xff0c;核心板采用工业级板对板连接器&#xff0c;高可靠&#xff0c;牢固耐…

中国火锅变局:从群雄逐鹿到双雄角逐

中国火锅&#xff0c;正在进入新的变局。 前不久&#xff0c;沙利文发布的《2024年中国火锅行业发展白皮书》&#xff0c;揭示出中国火锅新的市场变化和竞争格局。 首先在品类上&#xff0c;川渝火锅的市场份额高达66%&#xff0c;远超排在后面的北派火锅和粤式火锅。 在川渝…

围剿Model Y,小米SUV也来拼刺刀了

文 | AUTO芯球 作者 | 雷慢 马斯克真是被小米雷军盯上了&#xff0c; 前面小米SU7死磕Model 3&#xff0c; 现在小米SUV又来打Model Y了&#xff0c; 别不信啊&#xff0c;就刚刚&#xff0c;小米SUV出现了最大的曝光&#xff0c; 外观谍照&#xff0c;内饰中控台都曝光了…

基于SpringBoot+Vue的商场停车场管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的…

标签云效果

产品要求&#xff0c;词云要实现动态滚动。查资料&#xff0c;改写效果。 echarts词云效果 传统的echarts-wordCloud不能满足需求。 标签云 换了标签云&#xff0c;以下是代码 <template><div class"mx-auto" :style"{ width: width px }"&g…

正点原子RK3588(一)——开机测试+AI初探

一、adb adb shell&#xff0c;进入板子的根目录 exit&#xff0c;退出到linux adb pull 板子 linux&#xff08;从板子到linux&#xff09; adb push linux 板子&#xff08;从linux到板子&#xff09; 二、测试AI功能 2.1 resnet18 import cv2 import numpy as np import…

全视通解读政策风向,智慧手术室究竟怎么做信息化?

尽管智慧手术室的建设与发展日益重要&#xff0c;但是目前国内外对智慧手术室的定义仍是众说纷纭&#xff0c;直至日前&#xff0c;上海交通大学医学院附属瑞金医院、上海市数字医学创新中心联合L.E.K.咨询发布的《中国智慧手术室发展与实践白皮书》&#xff08;后简称《白皮书…

序列化方式二——JSON之fastjson

fastjson&fastjson2(版本&#xff1a;1.2.83_noneautotype) 扩展点 Fastjson通过其丰富的扩展点显著增强了用户定制序列化和反序列化行为的灵活性&#xff0c;完美契合了实际开发中的多样化需求。在SpringBoot与SpringCloud的集成环境中&#xff0c;开发者能够利用Seriali…