之所以想写这一系列,是因为之前工作过程中使用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
前面2章我们对资源服务器的基本配置和底层原理做了一个演示和说明,那么资源服务器还有哪些配置,一般资源服务器可能会作为内部网关使用,那么又该如何配置。这一章会讲述自定义资源服务器的配置到一个生产可用的网关配置。
从下图我们可以看到,其资源服务器可以自定义配置,下面就一些常见的配置做一下说明。
1 自定义异常处理
这里的异常处理其实是使用Spring Security的异常处理,因此只需要实现以下2个不同的处理逻辑即可:
- AuthenticationEntryPoint:处理认证失败的异常
- AccessDeniedHandler:处理没有权限或者没有登录的异常
2 自定义JWT
2.1 加载本地jwtdecoder
我们从上一章知道资源服务器会去授权服务器请求得到JWT的公钥,或者配置某一个地方获取公钥。这里还有一种办法,就是本地公钥,可以通过自定义decoder方式,获取本地公钥,这样就可以不依赖其它服务。
代码参考lesson08子模块
1)新建lesson08子模块,复制和lesson07子模块一致
2)在SecurityConfig配置下,配置自己加载本地的公钥
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
// userInfo接口需要SCOPE_profile权限
.requestMatchers("/userInfo").hasAuthority("SCOPE_profile")
// 其它访问都需要鉴权
.anyRequest().authenticated()
)
// 资源服务器默认配置
.oauth2ResourceServer((oauth2) -> oauth2.jwt(jwt -> jwt
// 配置set-uri替换yaml的jwk-set-uri配置
// .jwkSetUri("")
// 配置自己的decoder,替换从远程获取的公钥
.decoder(jwtDecoder())
));
return http.build();
}
/**
* 自定义JwtDecoder
*/
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(generateRsaKey()).build();
}
/**
* 其 key 在启动时生成,用于创建上述 JWKSource
*/
private static RSAPublicKey generateRsaKey() {
KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("demo.jks"), "linmoo".toCharArray());
KeyPair keyPair = factory.getKeyPair("demo", "linmoo".toCharArray());
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
return publicKey;
}
}
3)把lesson05子模块resources下面的demo.jks复制到lesson08子模块
4)测试方式和开篇中的《系列之十二 - 资源服务器–开篇中 3 自定义获取Jwks信息》一样
2.2 自定义AuthenticationConverter
在JwtAuthenticationProvider中,有一个功能就是通过JwtAuthenticationConverter
将JWT的token转换为AbstractAuthenticationToken,而这个AbstractAuthenticationToken里面存储了用户的信息,包括权限scopes,可供下游使用。
在官方网站说可以通过重新实现JwtAuthenticationConverter,如下图,这里就不演示了,因为一般情况下,我们权限管理不会采用Spring Security这一套。
2.3 自定义OAuth2TokenValidator
我们在JwtAuthenticationProvider中还看到一个OAuth2TokenValidator,其作用就是用于对JWT的token进行格外验证,默认会设置JwtIssuerValidator和X509CertificateThumbprintValidator的验证
如果你想自定义,你可以在定义时,设置自己想要的自定义,比如下面的例子就是设置JwtTimestampValidator和JwtIssuerValidator
/**
* 自定义JwtDecoder
*/
public JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(generateRsaKey()).build();
OAuth2TokenValidator<Jwt> withClockSkew = new DelegatingOAuth2TokenValidator<>(
new JwtTimestampValidator(Duration.ofSeconds(60)),
new JwtIssuerValidator(issuerUri));
jwtDecoder.setJwtValidator(withClockSkew);
return jwtDecoder;
}
结语:目前我们对客户端、授权服务器以及资源服务器都做了从配置到底层原理的讲解,相信学到此处的朋友已经能够很好地掌握。接下来还有几章的内容是关于高级功能(涉及OAuth2.1等)和真实案例应用的,这样能让我们学以致用。