目录

  • 1. Spring Security 5.6
  • 2. Spring Security OAuth2
  • 3. OAuth2、OIDC1.0的典型场景
  • 4. 关于Spring Authorization Server
  • 5. Spring Authorization Server扩展实现
  • 6. 后续...


最近由于工作需要,目前正对此开源库spring-authorization-server进行调研及扩展,故作此文章记录相关历程。


1. Spring Security 5.6

起初接触Spring Security,总感觉抓不到重点,不知道从哪入手,所以给人一种不够轻量的感觉。想要用好Spring Security,还是需要对其架构有一定深入研究的,不然遇到问题都不知道从哪入手。先简单说说Spring Security的总体架构。

spring framework 被别人调用api_spring security


如上图SpringSecurity是建立在Servlet Filter的基础上,

Spring Security通过DelegatingFilterProxy连接Servlet Filter和Spring上下文,

核心的SpringSecurity过滤器执行逻辑可参见FilterChainProxy

FilterChainProxy会根据配置维护多条过滤器链SecurityFilterChain

而单条过滤器链SecurityFilterChain由多个Filter组成,每个Filter都会各自执行具体逻辑。

DelegatingFilterProxy - 桥接Servlet Filter和Spring上下文

  • FilterChainProxy - 包含SecurityFilterChain(Security过滤器链)且根据path匹配不同的过滤器链
  • SecurityFilterChain - 包含多个SecurityFilter(Security过滤器)
  • SecurityFilter - 具体的过滤器实现(GenericFilterBean)

spring framework 被别人调用api_oidc_02

FilterChainProxy通过调用SecurityFilterChain.matches方法来决定执行哪条过滤器链SecurityFilterChain
可以理解为根据RequestMatcher来区分执行哪条链,仅执行第一次匹配成功的过滤器链。

注:默认不配置RequestMatcher则对所有请求都会执行过滤器链

我们可以通过debug断点,来观察当前Spring Security应用有多少条过滤器链,以及各个过滤器的执行顺序。

我通常都会在FilterChainProxy类中的getFilters方法中如图位置设置断点。

spring framework 被别人调用api_Server_03

在断点处,即可查看当前SecuritySecurity共有多少条过滤器链,以及每条过滤器链中包含的过滤器,

如此以来更有助于清楚的了解Security的执行过程及问题排查。

spring framework 被别人调用api_spring security_04


说了这么多的Filter,那Spring Security是怎么设置这些Filter的呢?

比如一段典型的Spring Security配置:

@EnableWebSecurity
public class DefaultSecurityConfig {

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeRequests(authorizeRequests -> authorizeRequests
                        .anyRequest().authenticated()
                )
              	//表单登录
                .formLogin(withDefaults());
                //OAuth2相关
                //.oauth2Login(...)
                //.oauth2Client(...)
                //.oauth2ResourceServer(...)
        return http.build();
    }
}

如上代码中,可以点进formLogin方法,可以发现每个配置入口都有一个对应的Configurer对象,如FormLoginConfigurer extends SecurityConfigurerAdapter,可以进入具体Configure对象的configure方法进行查看,便可发现具体的filter及其相关设置。

spring framework 被别人调用api_spring_05


spring framework 被别人调用api_spring_06

具体单个Filter的实现可查看源码,
比如认证相关的Filter,其总体结构如下:

AuthenticationProcessingFilter - 认证过滤器

  • AuthenticationManager -> ProviderManager - 认证管理器
  • AuthenticationProvider - 具体的认证逻辑实现
  • AuthenticationSuccessHandler - 认证成功处理器
  • AuthenticationFailureHandler - 认证失败处理器

注:
关于SpringSecurity不再讲更多了,想要了解更多可参见我之前的文章:SpringSecurity5.6架构杂记

2. Spring Security OAuth2

Spring Seurity在2019年11月发布了OAuth 2.0 Migration Guide

  • 宣布禁用原spring-security-oauth,并将其合并到Spring Security 5.2+,
  • 并且不再提供Authorization Server的支持。

后续在广大开发者的呼声下,在2020年4月Spring官方发布声明Announcing the Spring Authorization Server

截止到今天(2022-03-13):

如上几大模块组成了Spring Security对OAuth2生态的最新支持,
本文使用的OAuth2相关模块也是基于此最新生态。

3. OAuth2、OIDC1.0的典型场景

之前看的好多OAuth2流程示例,都是基于Web应用(即包含Web后端),而现如今普遍采用前后端分离的架构,即Web后端提供API,前端采用SPA实现(如Vue等),即前端作为Client端,后端作为Resource Server端。而SPA Client端暴露在用户浏览器端(用户可以通过浏览器查看SPA代码),因此无法在SPA Client端安全的存储client_secret。

注:
关于OAuth2及OIDC的相关术语、端点、选型等可参见我之前的文章:《OIDC(及Oauth)选型建议及SSO、SLO方案》

故基于SPA场景推荐使用如下模式:

  • 授权码Code流程 + PKCE
  • Authorization Code Flow with Proof Key for Code Exchange (PKCE)
  • PKCE使用code_challenge及code_verifier代替原client_secret验证,避免暴露client_secret
  • 支持Refresh Token流程并支持Refresh Token轮换
  • 提升用户体验,无需反复登录
  • 提升安全性,即每次通过refresh_token换取新token后,作废旧的refresh_token并生成且和返回新的refresh_token

所以此次对spring-authorization-server的使用及扩展也是重点关注此SPA场景

同时结合OIDC相关登出协议,期望扩展支持单点登录SSO、单点登出SLO的场景。

协议

功能

OpenID Connect RP-Initiated Logout 1.0

RP端向OP发出的初始登出请求(适用于单点登出SLO,end_session_endpoint),

