第十八章 集成OAuth2 Resource Server
前言
如果經(jīng)常有看Spring Security官方文檔的同學(xué)會(huì)發(fā)現(xiàn)船惨,我們寫的JWT這部分資源服務(wù)的鑒權(quán)操作其實(shí)是有官方實(shí)現(xiàn)的柜裸,那就是OAuth 2.0 Resource Server,本章節(jié)我們就來集成OAuth 2.0 Resource Server粱锐,來實(shí)現(xiàn)對(duì)資源服務(wù)的鑒權(quán)操作。
一扛邑、鑒定流程
官方文檔
我們需要了解Token的驗(yàn)證流程怜浅,具體如下:
- 當(dāng)用戶提交一個(gè)Token時(shí),BearerTokenAuthenticationFilter通過從HttpServletRequest中提取出來的Token來創(chuàng)建一個(gè)BearerTokenAuthenticationToken蔬崩。
- BearerTokenAuthenticationToken被傳遞到AuthenticationManager調(diào)用Authenticated方法進(jìn)行身份驗(yàn)證恶座。
- 如果失敗則調(diào)用失敗處理器
- 如果成功則可以繼續(xù)訪問資源接口
二、JWT身份驗(yàn)證的工作原理
1.將Token提交給ProviderManager沥阳。
2.ProviderManager會(huì)找到JwtAuthenticationProvider跨琳,并調(diào)用authenticate方法。
3.authenticate方法里面通過JwtDecoder來驗(yàn)證token桐罕,并轉(zhuǎn)換為Jwt對(duì)象脉让。
4.authenticate方法里面通過JwtAuthenticationConverter將Jwt對(duì)象轉(zhuǎn)換為已驗(yàn)證的Token對(duì)象。
5.驗(yàn)證成功就返回JwtAuthenticationToken對(duì)象功炮。
三溅潜、與JWT集成
原理說完了,現(xiàn)在該實(shí)操了薪伏,具體可以先抄官方的例子滚澜。
1.首先還是要引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
2.添加公鑰和私鑰
配置文件里面新增如下配置:
jwt:
private.key: classpath:app.key
public.key: classpath:app.pub
3.官方參考代碼
/**
* 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);
}
}
4.自己修改后的代碼
@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);
}
}
@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 &&
// 符合任意規(guī)則
this.antPathRequestMatcherList.stream().anyMatch(m -> m.matches(req));
/**
* 資源服務(wù)權(quán)限配置
*
* @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校驗(yàn)不需要session
.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling(exception -> exception
//未授權(quán)異常
.accessDeniedHandler(new JwtAccessDeniedHandler())
.authenticationEntryPoint(new JwtAuthenticationEntryPoint())
).build();
}
/**
* 賬號(hào)密碼的加密方式
*/
@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);
}
}
最核心的也就是這個(gè)WebSecurityConfigurer類里面的securityFilterChain配置嫁怀,這里面配置了白名單设捐、表單登錄、登錄成功和失敗后的處理方法塘淑、退出后的處理方法萝招,以及最重要的資源配置(oauth2ResourceServer),還有其他修改的地方請(qǐng)看源碼朴爬,我就不一一列舉了即寒。
當(dāng)前版本tag:2.0.3
代碼倉庫
四、 體驗(yàn)地址
后臺(tái)數(shù)據(jù)庫只給了部分權(quán)限,報(bào)錯(cuò)屬于正常母赵!
想學(xué)的老鐵給點(diǎn)點(diǎn)關(guān)注吧R菥簟!凹嘲!
歡迎留言交流J蟆!周蹭!
我是阿咕嚕趋艘,一個(gè)從互聯(lián)網(wǎng)慢慢上岸的程序員,如果喜歡我的文章凶朗,記得幫忙點(diǎn)個(gè)贊喲瓷胧,謝謝!