Spring Webflux -Security: How to let Spring return 401 (UNAUTHORIZED) exception when jwt token expired or wrong - spring-boot

Below is code which authorise JWT token (Keyclock) but in case of exception , server never returns 401
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
public SecurityWebFilterChain securityWebFilterChain(final ServerHttpSecurity http) {
// the matcher for all paths that need to be secured (require a logged-in user)
http.authorizeExchange(exchanges -> exchanges.pathMatchers("/actuator/**").permitAll()
.pathMatchers("/abcde/auth").permitAll()
.pathMatchers("/abcde/auth/refresh").permitAll()
.anyExchange().authenticated())
.csrf().disable()
.oauth2ResourceServer(oauth2ResourceServer ->
oauth2ResourceServer
.jwt(withDefaults())
).exceptionHandling(exception-> exception.authenticationEntryPoint((swe, e) -> Mono.fromRunnable(() ->
{
swe.getResponse()
.setStatusCode(HttpStatus.UNAUTHORIZED);
}
)
)
);
return http.build();
}
Another question :
Will this piece of code only validate expiry of JWT token or also other validation? What exactly happens is what i am interested to know.
In nutshell is this code sufficient enough for keyclock JWT validation through issuer URL?

Related

How do i allow access certain request path to every user so that he/she can register user in spring security 6?

In spring security 5.7.5, I had the following security filter chain that allows unauthenticated users to easily register accounts.
Code in spring Security 5.7.5
#Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests(
authorize ->
authorize
.mvcMatchers("/user/**").permitAll()
.mvcMatchers("/**").authenticated()
).formLogin(Customizer.withDefaults());
return http.build();
But now, The previous way code is not working. How should I configure a filter chain that allows anyone to register accounts?
Present Code
#EnableWebSecurity
public class SecurityConfig {
#Bean
#Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults());
//when unauthenticated user tries to login the resource server
// redirect him/her to login page
http
.exceptionHandling(
exception ->
exception.authenticationEntryPoint(
new LoginUrlAuthenticationEntryPoint("/login")
)
);
return http.build();
}
#Bean
#Order(2)
public SecurityFilterChain appSecurityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf().disable()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.disable()
.authorizeHttpRequests(
authorize ->
authorize
.requestMatchers("/user/registerUser",
"/user/getAllUser").permitAll()
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.build();
}
}
I'm getting 401 unauthorized in postman when i request for /user/registerUser and /user/getAllUser url. What i'm trying is, registering user account by unauthenticated users. I belive my security filter chain is sending me to /login page to authenticate which i don't want for register url.

Webflux with different authentication schemes

I just got a project I need to maintain and I need to add support for an extra authentication scheme in a resource server. Something like besides regular Authentication: Bearer <jwt.token> to use a custom one: Authentication: Custom <other.jwt.token>. Both should work and handled differently.
Yes, I know spring can handle multiple providers, I know I can use a ReactiveAuthenticationManager but I am stuck in how to deal with the Custom prefix for the opaque token.
Just to make it clear, I need both to work - and, of course, to be handled differently:
GET /
Authorization: Bearer x.y.z
and
GET /
Authorization: Custom a.b.c
If possible, I'd like also to return the list of supported authentication protocols in WWW-Authorization header (i.e. Bearer, Custom).
Any hints? Googling only points me to regular stuff, with Bearer and whatever I try, spring automatically rejects me with 401 (of course, token is not handled).
Thanks.
What I did:
I implemented different ReactiveAuthenticationManager, one for each protocol I needed. Something like BearerReactiveAuthenticationManager and CustomReactiveAuthenticationManager and made them #Components;
I also implemented ServerSecurityContextRepository and injected both authentication managers from previous point. In the body I had something like:
#Override
public Mono<SecurityContext> load(ServerWebExchange serverWebExchange) {
ServerHttpRequest request = serverWebExchange.getRequest();
String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (authHeader.startsWith("Bearer ")) {
String authToken = authHeader.substring(7);
Authentication auth = new UsernamePasswordAuthenticationToken(authToken, authToken);
return this.bearerReactiveAuthenticationManager.authenticate(auth)
.map(SecurityContextImpl::new);
} else if (authHeader.startsWith("Custom ")) { {
String authToken = authHeader.substring(7);
Authentication auth = new UsernamePasswordAuthenticationToken(authToken, authToken);
return this.customReactiveAuthenticationManager.authenticate(auth)
.map(SecurityContextImpl::new);
} else {
log.debug("Could not identify the authentication header");
return Mono.empty();
}
}
And my SecurityConfig bean looked like this:
#Configuration
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
#Slf4j
public class SecurityConfig {
private final ServerSecurityContextRepository serverSecurityContextRepository;
#Autowired
public SecurityConfig(ServerSecurityContextRepository serverSecurityContextRepository) {
this.serverSecurityContextRepository = serverSecurityContextRepository;
}
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable()
.securityContextRepository(serverSecurityContextRepository)
.exceptionHandling()
.authenticationEntryPoint((swe, e) -> Mono.fromRunnable(() -> swe.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED)))
.accessDeniedHandler((swe, e) -> Mono.fromRunnable(() -> swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN)))
.and().authorizeExchange();
return http.build();
}
}

How to get ServerWebExchange object in SecurityWebFilterChain bean

