第十八章 集成 OAuth2 Resource Server
前言
如果经常有看 Spring Security 官方文档的同学会发现,我们写的 JWT 这部分资源服务的鉴权操作其实是有官方实现的,那就是 OAuth 2.0 Resource Server,本章节我们就来集成 OAuth 2.0 Resource Server,来实现对资源服务的鉴权操作。
一、鉴定流程
官方文档 我们需要了解 Token 的验证流程,具体如下:
- 当用户提交一个 Token 时,BearerTokenAuthenticationFilter 通过从 HttpServletRequest 中提取出来的 Token 来创建一个 BearerTokenAuthenticationToken。
- BearerTokenAuthenticationToken 被传递到 AuthenticationManager 调用 Authenticated 方法进行身份验证。
- 如果失败则调用失败处理器
- 如果成功则可以继续访问资源接口
二、JWT 身份验证的工作原理
- 将 Token 提交给 ProviderManager。
- ProviderManager 会找到 JwtAuthenticationProvider,并调用 authenticate 方法。
- authenticate 方法里面通过 JwtDecoder 来验证 token,并转换为 Jwt 对象。
- authenticate 方法里面通过 JwtAuthenticationConverter 将 Jwt 对象转换为已验证的 Token 对象。 5.验证成功就返回 JwtAuthenticationToken 对象。
三、与 JWT 集成
原理说完了,现在该实操了,具体可以先抄官方的例子。
- 首先还是要引入依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
- 添加公钥和私钥
配置文件里面新增如下配置:
yml
jwt:
private.key: classpath:app.key
public.key: classpath:app.pub
- 官方参考代码
java
/**
* Security configuration for the main application.
*
* @author Josh Cummings
*/
@Configuration
public class RestConfig {
@Value("${jwt.public.key}")
RSAPublicKey key;
@Value("${jwt.private.key}")
RSAPrivateKey priv;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.csrf((csrf) -> csrf.ignoringRequestMatchers("/token"))
.httpBasic(Customizer.withDefaults())
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
.accessDeniedHandler(new BearerTokenAccessDeniedHandler())
);
// @formatter:on
return http.build();
}
@Bean
UserDetailsService users() {
// @formatter:off
return new InMemoryUserDetailsManager(
User.withUsername("user")
.password("{noop}password")
.authorities("app")
.build()
);
// @formatter:on
}
@Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(this.key).build();
}
@Bean
JwtEncoder jwtEncoder() {
JWK jwk = new RSAKey.Builder(this.key).privateKey(this.priv).build();
JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
return new NimbusJwtEncoder(jwks);
}
}
- 自己修改后的代码
java
@Configuration
public class SecurityEncodeConfig {
@Value("${jwt.public.key}")
RSAPublicKey key;
@Value("${jwt.private.key}")
RSAPrivateKey priv;
/**
* jwt的解密方式
*/
@Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(this.key).build();
}
/**
* jwt的加密方式
*/
@Bean
JwtEncoder jwtEncoder() {
JWK jwk = new RSAKey.Builder(this.key).privateKey(this.priv).build();
JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
return new NimbusJwtEncoder(jwks);
}
}
java
@EnableWebSecurity
@EnableConfigurationProperties({PermitUrlProperties.class})
public class WebSecurityConfigurer {
private final JwtEncoder jwtEncoder;
private PermitUrlProperties permitUrlProperties;
private List<AntPathRequestMatcher> antPathRequestMatcherList;
public WebSecurityConfigurer(JwtEncoder jwtEncoder, PermitUrlProperties permitUrlProperties) {
this.jwtEncoder = jwtEncoder;
this.permitUrlProperties = permitUrlProperties;
}
@PostConstruct
public void antPathMatcherList() {
this.antPathRequestMatcherList = Optional.ofNullable(permitUrlProperties.getUrls()).orElse(new ArrayList<>()).stream().map(AntPathRequestMatcher::new).collect(Collectors.toList());
}
private final RequestMatcher PERMIT_ALL_REQUEST = (req) ->
// 并且不包含token信息
req.getHeader(HttpHeaders.AUTHORIZATION) == null &&
// 符合任意规则
this.antPathRequestMatcherList.stream().anyMatch(m -> m.matches(req));
/**
* 资源服务权限配置
*
* @param http
* @return
* @throws Exception
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return
http
.authorizeRequests()
// 白名单不拦截
.requestMatchers(PERMIT_ALL_REQUEST)
.permitAll()
.anyRequest().authenticated()
.and()
.cors(cors -> cors.disable())
.csrf(csrf -> csrf.disable())
.formLogin()
.successHandler(new JwtAuthenticationSuccessHandler(jwtEncoder))
.failureHandler(new JwtAuthenticationFailureHandler())
.and()
.logout(logout -> logout.logoutSuccessHandler(new JwtLogoutSuccessHandler()))
.oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer
.jwt()
.jwtAuthenticationConverter(c -> {
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(SecurityConstants.AUTHORITIES);
jwtGrantedAuthoritiesConverter.setAuthorityPrefix("");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return jwtAuthenticationConverter.convert(c);
})
)
//禁用session,JWT校验不需要session
.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling(exception -> exception
//未授权异常
.accessDeniedHandler(new JwtAccessDeniedHandler())
.authenticationEntryPoint(new JwtAuthenticationEntryPoint())
).build();
}
/**
* 账号密码的加密方式
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder() {
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
/*
* 注意有提前加密
*/
return super.matches(decryptAES(rawPassword).trim(), encodedPassword);
}
};
}
private static String decryptAES(CharSequence password) {
AES aes = new AES(Mode.CBC, Padding.NoPadding, new SecretKeySpec(SecurityConstants.FRONTEND_KEY.getBytes(), "AES"), new IvParameterSpec(SecurityConstants.FRONTEND_KEY.getBytes()));
byte[] result = aes.decrypt(Base64.decode(password.toString().getBytes(StandardCharsets.UTF_8)));
return new String(result, StandardCharsets.UTF_8);
}
}
最核心的也就是这个 WebSecurityConfigurer 类里面的 securityFilterChain 配置,这里面配置了白名单、表单登录、登录成功和失败后的处理方法、退出后的处理方法,以及最重要的资源配置(oauth2ResourceServer),还有其他修改的地方请看源码,我就不一一列举了。
当前版本 tag:2.0.3 代码仓库
四、 体验地址
后台数据库只给了部分权限,报错属于正常!
想学的老铁给点点关注吧!!!
欢迎留言交流!!!
我是阿咕噜,一个从互联网慢慢上岸的程序员,如果喜欢我的文章,记得帮忙点个赞哟,谢谢!