【分布式微服务专题】SpringSecurity OAuth2快速入门

目录

  • 前言
  • 阅读对象
  • 阅读导航
  • 前置知识
  • 笔记正文
    • 一、OAuth2 介绍
      • 1.1 使用场景
      • *1.2 基本概念(角色)
      • 1.3 优缺点
    • 二、OAuth2的设计思路
      • 2.1 客户端授权模式
        • 2.1.0 基本参数说明
        • 2.1.1 授权码模式
        • 2.1.2 简化(隐式)模式
        • 2.1.3 密码模式
        • 2.1.4 客户端模式
      • 2.2 令牌的使用
      • 2.3 令牌更新
    • 三、Spring Security OAuth2快速开始
      • 3.1 授权服务器的几个节点
      • 3.2 整体架构(授权码模式)
      • 3.3 代码整合(授权码模式)
      • 3.4 更新令牌
      • 3.5 基于redis存储Token
    • 四、Spring Security Oauth2整合JWT
      • 4.1 整合JWT
      • 4.2 扩展JWT中的存储内容
      • 4.3 解析JWT
  • 学习总结
  • 感谢

前言

这里面的笔记都是我这里抄抄那里抄抄得来的,然后也经过了一些简单的测试,主要是用来拓宽知识面用的,毕竟到了项目开发中,不会裸用这些技术框架,通常都是采用第三方集成框架。
另外需要说明的是,这里要介绍的Spring Security OAuth2框架应该有点过时了,在Spring官网已经没有了项目介绍,现在项目似乎已经升级,并且迁移为Spring Authorization Server项目。所以,大家看自己需要是否要继续学习Spring Authorization Server项目了。

阅读对象

  1. 了解、熟悉Spring Security
  2. 有过OAuth2使用经验

阅读导航

系列上一篇文章:《【分布式微服务专题】SpringSecurity快速入门》

前置知识

笔记正文

一、OAuth2 介绍

权威文档地址:OAuth2.0协议介绍

OAuth(Open Authorization,开放授权)是一个关于授权(authorization)的开放网络标准协议,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容。OAuth在全世界得到广泛应用,目前的版本是2.0版。

注意:OAuth2只是一种标准协议,或者叫作:规范! 其实在我们计算机学习中,你会发现很多各种各样的规范,这些规范往往指的是一种约定,并不特指某一具体的项目,SpringSecurityOAuth2才是一个具体的项目。可别被带偏了呀

标准协议特点:

  • 简单:不管是OAuth服务提供者还是应用开发者,都很易于理解与使用
  • 安全:没有涉及到用户密钥等信息,更安全更灵活
  • 开放:任何服务提供商都可以实现OAuth,任何软件开发商都可以使用OAuth

1.1 使用场景

  • 原生app授权:app登录请求后台接口,为了安全认证,所有请求都带token信息,如果登录验证、请求后台数据。
  • 前后端分离单页面应用:前后端分离框架,前端请求后台数据,需要进行oauth2安全认证,比如使用vue、react或者h5开发的app
  • 第三方应用授权登录,比如QQ,微博,微信的授权登录

案例1:云快印
有一个【云快印】的网站,可以将用户存储在的百度网盘上的照片打印出来。用户为了使用该服务,必须让【云快印】读取自己储存在百度网盘上的照片。只有得到用户的授权,百度网盘才会同意【云快印】取这些照片。那么,【云快印】怎样获得用户的授权呢?
传统方法是,用户将自己百度网盘的用户名和密码,告诉【云快印】,后者就可以读取用户的照片了。这样的做法有以下几个严重的缺点:

  1. 【云快印】为了后续的服务,会保存用户的密码,这样很不安全
  2. 百度网盘不得不部署密码登录,而我们知道,单纯的密码登录并不安全
  3. 【云快印】拥有了获取用户储存在百度网盘服务所有资料的权力,用户没法限制【云快印】获得授权的范围和有效期
  4. 用户只有修改密码,才能收回赋予【云快印】的权力。但是这样做,会使得其他所有获得用户授权的第三方应用程序全部失效
  5. 只要有一个第三方应用程序被破解,就会导致用户密码泄漏,以及所有被密码保护的数据泄漏。

综上缺陷,这显然不是我们期待的方案。

案例2:CSDN登录
附上一张常见的OAuth2场景:CSDN接入微信QQ开放平台,用户可以通过微信QQ登录CSDN
在这里插入图片描述

