基于Spring Security 6的OAuth2 系列之十八 - 高级特性--OIDC1.0协议之一

之所以想写这一系列,是因为之前工作过程中使用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

上一章,我们讲解了设备授权码模式,对于OAuth2.1的重要新特性,我们就已经讲完了。本章主要讲解一种基于OAuth2基础之上的OIDC1.0协议以及Spring Security如何实现。

1 OIDC1.0协议

1.1 ODIC1.0是什么

我们在使用OAuth2返回的access_token作为授权使用,其中并不包括用户身份信息,同时如果要获取用户信息,可能需要在资源服务器中设置对应接口进行提供。是的,OAuth2.0本身是没有身份认证和个人信息获取的,你可以自己规定一下实现返回用户信息,同时在资源服务器实现获取用户信息功能,这样你就实现ODIC1.0的效果。

ODIC1.0协议RFC6749全称是OpenId Connect Core,在2014年发布的,由OpenID Foundation推动。是一个基于OAuth2.0协议的身份认证标准协议(OAuth2.0的超集)。OAuth2.0更关注授权,OIDC1.0使用OAuth2的授权服务器来为第三方客户端提供用户的身份认证,并把对应的身份认证信息ID Token传递给客户端,且提供获取用户信息的接口(UserInfo Endpoint),适用于各种类型的客户端(比如服务端Server应用,移动端APP,JS应用(SPA)等)。

也就是说,如果你实现了ODIC1.0,也就是实现了OAuth2,或者说ODIC1.0是OAuth2的扩展,增加身份认证以及用户信息获取。

1.2 为什么要使用ODIC1.0

那么为什么要使用ODIC1.0?或者换句话说,身份认证和获取用户信息在什么场景下需要?

  • SSO:Single Sign-On单点登录,即同时访问多个应用仅需要登录一次。在同一个公司下的不同业务系统,实现单点登录时非常有必要的,因此身份认证的功能就很有必要
  • 安全和隐私:虽然可以在access_token返回用户信息,但是OAuth2本身并没有这方面规定,因此ID Tokens 和数字签名技术来确保身份信息的安全传输和验证
  • 新型客户端要求:随着IoT、SPA的兴起,在这些现代应用架构中需要一种高效工作的身份验证方法,支持这些应用的身份验证需求,允许令牌和用户信息的安全传输

1.3 ODIC1.0的关键流程和角色

在这里插入图片描述

上图是协议官方图,看起来很多概念,其实就是在OAuth2的流程几乎一样

  • 步骤1:RP(客户端)向 OP(授权服务器)发送请求
  • 步骤2: OP(授权服务器)对EU(用户)进行身份验证并获取授权
  • 步骤3: OP(授权服务器)返回ID Token 和Access Token
  • 步骤4: RP(客户端)可以使用 Access Token 向 UserInfo Endpoint 发送请求
  • 步骤5: UserInfo Endpoint 返回有关最终用户的声明

我们需要对照一下角色概念,就会发现没什么不一样。

OIDC1.0OAuth2.0说明
EUUser Resource Owner用户
RPClient客户端
OPAuhtorization Server授权服务器
Resource ServerResource Server资源服务器

1.4 ODIC1.0的不同Flow(不同模式)

在了解ODIC1.0不同模式之前,我们可以回顾一下OAuth2的四种模式。而ODIC1.0采用的就是OAuth2的其中两种:授权码模式(Authorization Code Grant)简化模式(Implicit Grant)。我们知道,这两种模式几乎一样,唯一不同的就是简化模式删除了授权码code的步骤,直接返回access_token。

而ODIC1.0由于可以返回Id Token和Access Token,因此可以组合成不同的方式,由response_type来决定,比如response_type=code就是授权码模式,response_type=id_token token就是简化模式,response_type=id_token则只会返回id_token。同理,其对照表如下:

response_typeFlow
codeAuthorization Code Flow
id_tokenImplicit Flow
id_token tokenImplicit Flow
code id_tokenHybrid Flow
code tokenHybrid Flow
code id_token tokenHybrid Flow

注意:为何只支持授权码模式(Authorization Code Grant)简化模式(Implicit Grant),另外两种模式密码模式(Resource Owner Password Credentials Grant)和客户端模式(Client Credentials Grant)为何不支持?

  • 密码模式(Resource Owner Password Credentials Grant):之所以不支持是因为本身需要提供给客户端用户名和密码,因此客户端已经无需要对用户身份进行验证了
  • 客户端模式(Client Credentials Grant):之所以不支持是因为该过程不需要用户参与,因此也是默认用户身份合法,所以也无需进行用户身份验证了

1.5 id_token的内容

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

2 代码演示

从上面已经大概了解了OIDC是做什么用的,那么接下来我们看看Spring Security是如何实现OIDC的。

代码参考lesson12

1)新建lesson12子模块,其pom引入如下:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-oauth2-authorization-server</artifactId>
    </dependency>
</dependencies>

2)在resources目录下,配置yaml文件,配置端口和Spring Security的账号密码

server:
  port: 9000

logging:
  level:
    org.springframework.security: trace


spring:
  security:
    # 使用security配置授权服务器的登录用户和密码
    user:
      name: user
      password: 1234

3)在config包下面,配置SecurityConfig

@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());
        // 同时作为资源服务器,使用/userinfo接口
        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
                .anyRequest().authenticated()).formLogin(withDefaults());
        return http.build();
    }

    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        RegisteredClient registeredClient3 = RegisteredClient.withId(UUID.randomUUID().toString())
                // 客户端id
                .clientId("oidc-client")
                // 客户端密码
                .clientSecret("{noop}secret")
                // 客户端认证方式
                .clientAuthenticationMethods(methods ->{
                    methods.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
                })
                // 配置授权码模式
                .authorizationGrantTypes(grantTypes -> {
                    grantTypes.add(AuthorizationGrantType.AUTHORIZATION_CODE);
                })
                // 需要授权确认
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
                // 回调地址
                .redirectUri("http://localhost:8080/login/oauth2/code/oidc-client")
                .postLogoutRedirectUri("http://localhost:8080/")
                // 授权范围
                .scopes(scopes->{
                    scopes.add(OidcScopes.OPENID);
                    scopes.add(OidcScopes.PROFILE);
                })
                .build();

        return new InMemoryRegisteredClientRepository(registeredClient3 );
    }

}

4)启动类Oauth2Lesson12ServerApplication,并启动项目

@SpringBootApplication
public class Oauth2Lesson12ServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(Oauth2Lesson12ServerApplication.class, args);
    }

}

5)测试

请求授权码code

在这里插入图片描述

登录

在这里插入图片描述

再次请求授权码code

在这里插入图片描述

获取token

在这里插入图片描述

6)获取用户信息

在这里插入图片描述

7)验证id_token

在这里插入图片描述

结语:本章简介了OIDC1.0协议,并使用Spring Security演示了一遍OIDC1.0协议的实现。下章我们将去窥探其原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

linmoo1986

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值