CSRF token for interservice communication - spring

I have disabled CSRF token for all the APIs (url pattern - /api/..) that is called only from other microservice.
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> {
csrf.ignoringAntMatchers("/api/**");
csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}).authorizeHttpRequests(authz -> {
try {
authz.antMatchers("/authenticate").permitAll()
.anyRequest().authenticated().and().exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
} catch (Exception e) {
LOGGER.error("Exception in filterChain..");
}
}).httpBasic();
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
All works fine. But it shows sonarqube security bug for using csrf.ignoringAntMatchers("/api/**")
How could i safely turn off csrf for interservice communication.
Using spring boot version 2.6.6

Related

Spring Security in Spring Boot 3

I'm currently in the process of migrating our REST application from Spring Boot 2.7.5 to 3.0.0-RC2. I want everything to be secure apart from the Open API URL. In Spring Boot 2.7.5, we used to do this:
#Named
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/openapi/openapi.yml").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
and it worked fine. In Spring Boot 3, I had to change it to
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> requests
.requestMatchers("/openapi/openapi.yml").permitAll()
.anyRequest()
.authenticated())
.httpBasic();
return http.build();
}
}
since WebSecurityConfigurerAdapter has been removed. It's not working though. The Open API URL is also secured via basic authentication. Have I made a mistake when upgrading the code or is that possibly an issue in Spring Boot 3 RC 2?
Update
Since most of the new API was already available in 2.7.5, I've updated our code in our 2.7.5 code base to the following:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests((requests) -> requests
.antMatchers(OPTIONS).permitAll() // allow CORS option calls for Swagger UI
.antMatchers("/openapi/openapi.yml").permitAll()
.anyRequest().authenticated())
.httpBasic();
return http.build();
}
}
In our branch for 3.0.0-RC2, the code is now as follows:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests((requests) -> requests
.requestMatchers(OPTIONS).permitAll() // allow CORS option calls for Swagger UI
.requestMatchers("/openapi/openapi.yml").permitAll()
.anyRequest().authenticated())
.httpBasic();
return http.build();
}
}
As you can see, the only difference is that I call requestMatchers instead of antMatchers. This method seems to have been renamed. The method antMatchers is no longer available. The end effect is still the same though. On our branch for 3.0.0-RC2, Spring Boot asks for basic authentication for the OpenAPI URL. Still works fine on 2.7.5.
Author: https://github.com/wilkinsona
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.requestMatchers(new AntPathRequestMatcher("/openapi/openapi.yml")).permitAll()
.anyRequest().authenticated())
.httpBasic();
return http.build();
}
Source: https://github.com/spring-projects/spring-boot/issues/33357#issuecomment-1327301183
I recommend you use Spring Boot 3.0.0 (GA) right now, not RC version.
Inside my WebSecurityConfig, I did this:
private static final String[] AUTH_WHITELIST = {
// -- Swagger UI v2
"/v2/api-docs",
"v2/api-docs",
"/swagger-resources",
"swagger-resources",
"/swagger-resources/**",
"swagger-resources/**",
"/configuration/ui",
"configuration/ui",
"/configuration/security",
"configuration/security",
"/swagger-ui.html",
"swagger-ui.html",
"webjars/**",
// -- Swagger UI v3
"/v3/api-docs/**",
"v3/api-docs/**",
"/swagger-ui/**",
"swagger-ui/**",
// CSA Controllers
"/csa/api/token",
// Actuators
"/actuator/**",
"/health/**"
};
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests( auth -> auth
.requestMatchers(AUTH_WHITELIST).permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.httpBasic(withDefaults())
.addFilterBefore(authenticationJwtTokenFilter, UsernamePasswordAuthenticationFilter.class)
//.addFilterAfter(authenticationJwtTokenFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
#Bean
public SecurityFilterChain configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeHttpRequests((requests) -> requests
.requestMatchers( new AntPathRequestMatcher("swagger-ui/**")).permitAll()
.requestMatchers( new AntPathRequestMatcher("/swagger-ui/**")).permitAll()
.requestMatchers( new AntPathRequestMatcher("v3/api-docs/**")).permitAll()
.requestMatchers( new AntPathRequestMatcher("/v3/api-docs/**")).permitAll()
.anyRequest().authenticated())
.httpBasic();
return httpSecurity.build();
}
This and using Dockerfile (doing mvn clean package and running .jar from Docker) made me had no issues with authentication inside swagger ui.
Hope this can help you :)
Use
http.securityMatcher("<patterns>")...
to specify authentication for endpoints.
authorizeHttpRequests((requests) -> requests
.requestMatchers("<pattern>")
only works for authorization, if you don't set securityMatcher , SecurityFilterChain by default gets any request for authentication. And any request will be authenticated by an authentication provider.
In your case, you can define two security filter, chains: one for public endpoitns, another for secured. And give them proper order:
#Bean
#Order(1)
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.securityMatcher(OPTIONS,"/openapi/openapi.yml").csrf().disable()
.authorizeHttpRequests((requests) -> requests
.anyRequest().permitAll() // allow CORS option calls for Swagger UI
);
return http.build();
}
#Bean
Order(2)
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.securityMatcher("/**")
.csrf().disable()
.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated())
.httpBasic();
return http.build();
}
The official documentation suggests an example which I have abridged here with your config:
http
.authorizeExchange((exchanges) ->
exchanges
.pathMatchers("/openapi/openapi.yml").permitAll()
.anyExchange().authenticated())
.httpBasic();
return http.build();
You could try this, since it changes the "request" for the "exchange" wording, in line with the migration to declarative clients (#PostExchange vs. #PostMapping) I suppose. Hope it helps.
My security cfg looks like:
Spring 3.0.0
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(requests -> requests
.requestMatchers(HttpMethod.GET, "/", "/static/**", "/index.html", "/api/users/me").permitAll()
.requestMatchers(HttpMethod.POST, "/api/users").permitAll()
.requestMatchers(HttpMethod.GET, "/api/users/login", "/api/users/{username}", "/api/users/logout", "/api/costumers", "/api/storages").authenticated()
.requestMatchers(HttpMethod.POST, "/api/costumers", "/api/storages").authenticated()
.requestMatchers(HttpMethod.PUT, "/api/costumers/{id}", "/api/storages/{id}").authenticated()
.requestMatchers(HttpMethod.DELETE, "/api/users/{id}", "/api/storages/{id}", "/api/costumers/{id}").authenticated()
.anyRequest().denyAll())
.httpBasic();
return http.build();
}
it works
This seems to be a bug in Spring Boot 3. I've raised an issue.