OP首先清除OP端的session,

然后向当前session下的其他已登录的RP触发Front-Channel或者Back-Channel登出请求,

最终可通过post_logout_redirect_uri 重定向会当前RP

OpenID Connect Front-Channel Logout 1.0

返回OP前端登出页面,

页面通过嵌入iframe(src=frontchannel_logout_uri),

同时触发当前OP Session对应的多个Client的登出接口

OpenID Connect Back-Channel Logout 1.0

OP直接向当前OP Session对应的多个Client后端服务(不依赖User Agent,如不依赖浏览器)发送登出请求backchannel_logout_uri

且适用于User Agent被关闭的时候也可以退出登录。

OpenID Connect Session Management 1.0

RP端通过check_session_iframe监听当前UserAgent中用户的登录状态,

收到登录状态改变通知后RP应该清除自己的Session信息、页面跳转等。

查看Spring Security相关源码,可以发现如上提到的核心功能支持情况如下表:

注:
由于本文重点在讲解Authorization Server后端服务,并未对SPA端代码进行集成与示例,
暂以Spring Security OAuth2 Login模块模仿SPA端协议执行过程,
关于SPA端可以找寻对应的OAuth/OIDC客户端库实现。

功能

Authorization Server

OAuth2 Client

Authorization Code Flow with PKCE

授权码Code流程 + PKCE

✔️

✔️

Refresh Token without client_secret

PKCE后续无client_secret刷新token



Refresh Token Retotation(Reuse)

令牌轮换

✔️

✔️

OAuth2 SSO

单点登录

✔️

✔️

OIDC end_session_endpoint

单点登出


✔️

所以以上表格中未实现的,也就是需要后续进行扩展的功能。

4. 关于Spring Authorization Server

Spring Authorization Server的核心协议支持可参见:
https://github.com/spring-projects/spring-authorization-server/wiki/Feature-List#completed-features

目前已提供的Filter及说明如下表:

核心FIlter

endpoint

说明

OAuth2AuthorizationEndpointFilter

GET|POST /oauth2/authorize

授权端点,即RP跳转到OP的认证入口,

且EU认证通过后,OP重定向回RP,且附加code参数

OAuth2ClientAuthenticationFilter

POST /oauth2/token|introspect|revoke

即RP向OP发送获取token请求、检查token、吊销token时,OP端提供的认证逻辑

OAuth2TokenEndpointFilter

POST /oauth2/token

Token端点,RP向OP请求Token(通过code换token、执行refresh_token流程)

OAuth2TokenIntrospectionEndpointFilter

POST /oauth2/introspect

校验Token端点,RP请求OP检测token有效性

OAuth2TokenRevocationEndpointFilter

POST /oauth2/revoke

吊销Token端点,RP请求OP吊销token

OidcProviderConfigurationEndpointFilter

GET /.well-known/openid-configuration

OIDC协议发现端点

OidcUserInfoEndpointFilter

GET /userinfo

用户信息端点,提供用户信息查询

OidcClientRegistrationEndpointFilter

POST /connect/register

客户端信息注册端点(暂未使用)

注:
关于Spring Authorization Server核心FIlter及其执行逻辑说明,
可参见我之前的文章:《SpringSecurity5.6架构杂记 - OAuth2/Authorization Server》

5. Spring Authorization Server扩展实现

关于Spring Authorization Server的扩展实现,
我已经放到了我的开源代码库中了:https://gitee.com/luoex/spring-cloud-demo/tree/develop/spring-security-demo

模块

说明

oauth2-auth-server-oidc

OIDC AuthServer核心功能Base模块,其他示例模块均依赖此模块

oauth2-auth-server-oidc-minimal

OIDC AuthServer最小集成示例

oauth2-auth-server-oidc-token-ext

OIDC AuthServer扩展token示例

oauth2-auth-server-oidc-resource

OIDC AuthServer同时作为Resource Server示例

oauth2-auth-server-oidc-login-captcha

OIDC AuthServer自定义登录图片验证码集成示例

oauth2-auth-server-oidc-login-third

OIDC AuthServer集成第三方OAuth2(GitHub)登录示例

oauth2-auth-server-oidc-session

OIDC AuthServer共享session集成示例(支持AuthServer分布式部署)

oauth2-auth-server-oidc-combo

OIDC AuthServer综合集成示例(自定义登录页、手机验证码登录、token扩展、第三方登录、同时作为资源服务器)

oauth2-auth-server-oidc 的核心扩展点包括:

  • 支持自定义token(id_token、access_token)
  • 支持自定义userInfo
  • 支持集成第三方OAuth2登录(如GitHub)及第三方用户注册
  • 完善PKCE及refresh_token验证
  • 扩展支持OIDC SLO(end_session_endpoint)
  • 自定义通用登录模型(使用Map<String, String> authParam代替原唯一username参数)
  • 支持自定义登录页(支持登录表单Ajax提交)、授权确认页

针对之前提到的SPA典型场景,结合此次扩展给出oauth2-auth-server-oidc 认证及刷新Token的时序图如下:

spring framework 被别人调用api_oidc_07

放几张集成示例的效果图:

默认登录页:

spring framework 被别人调用api_spring_08

默认授权确认页面:

spring framework 被别人调用api_oauth2_09

自定义图片验证码登录页:

spring framework 被别人调用api_spring security_10

自定义组合登录页示例(支持第三方GitHub登录):

spring framework 被别人调用api_spring_11

自定义组合登录页 - 手机验证码登录示例:

spring framework 被别人调用api_oidc_12

6. 后续…

关于oauth2-auth-server-oidcspring-authorization-server的扩展实现会持续完善,有时间也会将扩展思路写到博客上。