Sleuth in Webflux Spring Security - filter

I have a webflux app and need to have sleuth context in my authentication logs (move from DefaultWebFilterChain to SecurityWebFilterChain).
I have tried to add manually in my security chain:
public GwApiSecurityConfig(Tracer tracer, HttpServerHandler httpServerHandler, CurrentTraceContext currentTraceContext){
this.tracer = tracer;
this.httpServerHandler = httpServerHandler;
this.currentTraceContext = currentTraceContext;
}
#Bean
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
return http
.csrf().disable()
.authorizeExchange()
.pathMatchers(HttpMethod.GET,"/swagger", "/v3/**", "/webjars/**", "/actuator/**").permitAll()
.anyExchange().authenticated()
.and()
.httpBasic()
.authenticationEntryPoint((exchange, exception) -> Mono.error(new GwException(HttpStatus.UNAUTHORIZED, GwError.AUTHENTICATION)))
.and()
.formLogin().disable()
.redirectToHttps()
.and()
.addFilterBefore(new TraceWebFilter(tracer, httpServerHandler, currentTraceContext), SecurityWebFiltersOrder.HTTP_BASIC)
.build();
}
But I got an error:
09:11:06.117 ERROR[reactor-http-nio-2] [,] GwApiErrorHandler - null
java.lang.NullPointerException: null
at org.springframework.cloud.sleuth.instrument.web.TraceWebFilter.spanFromContextRetriever(TraceWebFilter.java:139)
Also I checked this property:
spring.sleuth.web.filter-order=1
but I think that only affect the DefaultWebFilterChain order, no SecurityWebFilterChain.
And how to remove the filter from DefaultWebFilterChain to avoid filtering twice?
Any ideas?
Thanks!

Related

Migrating from WebSecurityConfigurerAdapter to SecurityFilterChain