Spring security very simple basic authentication

I've tried to implement a very simple BASIC authentication with Spring Boot, without the deprecated WebSecurityConfigurerAdapter.
#Configuration
public class SecurityConfig {
#Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers("/a", "/b", "/c", "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html");
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authz) -> authz
.anyRequest().authenticated()
)
.httpBasic();
return http.build();
}
#Bean
public InMemoryUserDetailsManager userDetailsService() {
UserDetails user = User.builder()
.username("user")
.password("{bcrypt}$2y$10$rUzpfbTx9lcIs6N4Elcg2e2DGM4wMwkx0ixom7qLW5kYnztRgT.a2")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
The ignored endpoints work (with a warning: You are asking Spring Security to ignore Ant [pattern='/swagger-ui.html']. This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.). For the other, I get an HTTP 403.
What have I done wrong?
If you are doing POST request, it can be the CSRF protection. Add logging.level.org.springframework.security=TRACE in your application.properties file and see the console output after the request is made to see what is happening.
If it is CSRF protection, I recommend you leave it enabled unless you have a requirement that tells you to disable it. You can have more details about Cross Site Request Forgery here.
Also, if you want to use the {bcrypt} prefix in your password, use the PasswordEncoderFactories.createDelegatingPasswordEncoder. If you want to use only the BCryptPasswordEncoder then you have to remove the {bcrypt} prefix

why is the AuthenticationManagerResolver called on a permitAll() (open endpoint)

Our spring boot 2.5.12 app is secured w/ a security configuration like this:
protected void configure(HttpSecurity http) throws Exception {
http
.cors().configurationSource(corsConfigurationSource)
.and()
.csrf()
.ignoringAntMatchers("/")
.and()
.authorizeRequests(authorizeRequests -> authorizeRequests
.mvcMatchers(GET, "/endpoint").hasAuthority("SCOPE_" + (Scope.READ))
.mvcMatchers(GET, "/endpoint/{reference}").permitAll()
.mvcMatchers(GET, "/error").permitAll()
.mvcMatchers(GET, "/info").permitAll()
.mvcMatchers(GET, "/health").permitAll()
.anyRequest().denyAll())
.oauth2ResourceServer()
.authenticationManagerResolver(authenticationManager())
.accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(authenticationEntryPoint);
}
w/ an AuthenticationManagerResolverBean:
public AuthenticationManagerResolver<HttpServletRequest> authenticationManager() {
return request -> {
...
...
...
};
}
it looks as if there's a bug as when i access the endpoint: /endpoint/ref123 it calls the AuthenticationManagerResolver even though this endpoint is open with a .permitAll(). So in the case the user accidentally provides an invalid token on this .permitAll() endpoint they aren't rejected.
if an endpoint is a .permitAll() then shouldn't spring not try to validate the token?
I didn't quite find why this is the behavior but we did find a workaround of sorts.
public void configure(WebSecurity web) {
web
.ignoring()
.mvcMatchers(GET, "/endpoint/{reference}");
}
It gets spring security to ignore tokens all together... valid or otherwise (which is what i thought permitAll did).

Spring security permitall return 401

Spring Security Config
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/admin").hasRole("ADMIN")
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/test/**").permitAll()
.antMatchers("/").permitAll()
.antMatchers("/favicon.ico").permitAll()
.antMatchers("/static/**").permitAll()
.antMatchers("/manifest.json").permitAll()
.antMatchers("/logo192.png").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
I also tried this but did not produce any result
.antMatchers(HttpMethod.POST, "/api/auth/**").permitAll()
/api/auth/signup return
error: "Unauthorized"
message: "Full authentication is required to access this resource"
path: "/error"
status: 401
Request URL: https://mysuite.ru/api/auth/signup
How can I fix this problem?
UPDATE
#Configuration
public class MvcSecurityConfig implements WebMvcConfigurer {
#Value("${path.frontend}")
private String frontendPath;
#Value("${frontendStaticResourcesPathPatterns}")
private String[] frontendStaticResourcesPathPatterns;
private static final String BASE_API_PATH = "/";
public void addResourceHandlers(ResourceHandlerRegistry registry){
String pathToFrontend = "file:" + this.frontendPath;
String pathToIndexHTML = pathToFrontend + "/index.html";
registry
.addResourceHandler(frontendStaticResourcesPathPatterns)
.setCachePeriod(0)
.addResourceLocations(pathToFrontend);
registry.addResourceHandler("/", "/**")
.setCachePeriod(0)
.addResourceLocations(pathToIndexHTML)
.resourceChain(true)
.addResolver(new PathResourceResolver() {
#Override
protected Resource getResource(String resourcePath, Resource location) throws IOException {
if (resourcePath.startsWith(BASE_API_PATH) || resourcePath.startsWith(BASE_API_PATH.substring(1))) {
return null;
}
return location.exists() && location.isReadable() ? location : null;
}
});
}
}
This is my Spring MVC Config.
Could any of this cause the problem?
I also tried to do permitAll step by step along the path but it didn't work (api/, api/auth, api/autn/**)
By default, Spring Security comes with CSRF Protection enabled, so when you perform an unsafe request (POST, PUT, DELETE) you have to provide a CSRF Token.
In your configure method you can disable it to check if it will work.
http.csrf().disable()
I advise you that disabling CSRF protection can be harmful to your app and you should make sure if you need to use it or not.
Also, if you are using Spring Security's version 5.4 or higher, you can enable trace logs to help you debug it.
logging.level.org.springframework.security=TRACE
You can get more details in the reference docs.
In an Ant matcher, ** matches zero or more directories in a path. Given your request URL you just need to match zero or more characters. Having said that, try replacing your Ant matcher with the following:
.antMatchers(HttpMethod.POST, "/api/auth/*").permitAll()
By pass your filter because any API request throught Filter. Your API can not pass Filter so you get 401 response.
Try add this to your Spring Security Config:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/auth/**");
}
Or add this to OncePerRequestFilter:
#Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return new AntPathMatcher().match("/api/auth/**", request.getServletPath());
}

Keycloak get 401 error, but spring security does not handle this error

The problem is as follows. I implemented the login through Keycloak Bearer Spring security like this
public class KeycloakSecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Override
public void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.sessionAuthenticationStrategy(sessionAuthenticationStrategy())
.and()
.csrf().disable()
.addFilterBefore(keycloakPreAuthActionsFilter(), LogoutFilter.class)
.addFilterBefore(keycloakAuthenticationProcessingFilter(), X509AuthenticationFilter.class)
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
.and()
.authorizeRequests().antMatchers(Constants.API_BASE_PATH + "/**").authenticated();
}
}
when I send the Authorization request header empty, keycloak throws error 401. Which I cannot catch through #ExceptionHandler like this :
#ExceptionHandler(RuntimeException.class)
protected ResponseEntity<Object> keycloakAuthenticationExceptionn(RuntimeException ex) {
return buildResponseEntity(new ErrorResponseWrapper(BAD_REQUEST,new
MessageResponse(ex.getLocalizedMessage()),ex,ErrorCode.NOT_AUTHORIZED));
}
KeyCloak has a KeycloakAuthenticationFailureHandler that handles authentication failures.
I was able to solve a similar problem by creating a Custom KeycloakAuthenticationFailureHandler then set my Custom class while overriding the
KeycloakAuthenticationProcessingFilter.
#Bean
#Override
protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception {
KeycloakAuthenticationProcessingFilter filter = new KeycloakAuthenticationProcessingFilter(this.authenticationManagerBean());
filter.setSessionAuthenticationStrategy(this.sessionAuthenticationStrategy());
filter.setAuthenticationFailureHandler(new CustomKeycloakAuthenticationFailureHandler());
return filter;
}
Inside my Custom Class...
public class CustomKeycloakAuthenticationFailureHandler implements AuthenticationFailureHandler {
public CustomKeycloakAuthenticationFailureHandler() {}
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
if (!response.isCommitted()) {
if (KeycloakCookieBasedRedirect.getRedirectUrlFromCookie(request) != null) {
response.addCookie(KeycloakCookieBasedRedirect.createCookieFromRedirectUrl((String)null));
}
//response.sendError(401, "Unable to authenticate using the Authorization header");
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getOutputStream().println("{ \"error\": \"" + exception.getMessage() + "\" }");
} else if (200 <= response.getStatus() && response.getStatus() < 300) {
throw new RuntimeException("Success response was committed while authentication failed!", exception);
}
}
}
I am able to use the response OutputStream to customize my response to the client.
I commented the default KeyCloak response.sendError line.
It looks like KeyCloak handles the exceptions internally.
This solution also solves the issue where the header: WWW-Authenticate
is not duplicated in the response.
Turning on DEBUG for KeyCloak troubleshooting HELPS: logging.level.org.keycloak=DEBUG
Hope this helps.
I'm not sure about the answer but i faced something like your question. ExceptionHandler working after http filters, so maybe Keycloak exception throws in his filter, before the request can to be handled using your ExceptionHandler. So you can trace the whole logs to see from where the exception thrown. I hope that will help you
**Thanks for answers!**
I think, this is the problem:
Earlier I used dependency keycloak version 4.0.0.Final
pom.xml dependency
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
<version>4.0.0.Final</version>
</dependency>
and spring boot version 1.5.4.RELEASE. Everything worked great.
Now I'm using spring boot version 2.1.5.RELEASE and keycloak version 10.0.1
pom.xml dependency
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-2-adapter</artifactId>
<version>10.0.1</version>
</dependency>
I checked it again from Postman when sending Authorization token "bearer " + "token" in the request header. I get a response with two the same WWW-Authenticate values ​​in the header.
Earlier, header came in a single copy.
Can you please tell me what the problem ?.

Resources