Hello Spring WebFlux community,
I have implemented x509 based authentication in spring webflux security bean using below code:
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
ReactiveAuthenticationManager authMan = auth0 -> {
..//lambda logic of setAuthenticated true, HERE I NEED TO ACCESS CURRENT REQUEST HEADERS VALUE BEFORE SETTING IT TO TRUE
};
http.x509(x509 -> x509
.principalExtractor(myx509PrincipalExtractor())
.authenticationManager(authMan ))
.authorizeExchange(exchanges ->
exchanges.anyExchange().authenticated());
return http.build();
}
Now, please observe the comment part inside auth0 lambda, there I need to access header values of current request. How can I get that ?

Spring security can't extract roles from JWT

im using FusionAuth for my Oauth server and I have a problem. Spring security will decode the JWT successfully but there are no roles!
This is my complete Authentication Object in JSON:
https://jsoneditoronline.org/#left=cloud.911cb58e717544ab9168632ed221aae1
and as you can see I have the roles and the role object in my principal. but when I try to use it in #PreAuthroize or even log it. it's empty.
WebSecurityConfigurerAdapter:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests(authorizeRequests ->
authorizeRequests
.antMatchers(HttpMethod.GET, "v1/balance/**").permitAll()
.antMatchers(HttpMethod.POST, "v1/balance/**").permitAll()
.antMatchers(HttpMethod.GET, "/actuator/**").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2ResourceServer ->
oauth2ResourceServer
.jwt(jwt ->
jwt.decoder(JwtDecoders.fromIssuerLocation(issuerUri)).jwkSetUri(jwksUrl)
)
);
}
this is how i try to log the roles:
#GetMapping("/currency/{currency}")
// #PreAuthorize("hasAnyRole('admin')")
public CurrencyBalance getWalletByCurrency(#PathVariable String currency, Principal principal){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Set<String> roles = authentication.getAuthorities().stream()
.map(r -> r.getAuthority()).collect(Collectors.toSet());
System.out.println(roles);
System.out.println(new Gson().toJson(authentication));
System.out.println(authentication.getAuthorities());
System.out.println(authentication.getCredentials().toString());
System.out.println(authentication.getDetails());
System.out.println(authentication.getPrincipal());
System.out.println(authentication.getName());
return null;
// return balanceService.getWalletByCurrency(principal.getName(),currency);
}
First of all, you need to understand How JWT Authentication Works
(source: spring.io)
JwtAuthenticationConverter converts JWT to authorities of Authentication, By default it only decode the SCOPE of JWT to authorities.(look at JwtGrantedAuthoritiesConverter).
You have to create a subclass of JwtAuthenticationConverter and override the extractAuthorities method if you want to decode custom attribute of JWT.
Finally, declare you custom subclass as a Bean, spring security will automatically use it

How to by pass Authorizatoin header for non secured endpoints in Spring

I am using spring webflux and security. I have 3 services A, B, C and two endpoints in service C as below
health - secured
status - shouldn't be secured
below is my webflux configuration
#Configuration
#EnableWebFluxSecurity
public class SecureConfiguration {
#Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.builder()
.username("john")
.password("{noop}" + "password")
.roles("")
.build();
return new MapReactiveUserDetailsService(user);
}
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.csrf().disable()
.authorizeExchange()
.pathMatchers("/test/health")
.authenticated()
.pathMatchers("/**").permitAll()
.and().httpBasic();
return http.build();
}
}
Below scenarios are working
health endpoint with valid Authorization header
status endpoint without Authorization header
status endpoint with valid Authorization header
But when i access status endpoint with invalid Authorization header it's failing with Anuauthorized
How to avoid this? because the status call will be originated from Service A, there is different auth required for Service A to Service B which is being passed to Service C because of that it's failing but for Service B to Service C no need any auth for status endpoint.
I know we can do by create a fresh request without auth header but i want to know why spring security is not ignoring Authorization header for non secure endpoints.
When you use httpBasic() method, Spring Security configure an AuthenticationWebFilter with a default anyExchange matcher, which means that every request that comes to your spring application is going to go through this filter. Also, AuthenticationWebFilter has precedence over AuthorizationWebFilter (see Spring Security source code to know the order).
In some point the request reach this method (see Spring Security source code):
#Override
#Deprecated
public Mono<Authentication> apply(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
String authorization = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (!StringUtils.startsWithIgnoreCase(authorization, "basic ")) {
return Mono.empty();
}
String credentials = authorization.length() <= BASIC.length() ?
"" : authorization.substring(BASIC.length(), authorization.length());
byte[] decodedCredentials = base64Decode(credentials);
String decodedAuthz = new String(decodedCredentials);
String[] userParts = decodedAuthz.split(":", 2);
if (userParts.length != 2) {
return Mono.empty();
}
String username = userParts[0];
String password = userParts[1];
return Mono.just(new UsernamePasswordAuthenticationToken(username, password));
}
Depending on which invalid header do you send, the result of this method could be:
An empty mono, which means the chain continues (this explains your 2 case).
An exception, here AuthenticationWebFilter is going to execute the ServerAuthenticationFailureHandler, this explains your failure case (see AuthenticationWebFilter):
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return this.requiresAuthenticationMatcher.matches(exchange)
.filter( matchResult -> matchResult.isMatch())
.flatMap( matchResult -> this.authenticationConverter.convert(exchange))
.switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
.flatMap( token -> authenticate(exchange, chain, token))
.onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler
.onAuthenticationFailure(new WebFilterExchange(exchange, chain), e));
}
By default this handler is going to ask again for the http basic headers.
The easiest way to bypass this, is implementing your own AuthenticationWebFilter, this will allow you to change the behavior.

Resources