当前位置: 首页 > article >正文

基于Spring Security 6的OAuth2 系列之九 - 授权服务器--token的获取

之所以想写这一系列,是因为之前工作过程中使用Spring Security OAuth2搭建了网关和授权服务器,但当时基于spring-boot 2.3.x,其默认的Spring Security是5.3.x。之后新项目升级到了spring-boot 3.3.0,结果一看Spring Security也升级为6.3.0。无论是Spring Security的风格和以及OAuth2都做了较大改动,里面甚至将授权服务器模块都移除了,导致在配置同样功能时,花费了些时间研究新版本的底层原理,这里将一些学习经验分享给大家。

注意由于框架不同版本改造会有些使用的不同,因此本次系列中使用基本框架是 spring-boo-3.3.0(默认引入的Spring Security是6.3.0),JDK版本使用的是19,本系列OAuth2的代码采用Spring Security6.3.0框架,所有代码都在oauth2-study项目上:https://github.com/forever1986/oauth2-study.git

目录

  • 1 OAuth2TokenEndpointFilter原理
    • 1.1 使用Postman获取token
    • 1.2 三种token的作用及内容
    • 1.3 OAuth2TokenEndpointFilter原理
    • 1.4 OAuth2TokenGenerator(token生成器)
    • 1.5 Opaque Token 和 JWT Token
  • 2 自定义JWT加密方式

前面我们对Spring Authrization Server的授权码模式都做了比较详细的解析,也知道通过/oauth2/token接口,可以获取到token,其处理是OAuth2TokenEndpointFilter过滤器,本章我们来更详细说一下OAuth2TokenEndpointFilter过滤器以及token。

1 OAuth2TokenEndpointFilter原理

1.1 使用Postman获取token

之前我们都是使用代码方式的客户端访问授权服务器,现在我们使用非代码方式,一步步看看授权码模式下如何获得token。

1)启动lesson04子模块的授权服务器
2)GET方式访问授权码:http://localhost:9000/oauth2/authorize?client_id=oidc-client&redirect_uri=http://localhost:8080/login/oauth2/code/oidc-client&response_type=code&scope=openid profile 如下图

注意:这里会跳转到登录界面。里面以一个_csrf的value,拷贝下来,登录时需要。

在这里插入图片描述

3)登录授权服务器,把步骤2)中的_csrf复制到此,作为请求body中的一个参数,POST请求:http://localhost:9000/login

注意:这里跳转到授权页面,有一个state返回值,拷贝下来,下一步获取授权code需要传入

在这里插入图片描述

4)获得授权码之前,先将Postman设置为不自动跳转,如下图

在这里插入图片描述

5)获得授权码,将步骤3)中的state拷贝过来作为入参。POST请求访问:http://localhost:9000/oauth2/authorize

注意:这里会获得授权码code,在返回的header的Location属性中,将code拷贝下来,请求token需要

在这里插入图片描述

6)获得token,将步骤5)中的授权码code拷贝过来,访问如下两个图。POST请求:http://localhost:9000/oauth2/token

注意:

  • 设置Body参数,分别是grant_type、code、client_id、redirect_uri
  • 设置Authorization为Basic Auth,因为我们配置的是client_secret_basic;

在这里插入图片描述

设置Authorization的Basic Auth

在这里插入图片描述

7)这时候得到access_token,就是我们需要的最终认证token,我们使用jwt在线解析,就可以看到Header、Payload以及私钥。

关于JWT的相关信息,可以参考《Spring Security 6 系列之集成JWT》

在这里插入图片描述

1.2 三种token的作用及内容

我们从上面步骤6中可以看到返回由三种token:

  • access_token:这个就是我们拥有从资源服务器获取资源需要的token
  • refresh_token:这个是用于刷新access_token使用的token,因为我们默认access_token只有5分钟有效期,需要刷新token才行
  • id_token:这个是基于OIDC1.0协议身份验证的一个token,后面会详细讲OIDC1.0,它是使用JWT格式的。

其中我们来说一下access_token的内容

