Mobile projects, authentication requirements are relatively simple, Spring Cloud Gateway is only responsible for JWT verification and role authentication, login and so on are all custom processing, microservices pass JWS to achieve the purpose of passing credentials, downstream services do not need to authenticate and do not rely on Spring Security, the code that requires the current user directly resolve JWS to get the current user
There is also the fact that WebSecurityConfigurerAdapter
has been marked as deprecated in Spring Security 5.4+ and bean injection has been recommended for non-reactive projects, so the following code can also be considered a migration guide for 5.4+
Core Code:
JwsService
provides JWS signature and checksum, returns the corresponding JwsPayload
object, JwsPayload
is a POJO, provides the current principal, contains a List<Authority> authoritys
property, Authority
is a POJO, because is placed in the lib
module, does not import Spring Security dependencies, and inherits directly from Object
, which can also be seen in the following code manually converted to GrantedAuthority
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
package com.seliote.bubble.gwsvr.security;
import com.seliote.bubble.svrlib.config.JwsProps;
import com.seliote.bubble.svrlib.service.JwsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.List;
/**
* JWS Filter
* If authorization header exists, will set to security context
*
* @author seliote
* @since 2022-07-06
*/
@Slf4j
@Component
public class JwsFilter implements WebFilter {
private final JwsService jwsService;
private final String headerName;
@Autowired
public JwsFilter(JwsService jwsService, JwsProps jwsProps) {
this.jwsService = jwsService;
this.headerName = jwsProps.getHeader();
}
@NonNull
@Override
public Mono<Void> filter(@NonNull ServerWebExchange exchange, @NonNull WebFilterChain chain) {
var header = exchange.getRequest().getHeaders().getFirst(headerName);
var payloadOpt = jwsService.verify(header);
if (payloadOpt.isPresent() && payloadOpt.get().available()) {
var payload = payloadOpt.get();
List<? extends GrantedAuthority> authorities = new ArrayList<>();
if (payload.getAuthorities() != null && payload.getAuthorities().size() != 0) {
authorities = payload.getAuthorities().stream().map(r -> (GrantedAuthority) r::getAuthority).toList();
}
var authentication = new UsernamePasswordAuthenticationToken(payload, null, authorities);
log.trace("Set security context {}", payload);
return chain.filter(exchange)
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication));
}
return chain.filter(exchange);
}
}
|
Because it is fully customizable to implement login and other operations, an empty ReactiveAuthenticationManager
is provided, which corresponds to the default implementation of authenticationManager()
in MVC.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
package com.seliote.bubble.gwsvr.security;
import com.seliote.bubble.svrlib.domain.Authority;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import reactor.core.publisher.Mono;
/**
* Security context config
*
* @author seliote
* @since 2022-07-06
*/
@Slf4j
@EnableWebFluxSecurity
public class SecurityConfig {
private final JwsFilter jwsFilter;
@Autowired
public SecurityConfig(JwsFilter jwsFilter) {
this.jwsFilter = jwsFilter;
}
@Bean
public ReactiveAuthenticationManager authenticationManager() {
return authentication -> Mono.empty();
}
@Bean
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
final var permitAll = new String[]{"/user-svr/country/list"};
return http.csrf().disable()
.httpBasic().disable()
.formLogin().disable()
.authorizeExchange().pathMatchers(permitAll).permitAll()
.pathMatchers("/**").hasAuthority(Authority.USER)
.anyExchange().authenticated()
.and().addFilterAt(jwsFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.build();
}
}
|
Reference: https://seliote.fun/2023/02/09/spring-webflux-%e6%94%af%e6%8c%81-spring-security-%e5%ae%9e%e7%8e%b0-jwt-%e9%89%b4%e6%9d%83/