Here is my working security conf before migration :
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/auth/**")
.antMatchers("/swagger-ui/**")
.antMatchers("/swagger-ui.html")
.antMatchers("/swagger-resources/**")
.antMatchers("/v2/api-docs/**")
.antMatchers("/v3/api-docs/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedPortalRoleConverter);
http
.csrf().disable()
.cors()
.and()
.exceptionHandling()
.authenticationEntryPoint(new AuthenticationFallbackEntryPoint())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests(authorize -> authorize.anyRequest().authenticated())
.oauth2ResourceServer()
.jwt().jwtAuthenticationConverter(jwtAuthenticationConverter);
}
And here is my Security chain config after migration :
#Bean
#Order(1)
public SecurityFilterChain ignorePathsSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.antMatchers(
"/auth/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/v3/api-docs/**")
.permitAll());
return http.build();
}
#Bean
#Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, GrantedPortalRoleConverter grantedPortalRoleConverter) throws Exception {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedPortalRoleConverter);
http
.csrf().disable()
.cors(Customizer.withDefaults())
.exceptionHandling(configurer -> configurer.authenticationEntryPoint(new AuthenticationFallbackEntryPoint()))
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
.oauth2ResourceServer(configurer -> configurer.jwt().jwtAuthenticationConverter(jwtAuthenticationConverter));
return http.build();
}
With the original conf, when I call a random non existing path :
#Test
void should_not_authenticate_or_return_not_found() throws Exception {
logger.info("should_not_authenticate_or_return_not_found");
mvc.perform(get("/toto/tata"))
.andExpect(status().isUnauthorized());
}
I get :
15:44:00.230 [main] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Failed to authorize filter invocation [GET /toto/tata] with attributes [authenticated]
With the new conf, I'm just getting HTTP 404, what am I missing here please ? I can't see any difference and debug logs don't show much.
Here is the first line of log missing using the non working conf :
16:24:58.651 [main] DEBUG o.s.s.w.a.e.ExpressionBasedFilterInvocationSecurityMetadataSource - Adding web access control expression [authenticated] for any request
But in both logs, I can see (2 lines of this for the new conf since there are 2 security chains) :
o.s.s.web.DefaultSecurityFilterChain - Will secure any request with (...)
Explanation
When you have multiple SecurityFilterChains, you have to specify a request matcher, otherwise all requests will be processed by the first SecurityFilterChain, annotated with #Order(1), and never reach the second SecurityFilterChain, annotated with #Order(2).
In the code you shared above, this means configuring .requestMatchers() in ignorePathsSecurityFilterChain:
#Bean
#Order(1)
public SecurityFilterChain ignorePathsSecurityFilterChain(HttpSecurity http) throws Exception {
http
.requestMatchers(requests -> requests // add this block
.antMatchers(
"/auth/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/v3/api-docs/**")
)
.authorizeHttpRequests(authorize -> authorize
.antMatchers(
"/auth/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/v3/api-docs/**")
.permitAll());
return http.build();
}
This means that only the requests matching /auth/**, /swagger-ui/** etc will be processed by ignorePathsSecurityFilterChain, while the rest of the requests will move on to defaultSecurityFilterChain.
To understand the difference between requestMatchers and authorizeHttpRequests you can check out this StackOverflow question.
Solution
An even better option is to combine the SecurityFilterChains into a single one. I don't see any reason why you would separate them in this case.
The resulting configuration would be:
#Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, GrantedPortalRoleConverter grantedPortalRoleConverter) throws Exception {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedPortalRoleConverter);
http
.authorizeHttpRequests(authorize -> authorize
.antMatchers(
"/auth/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/v3/api-docs/**")
.permitAll()
.anyRequest().authenticated()
)
.csrf().disable()
.cors(Customizer.withDefaults())
.exceptionHandling(configurer -> configurer.authenticationEntryPoint(new AuthenticationFallbackEntryPoint()))
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.oauth2ResourceServer(configurer -> configurer.jwt().jwtAuthenticationConverter(jwtAuthenticationConverter));
return http.build();
}
Alternative
Alternatively you can use a WebSecurityCustomizer to ignore certain endpoints:
#Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers(
"/auth/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/v3/api-docs/**");
}
Then you would use defaultSecurityFilterChain as your only SecurityFilterChain.

Authentication with spring and web flux

I have a question concerning spring and web flux.
I have a spring project with spring security and MVC as dependencies.
This application accepts requests and check authentication using the session cookie.
For all the requests starting with "/api/" a failed authentication results in a 401 response, so that can be intercepted by the frontend as such.
For all the requests different from "/api/**" a failed authentication results in the server returning a login page so that the user can login.
This is the SecuritConfig class:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.exceptionHandling()
.defaultAuthenticationEntryPointFor(new
HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
new AntPathRequestMatcher("/api/**"))
.and()
.cors();
}
}
Now, I am trying to achieve the same thing using web flux. With web flux the SecurityConfig is different, I can setup almost all the configs that I have in the old class but there is no equivalent for:
defaultAuthenticationEntryPointFor(new
HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
new AntPathRequestMatcher("/api/**"))
My new security config look like:
#Configuration
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/login/**")
.permitAll()
.anyExchange()
.authenticated()
.and()
.formLogin()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(new
HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED))
.and()
.build();
}
}
But in this case I only get 401 for all the requests that fail authentication.
Does anybody know how to achieve the same behavior with web flux?
Thank you

Spring Security in Webflux

I have been trying to enable spring security in web flux, with my own custom authentication method. So far so good, but I am not able to allow certain URL patterns using permitall.
I have tried to create different beans of SecurityWebFilterChain, also tried with different config altogether, but nothing seems to work for me.
Here is my SecurityWebFilterChain
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.csrf()
.disable()
.httpBasic()
.disable()
.formLogin()
.disable()
.logout()
.disable()
.authenticationManager(this.authenticationManager())
.securityContextRepository(this.securityContextRepository())
.authorizeExchange()
.pathMatchers("**/signal/health").permitAll()
.pathMatchers("**/order").permitAll()
.and()
.authorizeExchange()
.anyExchange()
.authenticated()
.and()
.build();
}
I have an internal health check system, which runs as soon as my application is up, so I want this to be allowed.
Moreover, I also want to allow another couple or URI, but the above config doesn't work for me.
Everything goes for authentication.
What am I doing wrong here?
I see a wrong and and authorizeExchange placed in between. Try using this:
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.csrf().disable()
.httpBasic().disable()
.formLogin().disable()
.logout().disable()
.authenticationManager(this.authenticationManager())
.securityContextRepository(this.securityContextRepository())
.authorizeExchange()
.pathMatchers("**/signal/health","**/order").permitAll()
.anyExchange()
.authenticated()
.and().build();
}

