How to by pass Authorizatoin header for non secured endpoints in Spring - 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.

Related

how microservice use jwt to communicate in springboot

I am using microservice in spring boot and i want to use jwt and oauth2 to access the server.But i just wonder that how microservice other than api gateway get the data in the jwt (id or name) .It seems that it is so tedious to set a decoder in every microservice.
I am thinking that is it possible to decode and add the data at the httprequest and route it the other microservice in apigateway.But it seems that i cant find a setheader method in webflux filter security.
Jwt filter:
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String authorizationheader= exchange.getRequest().getHeaders().get("Authorization").toString();
String token;
String Username = null;
String iss=null;
//check have tokem
if(authorizationheader !=null&& authorizationheader.startsWith("Bearer ")){
token=authorizationheader.substring(7);
Username=jwtDecoder.decode(token).getSubject();
iss= String.valueOf(jwtDecoder.decode(token).getIssuer());
} //verify by check username and iss
if(Username!=null && iss!=null&& SecurityContextHolder.getContext().getAuthentication()==null){
if(iss.equals("http://localhost:8080")){
UserDetails userDetails=new User(Username,null,null);
UsernamePasswordAuthenticationToken AuthenticationToken=new UsernamePasswordAuthenticationToken(
userDetails,null,userDetails.getAuthorities());
//set username and id to the request
SecurityContextHolder.getContext().setAuthentication(AuthenticationToken);
}
}
return chain.filter(exchange);
}
Securityfilter bean:
#Bean
public SecurityWebFilterChain filterChain(ServerHttpSecurity httpSecurity) throws Exception {
return httpSecurity
/*.csrf(csrf -> csrf.ignoringRequestMatchers("/Job/getRegionjobs/**",
"/Job/getalljobs","/login/oauth2/code/google"))*/
.csrf(csrf -> csrf.disable())
.authorizeExchange(auth->auth.anyExchange().authenticated())
.addFilterBefore(jwtFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.oauth2ResourceServer(ServerHttpSecurity.OAuth2ResourceServerSpec::jwt)
//.sessionManagement(session-> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.httpBasic(withDefaults())
.build();
}
Please help
It seems that it is so tedious to set a decoder in every microservice.
No, it is not. Configuring a resource-server (OAuth2 REST API) can be as simple as:
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<!-- replace "webmvc" with "weblux" if your micro-service is reactive -->
<artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
<version>6.0.12</version>
</dependency>
#Configuration
#EnableMethodSecurity
public static class WebSecurityConfig { }
com.c4-soft.springaddons.security.issuers[0].location=https://localhost:8443/realms/realm1
com.c4-soft.springaddons.security.issuers[0].authorities.claims=realm_access.roles,ressource_access.some-client.roles,ressource_access.other-client.roles
com.c4-soft.springaddons.security.cors[0].path=/some-api
If you don't want to use my starters, you can still create your own copying from it (it is open source and each is composed of 3 files only).
If you don't implement access-control in each micro-service, then you can't bypass the gateway and it's going to be a hell to implement rules involving the resources itself (like only user who created that kind of resource can modify it).

Spring oauth2login oidc grant access based on user info

I'm trying to set up Authentication based on this tutorial: https://www.baeldung.com/spring-security-openid-connect part 7 specifically.
I have filled properties and configured filter chain like this:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests -> authorizeRequests
.anyRequest().authenticated())
.oauth2Login(oauthLogin -> oauthLogin.permitAll());
return http.build();
}
which works, but now all users from oidc can connect log in. I want to restrict access based on userinfo. E.g. add some logic like:
if(principal.getName() == "admin") {
//allow authentication
}
are there any way to do it?
I tried to create customer provider like suggested here: Add Custom AuthenticationProvider to Spring Boot + oauth +oidc
but it fails with exception and says that principal is null.
You can retrieve user info when authentication is successful and do further checks based user info.
Here is sample code that clears security context and redirects the request:
#Component
public class OAuth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
if(authentication instanceof OAuth2AuthenticationToken) {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
// OidcUser or OAuth2User
// OidcUser user = (OidcUser) token.getPrincipal();
OAuth2User user = token.getPrincipal();
if(!user.getName().equals("admin")) {
SecurityContextHolder.getContext().setAuthentication(null);
SecurityContextHolder.clearContext();
redirectStrategy.sendRedirect(request, response, "login or error page url");
}
}
}
}
Are you sure that what you want to secure does not include #RestController or #Controller with #ResponseBody? If so, the client configuration you are referring to is not adapted: you need to setup resource-server configuration for this endpoints.
I wrote a tutorial to write apps with two filter-chains: one for resource-server and an other one for client endpoints.
The complete set of tutorials the one linked above belongs to explains how to achieve advanced access-control on resource-server. Thanks to the userAuthoritiesMapper configured in resource-server_with_ui, you can write the same security expressions based on roles on client controller methods as I do on resource-server ones.

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 add Jwt Token based security In Microservices