*1.2 基本概念(角色)

角色:

  1. Third-party application:第三方应用程序,又称【客户端client / 三方应用】,即例子中的【云快印】
  2. HTTP service:HTTP服务提供商,简称【服务提供商】,即例子中的百度网盘
  3. Resource Owne:资源所有者,又称【用户user】
  4. User Agent:用户代理,比如浏览器
  5. Authorization server:授权服务器,即服务提供商专门用来处理认证授权的服务器
  6. Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与授权服务器,可以是同一台服务器,也可以是不同的服务器。当然,想要访问资源,需要通过认证服务器由资源所有者授权才可以访问

概念:

  1. 授权许可 (Authorization Grant):授权许可是资源所有者授权给客户端访问受保护资源的凭证。OAuth2定义了多种授权许可类型,如授权码、简化授权、密码授权和客户端凭证等
  2. 访问令牌 (Access Token):访问令牌是由授权服务器颁发给客户端的凭证,表示客户端被授权访问受保护资源的权限。客户端使用访问令牌来请求资源服务器获取受保护资源
  3. 刷新令牌 (Refresh Token):刷新令牌是可选的,用于在访问令牌过期后获取新的访问令牌。客户端可以使用刷新令牌向授权服务器请求刷新访问令牌,以延长访问权限的有效期

1.3 优缺点

优点:

  • 更安全,客户端不接触用户密码,服务器端更易集中保护
  • 广泛传播并被持续采用
  • 短寿命和封装的token
  • 资源服务器和授权服务器解耦
  • 集中式授权,简化客户端
  • HTTP/JSON友好,易于请求和传递token
  • 考虑多种客户端架构场景
  • 客户可以具有不同的信任级别

缺点:

  • 协议框架太宽泛,造成各种实现的兼容性和互操作性差
  • 不是一个认证协议,本身并不能告诉你任何用户信息

二、OAuth2的设计思路

OAuth在【客户端】与【服务提供商】之间,设置了一个授权层(authorization layer)。【客户端】不能直接登录【服务提供商】,只能登录授权层,以此将用户与客户端区分开来。【客户端】登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期,【客户端】登录授权层以后,【服务提供商】根据令牌的权限范围和有效期,向【客户端】开放用户储存的资料。
在这里插入图片描述
(1)用户打开客户端以后,客户端要求用户给予授权
(2)用户同意给予客户端授权
(3)客户端使用上一步获得的授权,向授权服务器申请令牌。
(4)授权服务器对客户端进行认证以后,确认无误,同意发放令牌
(5)客户端使用令牌,向资源服务器申请获取资源
(6)资源服务器确认令牌无误,同意向客户端开放资源

令牌(token)与密码(password)的作用是一样的,都可以进入系统,但是有三点差异
(1)令牌是短期的,到期会自动失效,用户自己无法修改。密码一般长期有效,用户不修改,就不会发生变化
(2)令牌可以被数据所有者撤销,会立即失效。密码一般不允许被他人撤销
(3)令牌有权限范围(scope)。对于网络服务来说,只读令牌就比读写令牌更安全。密码一般是完整权限
上面这些设计,保证了令牌既可以让第三方应用获得权限,同时又随时可控,不会危及系统安全。这就是 OAuth 2.0 的优点

2.1 客户端授权模式

客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth 2.0 对于如何颁发令牌的细节,规定得非常详细。具体来说,一共分成四种授权类型(authorization grant),即四种颁发令牌的方式,适用于不同的互联网场景。它们分别是:

  • 授权码模式(authorization code)
  • 密码模式(resource owner password credentials)
  • 简化(隐式)模式(implicit)
  • 客户端模式(client credentials)

不过不管哪一种授权方式,它们的本质流程都是:三方客户端到服务提供商系统中备案,说明自己身份,然后拿到身份识别码【客户端id + 客户端密钥】。这是为了防止令牌被滥用,没有备案过的第三方应用,是不会拿到令牌的。

2.1.0 基本参数说明

在客户端授权中,我们通常需要用到以下参数:

  • client_id:客户端应用的ID
  • client_secret:客户端秘钥
  • response_type:响应类型,可选的值有:codetoken
  • grant_type:授权模式,可选的值有authorization_codepasswordclient_credentials等,对应的是不同的授权模式
  • scope:授权范围
  • redirect_uri:回调地址
  • username:用户名称,通常是在密码模式下使用
  • password:用户密码,通常是在密码模式下使用