Spring Security not intercepting correctly? [duplicate]

This question already has answers here:
Spring Security : Multiple HTTP Config not working
(2 answers)
Closed 3 years ago.
I have Spring Boot configuration which looks something like this:
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore( new Filter(), UsernamePasswordAuthenticationFilter.class)
.csrf().disable() // Disabled, cause enabling it will cause sessions
.headers()
.frameOptions()
.sameOrigin()
.addHeaderWriter(new XXssProtectionHeaderWriter())
.and()
.authorizeRequests()
.antMatchers("/app/**", "/rest/**").hasAuthority(DefaultPrivileges.ACCESS_TASK)
.anyRequest().permitAll();
My understanding was only the requests which start with /app or /rest will be intercepted by my custom filter but it turns out the requests to the root (http://localhost:8080/context/) are also intercepted.
I have multiple configurations for Spring Security the other configuration looks like this:
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable();
if (taskAppProperties.isRestEnabled()) {
if (restAppProperties.isVerifyRestApiPrivilege()) {
http
.antMatcher("/*-api/**")
.authorizeRequests()
.antMatchers("/*-api/**").hasAuthority(DefaultPrivileges.ACCESS_REST_API)
.and()
.httpBasic();
} else {
http
.antMatcher("/*-api/**")
.authorizeRequests()
.antMatchers("/*-api/**").authenticated()
.and()
.httpBasic();
}
} else {
http
.antMatcher("/*-api/**")
.authorizeRequests()
.antMatchers("/*-api/**").denyAll();
}
Can anyone help?
I realize this is a bit confusing, but there are actually two antMatchers methods, one that branches from authorizedRequests and another that branches from requestMatchers.
Let's look at the following declaration:
http
.requestMatchers()
.antMatchers("/app/**", "/api/**")
.and()
.authorizeRequests()
.antMatchers("...").authenticated()
...
The requestMatchers() DSL is where you describe the endpoints that matter to that instance of the Spring Security filter chain. So, this filter chain will only engage for URIs that start with /app or /api.
Let's take a look at another one:
http
.authorizeRequests()
.antMatchers("/app/**", "/api/**")
.authenticated();
While this may appear to be doing the same thing, it isn't. That's because you are calling the antMatchers method that belongs to authorizeRequests().
This is why indentation is important with the Spring Security DSL. Because there's a hierarchy in the DSL, then you want to indent, just like you want to indent inside your if statements.
In Spring Security 5.2, this is simplified a bit with the new lambda DSL:
http
.requestMatchers(r -> r.antMatchers("/app/**", "/api/**"))
.authorizeRequests(a -> a.antMatchers("...").authenticated());
HttpSecurity.authorizeRequests - returns ExpressionInterceptUrlRegistry where we are setting Matchers and Roles condition, which will be added using method ExpressionInterceptUrlRegistry.getRegistry and if you check other usage of this method only at permitAll stub where actual authentication happens.
The filter we add using HttpSecurity.addFilterBefore will not check any Request matching. If you need, you can do one more check in your custom filter to avoid other URIs
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterAfter( new Filter() {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = ((HttpServletRequest) request);
if(httpServletRequest.getRequestURI().startsWith("/app/") || httpServletRequest.getRequestURI().startsWith("/rest/")) {
// Do you secured filter computations
}
chain.doFilter(request, response);
}
#Override
public void destroy() {
}}, UsernamePasswordAuthenticationFilter.class)
.csrf()
.disable() // Disabled, cause enabling it will cause sessions
.headers()
.frameOptions()
.sameOrigin()
.addHeaderWriter(new XXssProtectionHeaderWriter())
.and()
.authorizeRequests()
.antMatchers("/app/**", "/rest/**")
.hasAuthority(DefaultPrivileges.ACCESS_TASK)
.anyRequest()
.permitAll();