字段说明
iss (Issuer Identifier)提供认证信息者的唯一标识。一般是一个https的URL
aud (Audience)标识 id_token 的受众。必须包含OAuth2的client_id
nbftoken在该时间之前无效,一般设置签发的时间
scope授权范围
exp (Expiration time)过期时间,超过此时间的id_token会作废不再被验证通过
iat (Issued At Time)token使用JWT发布的时间
auth_time (AuthenticationTime)用户完成认证的时间。如果客户端发送AuthN请求的时候携带max_age的参数,则此Claim是必须的
nonce客户端发送请求的时候提供的随机字符串,用来减缓重放攻击,也可以来关联ID Token和RP本身的Session信息
acr (Authentication Context Class Reference)身份验证上下文类参考
amr (Authentication Methods References)身份验证上下文类参考
azp (Authorized party)被授权方 - 向其颁发 id_token 的一方

1.3 OAuth2TokenEndpointFilter原理

从上面流程知道获取token需要访问/oauth2/token接口,从《系列之八-授权服务器–Spring Authrization Server的基本原理》中得知OAuth2TokenEndpointFilter过滤器是用于拦截/oauth2/token接口的。

1)那么我们先从OAuth2TokenEndpointFilter的doFilter入手,看到是通过authenticationManager,那就是熟悉使用ProviderManager中代理了OAuth2AuthorizationCodeAuthenticationProvider

在这里插入图片描述

2)从OAuth2AuthorizationCodeAuthenticationProvider中,我们看到如下图代码,就是使用tokenGenerator生成token

在这里插入图片描述

3)tokenGenertor也是一个代理DelegatingOAuth2TokenGenerator,里面有多个tokenGenertor。这里默认使用的是JwtGenerator

在这里插入图片描述

4)JwtGenerator中使用jwtEncoder进行生成真正JWT的token

在这里插入图片描述

5)从上面整个流程可以看出,主要是使用tokenGenertor进行生成

1.4 OAuth2TokenGenerator(token生成器)

从上面流程可以知道,最终token都是使用OAuth2TokenGenerator生成器生成,以下是授权服务器常见的生成器
在这里插入图片描述

下面介绍一下不同的token生成器:

  • DelegatingOAuth2TokenGenerator:token生成器代理,用于循环选择适合的token生成器,默认情况下,自动加载3个tokenGenerator生成器,如下图:
    在这里插入图片描述

  • JwtGenerator:生成JWT格式的access_token和id_token,如果客户端配置中oauth2_registered_client表中字段token_settings配置为self-contained时,access_token和id_token由JwtGenerator生成,也就是会生成JWT格式的token
    在这里插入图片描述

  • OAuth2AccessTokenGenerator:生成BASE64的access_token,长度为128,如果客户端配置中oauth2_registered_client表中字段token_settings配置为reference时,access_token和id_token由OAuth2AccessTokenGenerator生成,也就生成BASE64格式的token

  • OAuth2RefreshTokenGenerator:生成刷新refresh_token,也是基于BASE64,长度为128

1.5 Opaque Token 和 JWT Token

我们从上面知道,access_token是可以根据token_settings的配置决定生成的方式,当值为self-contained时,使用JWT Token,当值为reference时,使用BASE64格式。而这种BASE64也称为Opaque Token。大家可以尝试一下,把token_settings配置为reference,token就会是一个无意义的字符串,而JWT Token则是可以解析其中存储的信息。这Opaque Token其实是为了安全,不暴露用户信息。因此如果只是内部系统之间使用,推荐使用JWT Token,如果是共用的,推荐Opaque Token。

2 自定义JWT加密方式

我们从源码OAuth2AuthorizationServerJwtAutoConfiguration可以看到默认情况下是自动生成一个RSA非对称的加密,如下图:

在这里插入图片描述

但是如果使用默认的情况会有2个问题:

  • 安全问题:我们知道密钥是经常需要轮换的,如果使用默认我们就无法定时轮换,当然重新启动就能切换
  • 集群问题:如果我们的授权服务器是一个集群,那么每个服务器的密钥都是不一样,无法实现集群效果