2.1.1 授权码模式

目前主流的三方授权模式

授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。

特点:

  • 安全性最高
  • 适用于有后端的Web应用(前后分离)
  • 授权码通过前端传送
  • 令牌储存在后端(避免令牌泄露)
  • 由后端与资源服务器通信

一般性流程描述:
(1)用户访问客户端,后者将前者导向授权服务器
(2)用户选择是否给予客户端授权
(3)假设用户给予授权,授权服务器将用户导向客户端事先指定的【重定向URI(redirection URI)】,同时附上一个授权码
(4)客户端收到授权码,附上早先的【重定向URI】,向授权服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见
(5)授权服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)

案例:
1) A网站提供一个链接,用户点击后就会跳转到 B 网站,授权用户数据给 A 网站使用。下面就是 A 网站跳转 B 网站的一个示意链接

https://b.com/oauth/authorize?response_type=code&            # 表示授权类型,此处固定为codeclient_id=CLIENT_ID&           # 表示客户端的id,通常为必填redirect_uri=CALLBACK_URL&     # 表示重定向的uri。接受或拒绝请求后的跳转地址scope=read					  # 表示申请的权限范围(这里是只读)state=xxx					  # 示客户端的当前状态,可以指定任意值,授权服务器会原封不动地返回这个值

2)用户跳转后,B 网站会要求用户登录,然后询问是否同意给予 A 网站授权。用户表示同意,这时 B 网站就会跳回redirect_uri参数指定的网址。跳转时,会传回一个授权码,就像下面这样

https://a.com/callback?code=AUTHORIZATION_CODE    #code参数就是授权码

3)A 网站拿到授权码以后,就可以在后端,向 B 网站请求令牌。 用户不可见,服务端行为

https://b.com/oauth/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&      # client_id和client_secret用来让 B 确认 A 的身份,client_secret参数是保密的,因此只能在后端发请求grant_type=authorization_code&    # 采用的授权方式是授权码code=AUTHORIZATION_CODE&          # 上一步拿到的授权码redirect_uri=CALLBACK_URL			# 令牌颁发后的回调网址

4)B 网站收到请求以后,就会颁发令牌。具体做法是向redirect_uri指定的网址,发送一段JSON数据

{    "access_token":"ACCESS_TOKEN",     # 令牌"token_type":"bearer","expires_in":2592000,"refresh_token":"REFRESH_TOKEN","scope":"read","uid":100101,"info":{...}
}
2.1.2 简化(隐式)模式

有些 Web 应用是纯前端应用,没有后端。这时就不能用上面的方式了,必须将令牌储存在前端。RFC 6749 就规定了第二种方式,允许直接向前端颁发令牌,这种方式没有授权码这个中间步骤,所以称为【(授权码)隐式(implicit)】

特点:

  • 三方应用没有服务端
  • 不需要授权码,直接颁发令牌给三方
  • 不安全,适用于安全要求不高的场景
  • 令牌时效短,通常是会话期内有效

一般性流程描述:
(1)用户访问客户端,后者将前者导向授权服务器
(2)用户选择是否给予客户端授权
(3)假设用户给予授权,授权服务器将用户导向客户端事先指定的【重定向URI(redirection URI)】,并在URI的Hash部分包含了访问令牌

案例:
1)A 网站提供一个链接,要求用户跳转到 B 网站,授权用户数据给 A 网站使用

 https://b.com/oauth/authorize?response_type=token&          # response_type参数为token,表示要求直接返回令牌client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read

2)用户跳转到 B 网站,登录后同意给予 A 网站授权。这时,B 网站就会跳回redirect_uri参数指定的跳转网址,并且把令牌作为 URL 参数,传给 A 网站

https://a.com/callback#token=ACCESS_TOKEN     #token参数就是令牌,A 网站直接在前端拿到令牌
2.1.3 密码模式

如果你高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为【密码式(password)】。
在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而授权服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。

特点:

  • 用户必须把自己的密码给客户端,但是客户端不得储存密码

一般性流程描述:
(1)用户向客户端提供用户名和密码
(2)客户端将用户名和密码发给授权服务器,向后者请求令牌
(3)授权服务器确认无误后,向客户端提供访问令牌

案例:
1)A 网站要求用户提供 B 网站的用户名和密码,拿到以后,A 就直接向 B 请求令牌。整个过程中,客户端不得保存用户的密码

 https://oauth.b.com/token?grant_type=password&       # 授权方式是"密码式"username=USERNAME&password=PASSWORD&client_id=CLIENT_ID