Getting 401 error status from swagger response when providing authentication using oauth2

I am trying to setup swagger with spring security and oauth2.0 in my application.I have created all the files required for this , i am able to get the access_token and also able to authenticate using that token when trying with postman.
but now i need to do the same using swagger and i am getting status 401
and error: "Auth ErrorTypeError: Failed to fetch" on swagger.
also when i am hitting the authenticate button i am getting method type options.
i have used cors filter in my application and have removed the security for HttpMethod.options.
Why i am getting this error? what i have missed in this?
authorazation server code:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().and().cors().and()
.authorizeRequests()
.antMatchers("/login","/logout.do").permitAll()
.antMatchers(HttpMethod.OPTIONS,
"/oauth/token").permitAll()
// .antMatchers("/**").authenticated()
.anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login.do")
.usernameParameter("username")
.passwordParameter("password")
.loginPage("/login")
.and()
.logout()
.logoutRequestMatcher(new
AntPathRequestMatcher("/logout.do"))
.and()
.userDetailsService(userDetailsServiceBean());
}
Resource server:
#Override
public void configure(HttpSecurity http) throws Exception{
http
.csrf().disable().authorizeRequests().antMatchers("/v2/api-docs", "/configuration/ui", "/swagger-resources", "/configuration/security",
"/swagger-ui.html", "/webjars/**", "/swagger-resources/configuration/ui", "/swagger-ui.html",
"/swagger-resources/configuration/security")
.permitAll();
http.csrf().and()
//.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class)
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll()
.antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')")
.antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')");
}
swagger configuration:
#Bean
public Docket edstrumentsApi() {
List<ResponseMessage> list = new ArrayList<>();
list.add(new ResponseMessageBuilder().code(500).message("500 message")
.responseModel(new ModelRef("Result")).build());
list.add(new ResponseMessageBuilder().code(401).message("Unauthorized")
.responseModel(new ModelRef("Result")).build());
list.add(new ResponseMessageBuilder().code(406).message("Not Acceptable")
.responseModel(new ModelRef("Result")).build());
return new Docket(DocumentationType.SWAGGER_2)
.select().apis(RequestHandlerSelectors.basePackage("backend"))
.paths(PathSelectors.any())
.build()
.directModelSubstitute(LocalDate.class, String.class)
.genericModelSubstitutes(ResponseEntity.class)
.securitySchemes(Collections.singletonList(securitySchema()))
.securityContexts(Collections.singletonList(securityContext())).pathMapping("/")
.useDefaultResponseMessages(false).apiInfo(apiInfo()).globalResponseMessage(RequestMethod.GET, list)
.globalResponseMessage(RequestMethod.POST, list)
.apiInfo(apiInfo());
}
#Bean
public OAuth securitySchema() {
List<AuthorizationScope> authorizationScopeList = new ArrayList();
authorizationScopeList.add(new AuthorizationScope(Constants.SCOPE_READ, "read all"));
authorizationScopeList.add(new AuthorizationScope(Constants.SCOPE_TRUST, "trust all"));
authorizationScopeList.add(new AuthorizationScope(Constants.SCOPE_WRITE, "access all"));
List<GrantType> grantTypes = new ArrayList();
GrantType creGrant = new ResourceOwnerPasswordCredentialsGrant( "http://localhost:8081/oauth/token");
grantTypes.add(creGrant);
return new OAuth("oauth2schema", authorizationScopeList, grantTypes);
}
yes i found the cause of the error.
i was creating the cors filter at the resource side instead of authorisation server.
That's why i was getting unable to get the option request on the server.

Resources