因此我们只需要自定义jwkSource,就可以自己使用自己生成的RSA密钥。

代码参考lesson05子模块

1)生成自己的RSA密钥对,这个可以使用keytool

生成jks,在命令行模式下,执行下面语句
keytool -genkeypair -alias demo -keyalg RSA -keypass linmoo -storepass linmoo -keysize 2048 -keystore demo.jks
这时候会在目录下生成一个demo.jks文件,该文件包括私钥和公钥 。该文件拷贝到项目resources文件下面,供签名使用
在这里插入图片描述

2)新建lesson05子模块,其代码拷贝lesson04子模块的src和resources目录,以及其pom依赖

3)拷贝过来之后,注意:yaml文件中的这2处需要修改,不然mybatis-plus会加载不了entity和handler
在这里插入图片描述

4)SecurityConfig配置jwkSource、jwtDecoder以及读取密钥对的方法

@Configuration
public class SecurityConfig {

    // 自定义授权服务器的Filter链
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
                // oidc配置
                .oidc(withDefaults())
        ;
        // 资源服务器默认jwt配置
        http.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(withDefaults()));
        // 异常处理
        http.exceptionHandling((exceptions) -> exceptions.authenticationEntryPoint(
                new LoginUrlAuthenticationEntryPoint("/login")));
        return http.build();
    }

    // 自定义Spring Security的链路。如果自定义授权服务器的Filter链,则原先自动化配置将会失效,因此也要配置Spring Security
    @Bean
    @Order(SecurityProperties.BASIC_AUTH_ORDER)
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests((authorize) -> authorize
                .requestMatchers("/demo", "/test").permitAll()
                .anyRequest().authenticated()).formLogin(withDefaults());
        return http.build();
    }

    /**
     * 访问令牌签名
     */
    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        RSAKey rsaKey = new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return new ImmutableJWKSet<>(jwkSet);
    }

    /**
     * 其 key 在启动时生成,用于创建上述 JWKSource
     */
    private static KeyPair generateRsaKey() {
        KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("demo.jks"), "linmoo".toCharArray());
        KeyPair keyPair = factory.getKeyPair("demo", "linmoo".toCharArray());
        return keyPair;
    }
}

5)当然,这个部分我们还可以改进,就是把密钥对放到nacos或者redis上面,这样我们可以手动更新。

结语:本章我们讲解了OAuth2的token以及Spring Security如何实现的底层原理,并自定义jwkSource来实现使用自己的RSA的密钥对。其中还有一个点没有讲,就是token的刷新。下一章我们将详细讲一下如何刷新token以及其代码原理。


http://www.kler.cn/a/531759.html

相关文章:

  • C基础寒假练习(2)
  • Nginx笔记220825
  • ubuntu18.04环境下,Zotero 中pdf translate划线后不翻译问题解决
  • SQL优化
  • 电控三周速成计划参考
  • 传输层协议 UDP 与 TCP
  • Golang 并发机制-5:详解syn包同步原语
  • 【玩转 Postman 接口测试与开发2_015】第12章:模拟服务器(Mock servers)在 Postman 中的创建与用法(含完整实测效果图)
  • 大模型综述一镜到底(全文八万字) ——《Large Language Models: A Survey》
  • DeepSeek:以AI创新引领全球科技潮流
  • 容器适配器(以stack和queue为例)
  • DeepSeek 提示词之角色扮演的使用技巧
  • openssl 静态编译
  • 下载hugging face上的数据集
  • 【Go - 小顶堆/大顶堆】
  • CSDN原力值提升秘籍:解锁社区活跃新姿势
  • AI开发学习之——PyTorch框架
  • Java控制台登录系统示例代码
  • nginx 新手指南
  • 强化学习数学原理(五)——随机近似与随机
  • 携程Java开发面试题及参考答案 (200道-下)
  • 分享半导体Fab 缺陷查看系统,平替klarity defect系统
  • 【leetcode练习·二叉树拓展】快速排序详解及应用
  • 蓝桥与力扣刷题(234 回文链表)
  • PHP代码审计学习02
  • Vue-data数据