2)B 网站验证身份通过后,直接给出令牌。注意,这时不需要跳转,而是把令牌放在 JSON 数据里面,作为 HTTP 回应,A 因此拿到令牌

2.1.4 客户端模式

客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向【服务提供商】请求授权。
适用于没有前端的命令行应用,即在命令行下请求令牌。一般用来提供给我们完全信任的服务器端服务。

特点:
一般性流程描述:
(1)客户端向授权服务器进行身份认证,并要求一个访问令牌
(2)授权服务器确认无误后,向客户端提供访问令牌

案例:
1)A 应用在命令行向 B 发出请求

 https://oauth.b.com/token?grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET

2)B 网站验证通过以后,直接返回令牌

2.2 令牌的使用

A 网站拿到令牌以后,就可以向 B 网站的 API 请求数据了。这个时候,每次发送的请求,都会带上令牌,具体做法是,在头信息header中,加上一个Authorization字段,然后把令牌值设置在该字段中

2.3 令牌更新

令牌到期之后,正常来说是需要有自动刷新的逻辑的,不然每次到期让用户重新申请授权,未免显得太麻烦,而且也没必要。于是OAuth 2.0 允许用户自动更新令牌。
具体方法是,B 网站颁发令牌的时候,一次性颁发两个令牌,一个用于获取数据,另一个用于获取新的令牌(refresh token 字段)。令牌到期前,用户使用 refresh token 发一个请求,去更新令牌。

 https://b.com/oauth/token?grant_type=refresh_token&    # grant_type参数为refresh_token表示要求更新令牌client_id=CLIENT_ID&client_secret=CLIENT_SECRET&refresh_token=REFRESH_TOKEN    # 用于更新令牌的令牌

三、Spring Security OAuth2快速开始

首先,为了帮助更好的理解当前正在做什么,先再次声明以下内容:

  • Spring Security是Spring提供的一个身份验证和访问控制框架,是一个具体的项目
  • OAuth是一个标准协议,是一种约定,不特指某一个具体的项目
  • Spring Security OAuth2是实现了OAuth2标准协议的Spring Security框架,是一个具体的项目

我在前面大概介绍过SpringSecurity了,这边就不再赘述了。但首先我们得再次声明一点:SpringSecurity主要实现了【认证】和【访问控制】。
正常在我们的企业应用中,需要结合SpringSecurity与OAuth2,这样才算是得到一套完整的解决方案。我们可以通过Spring Security + OAuth2构建一个授权服务器来验证用户身份以提供access_token,并使用这个access_token来从资源服务器请求数据。

3.1 授权服务器的几个节点

在这里插入图片描述
上面是一个典型授权服务器的节点图。它们分别有如下作用:

  • Authorize Endpoint :授权端点,进行授权
  • Token Endpoint :令牌端点,经过授权拿到对应的Token
  • Introspection Endpoint :校验端点,校验Token的合法性
  • Revocation Endpoint :撤销端点,撤销授权

3.2 整体架构(授权码模式)

在这里插入图片描述
上图的流程:

  1. 用户访问资源,此时没有Token。Oauth2RestTemplate会报错,这个报错信息会被Oauth2ClientContextFilter捕获并重定向到授权服务器
  2. 授权服务器通过Authorization Endpoint进行授权,并通过AuthorizationServerTokenServices生成【授权码】并返回给客户端
  3. 客户端拿到授权码去授权服务器通过Token Endpoint调用AuthorizationServerTokenServices生成Token并返回给客户端
  4. 客户端拿到Token去资源服务器访问资源,一般会通过Oauth2AuthenticationManager调用ResourceServerTokenServices进行校验。校验通过可以获取资源

3.3 代码整合(授权码模式)

1)引入依赖

    <!-- 接入spring security--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 --><dependency><groupId>org.springframework.security.oauth</groupId><artifactId>spring-security-oauth2</artifactId><version>2.5.2.RELEASE</version></dependency>

2)配置 spring security

    @Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin().permitAll().and().authorizeRequests().antMatchers("/oauth/**").permitAll().anyRequest().authenticated().and().logout().permitAll().and().csrf().disable();}
}

3)配置授权服务器