In my microservices, I will try to implement Jwt spring-security, But I don't know how to apply it.
In my microservices, I have used the 2020.0.3 spring cloud version.
In user services, I have connected the department service using the Rest template.
I need help with how to add Jwt security in these microservices.
This is 4 microservices
Server = Eureka Server
service-API-gateway = Spring cloud Apigateway
service-department & services-user = These two microservices connect with Rest template
Microservices Project Structure
: https://i.stack.imgur.com/ajTiX.png
So at a higher level, Spring Security is applied on controller level when using jwt as authentication. First you need to add a Security config that will extend WebSecurityConfigurerAdapter (this is common for http based security) and in that class you need to define configure method like:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.csrf().disable() // IF your clients connect without a cookie based, this will be fine
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/register", "/login","/your_open_endpoints_etc").permitAll()
.and()
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
Then in the filter class which extends OncePerRequestFilter, you can define the do filter like this, you have to set the UsernamePasswordAuthenticationFilter instance inside the Spring authentication context:
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
logger.info("do filter...");
String token = jwtProvider.getTokenFromRequest((HttpServletRequest) httpServletRequest);
try{
if (token != null && jwtProvider.validateToken(token)) {
String username = jwtProvider.getUsernameFromToken(token);
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, null, jwtProvider.getAuthorities(token));
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
catch (RuntimeException e)
{
// Some general Exception handling that will wrap and send as HTTP Response
}
}
Check on the extending filters further, they might change as per your requirement
finally in rest endpoints you can safe guard like:
#PreAuthorize("hasRole('ROLE_YOURROLE')")
#GetMapping(path = "/your_secured_endpoint", consumes = "application/json",
produces = "application/json")
public ResponseEntity<List<SomePOJOObject>> getAllAppointmentsForPatient()
{
return new ResponseEntity<>(thatSomePOJOObjectListYouWant, HttpStatus.OK);
}

Spring boot authorization returns 403 for any authorization request using #RolesAllowed, #Secured or #PreAuthorize

I've been working from this article (and a few other similar ones): https://medium.com/omarelgabrys-blog/microservices-with-spring-boot-authentication-with-jwt-part-3-fafc9d7187e8
The client is an Angular 8 app which acquires a Jwt from an independent microservice. Trying to add filter(s) to a different microservice to require specific authorization via jwt roles.
Consistently receiving 403 errors.
Security Config:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true,
securedEnabled = true,
jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurityConfig() {}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable()
// make sure we use stateless session; session won't be used to store user's state.
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// Add a filter to validate the tokens with every request
.addFilterAfter(new JwtAuthorizationFilter2(), UsernamePasswordAuthenticationFilter.class)
// authorization requests config
.authorizeRequests()
// Any other request must be authenticated
.anyRequest().authenticated();
}
}
Filter:
public class JwtAuthorizationFilter2 extends OncePerRequestFilter {
private final String HEADER = "Authorization";
private final String PREFIX = "Bearer ";
private final String SECRET = "foo";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String token = request.getHeader(SecurityConstants.HEADER_STRING);
if (token != null) {
// parse the token.
DecodedJWT decoded = JWT.require(Algorithm.HMAC512(SecurityConstants.SECRET.getBytes()))
.build()
.verify(token.replace(SecurityConstants.TOKEN_PREFIX, ""));
String user = decoded.getSubject();
List<SimpleGrantedAuthority> sgas = Arrays.stream(
decoded.getClaim("roles").asArray(String.class))
.map( s -> new SimpleGrantedAuthority(s))
.collect( Collectors.toList());
if (sgas != null) {
sgas.add(new SimpleGrantedAuthority("FOO_Admin"));
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
user,
null,
sgas);
SecurityContextHolder.getContext().setAuthentication(auth);
}
else {
SecurityContextHolder.clearContext();
}
chain.doFilter(request, response);
}
}
}
This code works fine without any authorization requirements defined, but if an authorization is defined in WebSecurityConfig, or by decorating a controller method, http 403 is received for all requests in scope.
Examples:
.authorizeRequests().antMatchers("/**").hasRole("FOO_Admin")
// or any of these
#PreAuthorize("hasRole('FOO_Admin')")
#RolesAllowed({"FOO_Admin"})
#Secured({"FOO_Admin"})
Device get(#PathVariable String id) {
// some code
}
When code is halted at SecurityContextHolder.getContext().setAuthentication(auth),
auth.authenticated = true
and
auth.authorities includes a SimpleGrantedAuthority for "FOO_Admin"
So I'm wondering whether:
The FilterChain needs an Authentication Filter (or does authentication occur in JwtAuthorizationFilter2?)?
There is not a spelling or formatting or capitalization difference to role name.
I'm stupefied. Any help would be greatly appreciated.
#PreAuthorize("hasRole('FOO_Admin')) expects the user has an authority ROLE_FOO_Admin, which will be prefixed by ROLE_. However, the user only has the authority FOO_Admin , hence it fails to access the method.
You have several options:
(1) Change the prefix by declaring a GrantedAuthorityDefaults bean:
#Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("FOO");
}
And use #PreAuthorize(hasRole('Admin')) to secure the method.
(2) Or more simpler is to use #PreAuthorize("hasAuthority('FOO_Admin')") , which will directly check if the user has the authority FOO_Admin , without adding any prefix to it.
P.S JwtAuthorizationFilter2 only verifies if an user is valid and get the related user information which prepare for the authorization user later. It is an authentication and I would rename it to JwtAuthenticationFilter2 to describe more exactly what it does actually.

Resources