public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {@Autowiredprivate PasswordEncoder passwordEncoder;@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory()// 配置client_id.withClient("client")// 配置client-secret.secret(passwordEncoder.encode("123456"))// 配置访问token的有效期.accessTokenValiditySeconds(3600)// 配置刷新token的有效期.refreshTokenValiditySeconds(864000)// 配置redirect_uri,用于授权成功后跳转.redirectUris("http://www.baidu.com")// 配置申请的权限范围.scopes("all")// 配置grant_type,表示授权类型.authorizedGrantTypes("authorization_code");}
}

4)配置资源服务器

@Configuration
@EnableResourceServer
public class ResourceServiceConfig extends ResourceServerConfigurerAdapter {@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().requestMatchers().antMatchers("/user/**");}
}

5)测试,获取授权码
输入:http://localhost:8081/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://www.baidu.com&scope=all
接着会进入这个页面:
在这里插入图片描述
登录之后选择Approve
在这里插入图片描述
接着就会在地址栏显示如下:
在这里插入图片描述
上面的code即为我们的【授权码】

6)根据授权码获取令牌
下面需要使用Apifox来构建一个post请求,去授权服务器中获取令牌了
在这里插入图片描述
在这里插入图片描述
7)请求令牌,得到如下数据:
在这里插入图片描述

3.4 更新令牌

使用oauth2时,如果令牌失效了,可以使用刷新令牌通过refresh_token的授权模式再次获取access_token。只需修改认证服务器的配置,添加refresh_token的授权模式即可。
1)修改授权服务器AuthorizationServerConfig配置,增加refresh_token配置

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {@Autowiredprivate PasswordEncoder passwordEncoder;@Autowiredprivate ShenUserService userService;@Autowiredprivate AuthenticationManager authenticationManagerBean;@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.authenticationManager(authenticationManagerBean) //使用密码模式需要配置// .tokenStore(tokenStore)  //指定token存储到redis.reuseRefreshTokens(false)  //refresh_token是否重复使用.userDetailsService(userService) //刷新令牌授权包含对用户信息的检查.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求}@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory()// 配置client_id.withClient("client")// 配置client-secret.secret(passwordEncoder.encode("123456"))// 配置访问token的有效期.accessTokenValiditySeconds(3600)// 配置刷新token的有效期.refreshTokenValiditySeconds(864000)// 配置redirect_uri,用于授权成功后跳转.redirectUris("http://www.baidu.com")// 配置申请的权限范围.scopes("all")// 配置grant_type,表示授权类型.authorizedGrantTypes("authorization_code", "implicit", "refresh_token");}
}

测试获取token,得到如下结果:
在这里插入图片描述
相比之前的,多了一个refresh_token

3.5 基于redis存储Token

我们生成的token可以存储在很多地方,比较常用的一种方式是存储到redis中。只需要3步即可

1)引入依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>

2)修改application.yml

spring:redis:host: 127.0.0.1database: 0

3)编写redis配置类,声明TokenStore Bean

@Configuration
public class RedisConfig {@Autowiredprivate RedisConnectionFactory redisConnectionFactory;@Beanpublic TokenStore tokenStore(){return new RedisTokenStore(redisConnectionFactory);}
}

4)在授权服务器AuthorizationServerConfig配置中指定令牌的存储策略为Redis

   @Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.authenticationManager(authenticationManagerBean) //使用密码模式需要配置.tokenStore(tokenStore)  //指定token存储到redis.reuseRefreshTokens(false)  //refresh_token是否重复使用.userDetailsService(userService) //刷新令牌授权包含对用户信息的检查.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求}

四、Spring Security Oauth2整合JWT

我在前一篇笔记里面已经简单介绍过JWT了,感兴趣的朋友可以看看我上一篇文章,链接在最前面的【阅读导航】中有介绍。下面开始介绍整合步骤

4.1 整合JWT

1)引入依赖

<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-jwt</artifactId><version>1.0.9.RELEASE</version>
</dependency>

2)添加配置文件JwtTokenStoreConfig.java

@Configuration
public class JwtTokenStoreConfig {@Beanpublic TokenStore jwtTokenStore(){return new JwtTokenStore(jwtAccessTokenConverter());}@Beanpublic JwtAccessTokenConverter jwtAccessTokenConverter(){JwtAccessTokenConverter accessTokenConverter = newJwtAccessTokenConverter();// 配置JWT使用的秘钥accessTokenConverter.setSigningKey("123456");return accessTokenConverter;}
}

3)在授权服务器AuthorizationServerConfig配置中指定令牌的存储策略为JWT

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {@Autowiredprivate PasswordEncoder passwordEncoder;@Autowiredprivate ShenUserService userService;@Autowiredprivate AuthenticationManager authenticationManagerBean;@Autowiredprivate TokenStore tokenStore;@Autowiredprivate JwtAccessTokenConverter jwtAccessTokenConverter;@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.authenticationManager(authenticationManagerBean) // 使用密码模式需要配置.tokenStore(tokenStore)  // 指定token存储到redis.accessTokenConverter(jwtAccessTokenConverter).reuseRefreshTokens(false)  // refresh_token是否重复使用.userDetailsService(userService) // 刷新令牌授权包含对用户信息的检查.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST); // 支持GET,POST请求}@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory()// 配置client_id.withClient("client")// 配置client-secret.secret(passwordEncoder.encode("123456"))// 配置访问token的有效期.accessTokenValiditySeconds(3600)// 配置刷新token的有效期.refreshTokenValiditySeconds(864000)// 配置redirect_uri,用于授权成功后跳转.redirectUris("http://www.baidu.com")// 配置申请的权限范围.scopes("all")// 配置grant_type,表示授权类型.authorizedGrantTypes("authorization_code", "implicit", "refresh_token");}
}

4)接着还是按照上面的测试步骤走一遍,得到以下结果,令牌变成了JWT格式
在这里插入图片描述

4.2 扩展JWT中的存储内容

有时候我们需要扩展JWT中存储的内容,这里我们在JWT中扩展一个 key为enhance,value为enhance info 的数据。
继承TokenEnhancer实现一个JWT内容增强器

1)新增JwtTokenEnhancer ,增强内容:("enhance", "enhance info")

@Component
public class JwtTokenEnhancer implements TokenEnhancer {@Overridepublic OAuth2AccessToken enhance(OAuth2AccessToken accessToken,OAuth2Authentication authentication) {Map<String, Object> info = new HashMap<>();info.put("enhance", "enhance info");((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);return accessToken;}
}

2)在授权服务器AuthorizationServerConfig配置中配置JWT的内容增强器

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {@Autowiredprivate PasswordEncoder passwordEncoder;@Autowiredprivate ShenUserService userService;@Autowiredprivate AuthenticationManager authenticationManagerBean;@Autowiredprivate TokenStore tokenStore;@Autowiredprivate JwtAccessTokenConverter jwtAccessTokenConverter;@Autowiredprivate JwtTokenEnhancer jwtTokenEnhancer;@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {//配置JWT的内容增强器TokenEnhancerChain enhancerChain = new TokenEnhancerChain();List<TokenEnhancer> delegates = new ArrayList<>();delegates.add(jwtTokenEnhancer);delegates.add(jwtAccessTokenConverter);enhancerChain.setTokenEnhancers(delegates);endpoints.authenticationManager(authenticationManagerBean) // 使用密码模式需要配置.tokenStore(tokenStore)  // 指定token存储到redis.accessTokenConverter(jwtAccessTokenConverter).tokenEnhancer(enhancerChain) //配置tokenEnhancer.reuseRefreshTokens(false)  // refresh_token是否重复使用.userDetailsService(userService) // 刷新令牌授权包含对用户信息的检查.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST); // 支持GET,POST请求}@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory()// 配置client_id.withClient("client")// 配置client-secret.secret(passwordEncoder.encode("123456"))// 配置访问token的有效期.accessTokenValiditySeconds(3600)// 配置刷新token的有效期.refreshTokenValiditySeconds(864000)// 配置redirect_uri,用于授权成功后跳转.redirectUris("http://www.baidu.com")// 配置申请的权限范围.scopes("all")// 配置grant_type,表示授权类型.authorizedGrantTypes("authorization_code", "implicit", "refresh_token");}
}

3)测试,获取令牌
4)对获取到的令牌,解密之后得到

{"user_name": "Nico","scope": ["all"],"exp": 1704786179,"authorities": ["admin"],"jti": "2DyqZildiHtLr7tojfDOwGqjnLE","client_id": "client","enhance": "enhance info"
}

可以看见,在Payload中得到了被加强的内容

4.3 解析JWT

1)添加依赖

<!--JWT依赖-->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>

2)修改UserController类,使用jjwt工具类来解析Authorization头中存储的JWT内容

@GetMapping("/getCurrentUser")public Object getCurrentUser(Authentication authentication,HttpServletRequest request) {String header = request.getHeader("Authorization");String token = null;if(header!=null){token = header.substring(header.indexOf("bearer") + 7);}else {token = request.getParameter("access_token");}return Jwts.parser().setSigningKey("123456".getBytes(StandardCharsets.UTF_8)).parseClaimsJws(token).getBody();}

3)测试,不过这一次我们需要将得到的令牌放在header的Authorization中了,因为我们的逻辑是这么写的
接着访问上面的getCurrentUser接口,即会得到如下结果:
在这里插入图片描述

学习总结

  1. 稍微学习了OAuth2的一些内容,只不过还是有一些疑惑的点

感谢

  1. 感谢站内大佬【作者:歪桃】的文章《Spring Security oauth2(一)快速入门,搭建授权服务器》
  2. 感谢大佬【作者:阮一峰】的文章《理解OAuth 2.0》

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

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

相关文章

如何使用人工智能优化 DevOps?

DevOps 和人工智能密不可分&#xff0c;影响着各种业务。DevOps 可以加快产品开发速度并简化现有部署的维护&#xff0c;而 AI 则可以改变整个系统的功能。DevOps团队可以依靠人工智能和机器学习来进行数据集成、测试、评估和发布系统。更重要的是&#xff0c;人工智能和机器学…

轻松掌握构建工具:Webpack、Gulp、Grunt 和 Rollup 的使用技巧(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

【大厂算法面试冲刺班】day0:数据范围反推时间复杂度

常见算法的时间复杂度 规定n是数组的长度/树或图的节点数 二分查找&#xff1a;O(logn) 双指针/滑动窗口&#xff1a;O(n) DFS/BFS&#xff1a;O(n) 构建前缀和&#xff1a;O(n) 查找前缀和&#xff1a;O(1) 一维动态规划&#xff1a;O(n) 二维动态规划&#xff1a;O(n^2) 回溯…

【深度学习每日小知识】Logistic Loss 逻辑回归

逻辑回归的损失函数 线性回归的损失函数是平方损失。逻辑回归的损失函数是对数损失&#xff0c;定义如下&#xff1a; L o g L o s s ∑ ( x , y ) ∈ D − y log ⁡ ( y ′ ) − ( 1 − y ) log ⁡ ( 1 − y ′ ) LogLoss\sum_{(x,y)\in D}-y\log(y)-(1-y)\log(1-y) LogLoss…

卡尔曼滤波:理论与代码

卡尔曼滤波&#xff1a;理论与代码 引言 卡尔曼滤波是一种用于估计系统状态的优化技术&#xff0c;特别适用于含有噪声的测量数据和系统动态变化的情况。本文将简单探讨卡尔曼滤波的理论基础、数学公式的推导&#xff0c;并通过Python代码示例演示其在实际应用中的效果。 一…

基于SSM+vue的篮球场预约管理系统(Java毕业设计)

大家好&#xff0c;我是DeBug&#xff0c;很高兴你能来阅读&#xff01;作为一名热爱编程的程序员&#xff0c;我希望通过这些教学笔记与大家分享我的编程经验和知识。在这里&#xff0c;我将会结合实际项目经验&#xff0c;分享编程技巧、最佳实践以及解决问题的方法。无论你是…

Python 最新版本 3.12.1 环境配置(windows)

文章目录 python 3.12.1环境安装3.12.1 网盘下载3.12.1 官网下载 python 安装完成测试第一个 python 程序Hello Python python 3.12.1环境安装 3.12.1 网盘下载 python 3.12.1 百度网盘地址&#xff1a;https://pan.baidu.com/s/1SAcH_uH0T3DiERn6AZeQlg?pwd4242 提取码&a…

不同activity项目创建时的区别

在 Android Studio 中创建项目时&#xff0c;可以选择创建不同类型的 Activity 作为应用程序的入口点。其中&#xff0c;包括 Empty Activity、Basic Activity、Empty Compose Activity 和 Basic Compose Activity 四种类型。 Empty Activity&#xff1a;这是最简单的 Activity…

最新靠谱可用的-Mac-环境下-FFmpeg-环境搭建

最近在尝试搭建 FFmpeg 开发环境时遇到一个蛋疼的事&#xff0c;Google 了 N 篇文章竟然没有一篇是可以跑起来的&#xff01; 少部分教程是给出了自我矛盾的配置&#xff08;是的&#xff0c;按照贴出来的代码和配置&#xff0c;他自己都跑不起来&#xff09;&#xff0c;大部…

【Leetcode】2182. 构造限制重复的字符串

文章目录 题目思路代码 题目 2182. 构造限制重复的字符串 问题&#xff1a;给你一个字符串 s 和一个整数 repeatLimit &#xff0c;用 s 中的字符构造一个新字符串 repeatLimitedString &#xff0c;使任何字母 连续 出现的次数都不超过 repeatLimit 次。你不必使用 s 中的全…

【新年福利】买1送1,告别360!这个系统清理神器干净无广告!

在日常的工作中&#xff0c;面对重要文件时往往都会备份一份&#xff1b;在下载文件时&#xff0c;有时也会不小心把一份文件下载好多次。这些情况会导致电脑中出现重复的文件&#xff0c;删除这些重复文件&#xff0c;可以节省电脑空间&#xff0c;帮助提高电脑运行速度。那么…

mysql关于创建表的小试题

目录 例题&#xff1a; 解题思路及步骤&#xff1a; 实验步骤&#xff1a; 步骤一&#xff1a;创建数据库 步骤二&#xff1a;创建表 步骤三&#xff1a;插入数据 例题&#xff1a; 1、创建一个英雄表(hero)&#xff0c;管于四大名著的主键 nam…

2-《Java并发编程实战》(Java Concurrency in Practice) 代码示例

说明 这是针对《Java并发编程实战》(Java Concurrency in Practice)一书中的示例代码进行扩展&#xff0c;并且进行验证的完整代码&#xff0c;具体背景可看这篇文章&#xff1a;1-《Java并发编程实战》(Java Concurrency in Practice) 代码示例 下面的示例代码都是针对书中的&…

Android Studio个性化修改

Android Studio原始界面看着也太无趣了叭&#xff0c;话不多说跟步骤走就可以。 1.更改Android Studio主题及背景 1.背景修改 File->Settings->Plugins&#xff0c;搜索Sexy Editor 重启后&#xff0c;左侧边栏出现Other Settings选项&#xff0c;点击SexyEditor进行背…

二分搜索边界问题的简单结论

引言 二分搜索是一个说简单也很简单&#xff08;代码很固定&#xff0c;也没几行&#xff09;&#xff0c;说难也很难&#xff08;边界问题可能会让人想不太清楚&#xff09;。 事实上&#xff0c;边界问题也是是算法题中普遍存在的难点。 这篇文章讲两个简单的结论&#xff0…

009-Zynq基操之如何去玩转PL向PS的中断(对新手友好,走过路过千万不要错过)

文章目录 前言一、PL-PS的中断是啥&#xff1f;二、PL-PS端中断详细步骤1.ZYNQ核配置2.PS端中断函数配置3.需要拓展多个中断函数 总结 前言 本设计跟我的ZYNQ实战合集专栏中的脉冲触发电路有关系&#xff0c;也正好趁这个机会讲述一下PL-PS的中断系统&#xff0c;如何去触发中…

Java 设计模式

1.单例设计模式 对某个类只能存在一个对象实例&#xff0c;并且该类只提供一个取得其对象实例的方法。 1.1 饿汉式 构造器私有化 --> 防止直接new类的内部创建对象提供一个static的public方法 getInstance class GirlFriend {private String name;private static GirlFri…

MySQL第二次

作业要求&#xff1a; 作业代码实现&#xff1a; create database db_04 default charsetutf8mb4;use db_04;create table if not exists t_hero(id int primary key auto_increment,name varchar(20) not null unique,nickname varchar(50) not null unique,address varchar…

【Python机器学习系列】建立KNN模型预测心脏疾病(完整实现过程)

这是Python程序开发系列原创文章&#xff0c;我的第198篇原创文章。 一、问题 对于表格数据&#xff0c;一套完整的机器学习建模流程如下&#xff1a; 针对不同的数据集&#xff0c;有些步骤不适用即不需要做&#xff0c;其中橘红色框为必要步骤&#xff0c;由于数据质量较高&…

4点优势,昂首资本使用浮动差价不使用固定差价的原因

在交易中&#xff0c;很多投资者和昂首资本一样&#xff0c;会使用浮动点差而不使用固定点差&#xff0c;那是因为投资者和昂首资本一样认为&#xff0c;使用浮动差价交易会比使用固定价差交易更有优势。 首先在大部分交易时段&#xff0c;价差缩小。正如投资者和昂首资本所知…