Spring Security not working on any endpoint with multiple WebSecurityConfigurerAdapter implementations - spring

I'm trying to setup Spring Security in my application, which has 3 components:
REST API (under v1 path)
Spring Admin & actuator (under /admin path)
Docs (under /docs and /swagger-ui paths)
I want to setup security like this:
REST API secured with JWT token
Admin secured with HTTP basic
Docs unsecured (public resource)
I've tried to configure authentication for those 3 parts in separate implementations of WebSecurityConfigurerAdapter, and the result looks like this:
For REST API:
#Configuration
#EnableWebSecurity
#Order(1)
class ApiWebSecurityConfig : WebSecurityConfigurerAdapter() {
// FIXME: Temporary override to disable auth
public override fun configure(http: HttpSecurity) {
http
.antMatcher("/v1/*")
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.anyRequest().permitAll()
.and()
.csrf().disable()
}
}
For Spring Admin:
#Configuration
#EnableWebSecurity
#Order(2)
class AdminWebSecurityConfig(
private val adminServerProperties: AdminServerProperties
) : WebSecurityConfigurerAdapter() {
public override fun configure(http: HttpSecurity) {
http.antMatcher("${adminServerProperties.contextPath}/**")
.authorizeRequests()
.antMatchers("${adminServerProperties.contextPath}/assets/**").permitAll()
.antMatchers("${adminServerProperties.contextPath}/login").permitAll()
.anyRequest()
.authenticated()
.and()
.cors()
.and()
.formLogin()
.loginPage("${adminServerProperties.contextPath}/login")
.and()
.logout()
.logoutUrl("${adminServerProperties.contextPath}/logout")
.and()
.httpBasic()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringRequestMatchers(
AntPathRequestMatcher("${adminServerProperties.contextPath}/instances", HttpMethod.POST.toString()),
AntPathRequestMatcher("${adminServerProperties.contextPath}/instances/*", HttpMethod.DELETE.toString()),
AntPathRequestMatcher("${adminServerProperties.contextPath}/actuator/**")
)
}
#Bean
fun corsConfigurationSource(): CorsConfigurationSource = UrlBasedCorsConfigurationSource().apply {
registerCorsConfiguration("/**", CorsConfiguration().apply {
allowedOrigins = listOf("*")
allowedMethods = listOf("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH")
allowCredentials = true
allowedHeaders = listOf("Authorization", "Cache-Control", "Content-Type")
})
}
}
And for public docs:
#Configuration
#EnableWebSecurity
#Order(3)
class DocsWebSecurityConfig : WebSecurityConfigurerAdapter() {
public override fun configure(http: HttpSecurity) {
http
.requestMatchers()
.antMatchers("/swagger-ui/**", "/docs/**", "/docs-oas3/**")
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
}
}
And my main application class looks like this:
#SpringBootApplication
#EnableAdminServer
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
#EnableConfigurationProperties(FirebaseConfigurationProperties::class, JwtConfigurationProperties::class)
class HldpDeviceManagementApplication
fun main(args: Array<String>) {
runApplication<HldpDeviceManagementApplication>(*args)
}
When I run the application, there's no error or any security information, besides this log output:
Will not secure Ant [pattern='/v1/**']
Will not secure Ant [pattern='/admin/**']
Will not secure Or [Ant [pattern='/swagger-ui/**'], Ant [pattern='/docs/**'], Ant [pattern='/docs-oas3/**']]
Any suggestion why doesn't the configuration work? Or maybe another way I can secure the application like this? I've tried doing a few changes in the configuration, but nothing seems to help.

I've found the problem - it's a bug in the latest version of Spring:
https://github.com/spring-projects/spring-security/issues/10909

You didn't mention when it doesn't work, is it when you make a request, or on application startup? However, I can help you with your configuration and get the information needed to solve the problem.
I'll try to simplify your configuration with the new way to configure HttpSecurity, by exposing a SecurityFilterChain bean.
#Configuration
public class SecurityConfiguration {
#Bean
#Order(0)
public SecurityFilterChain api(HttpSecurity http) throws Exception {
http
.requestMatchers(requests -> requests.antMatchers("/v1/**"))
...
// the rest of the configuration
return http.build();
}
#Bean
#Order(1)
public SecurityFilterChain admin(HttpSecurity http) throws Exception {
http
.requestMatchers(requests -> requests.antMatchers("/admin/**"))
...
// the rest of the configuration
return http.build();
}
#Bean
#Order(2)
public SecurityFilterChain docs(HttpSecurity http) throws Exception {
http
.requestMatchers(requests -> requests.antMatchers("/docs/**"))
...
// the rest of the configuration
return http.build();
}
}
This is in Java, but you can adapt to Kotlin easily, I'm sorry to not provide it in Kotlin already. With this simplified configuration, now you can add logging.level.org.springframework.security=TRACE to your application.properties file and check what Spring Security is doing by reading the logs.

Related

Springboot with Keycloak always return 403

I have created a Springboot application with Keycloak by following this tutorial Baeldung
When I try to enter /api/foos it always returns 403 without any error messages.
// SecurityConfig
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, jsr250Enabled = true, prePostEnabled = true)
class SecurityConfig(
private val unauthorizedHandler: JwtAuthenticationEntryPoint
) : WebSecurityConfigurerAdapter() {
#Throws(Exception::class)
override fun configure(http: HttpSecurity) {
http
.cors()
.and()
.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/user/info", "/api/foos/**", "/api/foos")
.hasAnyRole("free_user")
.antMatchers(HttpMethod.POST, "/api/foos")
.hasAnyRole("free_user")
.anyRequest()
.authenticated()
.and()
.oauth2ResourceServer()
.jwt()
}
}
// Controller
#RestController
#RequestMapping(value = ["/api/foos"])
class SecurityTestController() {
#GetMapping(value = ["/{id}"])
fun findOne(#PathVariable id: Long?): String {
return "fineOne with id $id"
}
#GetMapping
fun findAll(): Message {
return Message("findAll")
}
}
// application.properties
# Resource server config
rest.security.issuer-uri=http://localhost:8081/auth/realms/dev
spring.security.oauth2.resourceserver.jwt.issuer-uri=${rest.security.issuer-uri}
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=${rest.security.issuer-uri}/protocol/openid-connect/certs
// build.gradle (app)
plugins {
id("org.springframework.boot")
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter- oauth2-resource-server")
implementation("org.springframework.boot:spring-boot-starter-security")
testImplementation("org.springframework.security:spring-security-test")
}
Here is my user on Keycloak
Postman result:
What I have already tried
disable csrf - Not working
Comment SecurityConfig class - Working without security
It looks like "free_user" is a client role.
Two solutions :
Either remove the current "free_user" client role, create a "free_user" realm role in the global Roles tab, and assign it to your user.
Or, add the property keycloak.use-resource-role-mappings=true to your Spring boot configuration to allow spring security to map your current client role.
I found the answer here LINK
We need to create a custom mapping between Spring security and Keycloak roles.
public class KeycloakRealmRoleConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
#Override
public Collection<GrantedAuthority> convert(Jwt jwt) {
final Map<String, Object> realmAccess = (Map<String, Object>) jwt.getClaims().get("realm_access");
return ((List<String>)realmAccess.get("roles")).stream()
.map(roleName -> "ROLE_" + roleName) // prefix to map to a Spring Security "role"
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
}

Spring Boot AntMatchers vs #PostAuthorize usage

I'm tasked with implementing RBAC(Role-Based Access Control) in the REST API I'm working on.
What puzzles me is that when I use in my Security class that extends WebSecurityConfigurerAdapter, in configure method antMatchers, the Authorisation is working correctly, but when I dispose of antMatchers and try to replace them by #PostAuthorize on top of an endpoint, RBAC fails to work.
That's my configure method from a class extending WebSecurityConfigurerAdapter:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.rememberMe()
.and()
.addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager()))
.addFilterAfter(new JwtTokenVerifierFilter(), JwtUsernameAndPasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/h2-console/**").permitAll()
.antMatchers("/user").authenticated()
.antMatchers("/hello").hasRole(ApplicationUserRole.ADMIN.name())
.anyRequest()
.authenticated();
http.headers().frameOptions().disable();/*REQUIRED FOR H2-CONSOLE*/
}
Which works fine.
Thats by annotarion on top of an endpoint that shoud be authorized, but is not.
#PostAuthorize("hasRole('ADMIN')")
#RequestMapping("/hello")
String hello(){
return "hello";
}
What am I doing wrong, that it is not workind correctly?
Did you try annotating your security config class with the below annotations?
Something like this.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(final HttpSecurity http) throws Exception {}
}

How to exclude a path from authentication in a spring based reactive application?

In a non reactive spring application I would usually create a configuration class, extend WebSecurityConfigurerAdapter and configure the WebSecurity like such:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/pathToIgnore");
}
How can I do the equivalent in a reactive application?
In your security config class which you have annotated with #EnableWebFluxSecurity and #EnableReactiveMethodSecurity, register a bean as follows:
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.authorizeExchange()
.pathMatchers("/pathToIgnore")
.permitAll()
.anyExchange()
.authenticated()
.and()
.formLogin()
.and()
.csrf()
.disable()
.build();
}
In this config, pathMatchers("/pathToIgnore").permitAll() would configure it to allow the paths matched to be excluded from auth and anyExchange().authenticated() would configure it to authenticate all other requests.

Spring: Using Oauth2 and HttpBasicAuth together

I'm using Spring Boot 2.0.0
For securing my REST API i'm using Oauth2 with JWT, which works perfectly fine.
The problem is:
I'm also using Springfox Swagger which should be secured by BasicAuth. So that the user is challenged if he points his browser to /swagger-ui.html
Therefore i got two configuration files:
SecurityConfig
#Configuration
#EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {
#Throws(Exception::class)
override fun configure(web: WebSecurity) {
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**")
}
#Throws(Exception::class)
override fun configure(auth: AuthenticationManagerBuilder) {
auth.inMemoryAuthentication()
//user: "user", password: "Passw0rd!"
.withUser("user")
.password("\$2a\$04\$DDYoNw1VAYt64.zU.NsUpOdvjZ3OVrGXJAyARkraaS00h322eL2iy")
.roles("ADMIN")
}
}
ResourceServerConfig
#Configuration
#EnableResourceServer
class ResourceServerConfig : ResourceServerConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
super.configure(http)
http.httpBasic().and().cors().and().csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.antMatcher("/swagger-ui.html**")
.authorizeRequests().anyRequest().hasRole("ADMIN")
}
}
I think the OAuth2AuthorizationServerConfig is not needed here.
The shown configuration (of course) doesn't work, so the question is:
Is it possible to mix BasicAuth and Oauth2?
Ok, i more or less found an answer to my question:
SecurityConfig
#Configuration
#EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {
#Throws(Exception::class)
override fun configure(auth: AuthenticationManagerBuilder) {
auth.inMemoryAuthentication()
//user: "user", password: "Passw0rd!"
.withUser("user")
.password("...")
.roles("ADMIN")
}
override fun configure(http: HttpSecurity) {
http.csrf()
.and()
.httpBasic()
.and()
.authorizeRequests()
.antMatchers(
"/swagger-ui.html**").hasRole("ADMIN")
.antMatchers(
"/v2/api-docs",
"/swagger-resources/**",
"/configuration/security", "/webjars/**"
).permitAll()
}
}
ResourceServerConfig
#Configuration
#EnableResourceServer
class ResourceServerConfig : ResourceServerConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http.cors().and()
.antMatcher("/api")
.authorizeRequests().antMatchers("/api/**").authenticated()
}
}
I'm using antMatcher in the ResourceServerConfig to protect only the /api/** paths with oauth2.
The BasicAuth part happens in SecurityConfig.
The current solution only applies the BasicAuth only to the /swagger-ui.html endpoint. The other swagger resources are public visible.
Does anyone know a way to also protect /v2/api-docs?
It's all about matching security configurations onto different application context paths. See my answer at Run a Spring Boot oAuth2 application as resource server AND serving web content - I hope this helps.

How to turn off Spring Security in Spring Boot Application

I have implemented authentication in my Spring Boot Application with Spring Security.
The main class controlling authentication should be websecurityconfig:
#Configuration
#EnableWebSecurity
#PropertySource(value = { "classpath:/config/application.properties" })
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private RestAuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(
SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/logout").permitAll()
.antMatchers("/ristore/**").authenticated()
.anyRequest().authenticated()
.and()
.formLogin()
.successHandler(authenticationSuccessHandler)
.failureHandler(new SimpleUrlAuthenticationFailureHandler());
}
Since I am doing OAuth, I have AuthServerConfig and ResourceServerConfig as well. My main application class looks like this:
#SpringBootApplication
#EnableSpringDataWebSupport
#EntityScan({"org.mdacc.ristore.fm.models"})
public class RistoreWebApplication extends SpringBootServletInitializer
{
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*");
}
};
}
public static void main( String[] args )
{
SpringApplication.run(RistoreWebApplication.class, args);
}
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(RistoreWebApplication.class);
}
}
Since we are doing code consolidation, we need to turn off authentication temporarily. However, I tried the following methods and nothing seems to work. I am still getting 401 when I hit these rest api urls.
Comment out all the annotations in classes related to security including #Configuration, #EnableWebSecurity. In Spring boot Security Disable security, it was suggested at the bottom adding #EnableWebSecurity will DISABLE auth which I don't think make any sense. Tried it anyway, did not work.
Modify websecurityconfig by removing all the security stuff and only do
http
.authorizeRequests()
.anyRequest().permitAll();
Disable Basic Authentication while using Spring Security Java configuration. Does not help either.
Remove security auto config
#EnableAutoConfiguration(exclude = {
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration.class,
org.springframework.boot.actuate.autoconfigure.ManagementSecurityAutoConfiguration.class})
like what they did in disabling spring security in spring boot app. However I think this feature only works with spring-boot-actuator which I don't have. So didn't try this.
What is the correct way disable spring security?
As #Maciej Walkowiak mentioned, you should do this for your main class:
#SpringBootApplication(exclude = org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration.class)
public class MainClass {
try this
1->Comment annotation #EnableWebSecurity in your security config
//#EnableWebSecurity
2->Add these lines in your security config
spring.security.enabled=false
management.security.enabled=false
security.basic.enabled=false
What worked for me is this. Creating WebFilter and PermitAll Request Exchange and disabling CSRF.
#Bean
public SecurityWebFilterChain chain(ServerHttpSecurity http, AuthenticationWebFilter webFilter) {
return http.authorizeExchange().anyExchange().permitAll().and()
.csrf().disable()
.build();
}
Just put this code in #SpringBootApplication class, Like this and will work like charm
#SpringBootApplication
public class ConverterApplication {
public static void main(String[] args) {
SpringApplication.run(ConverterApplication.class, args);
}
#Bean
public SecurityWebFilterChain chain(ServerHttpSecurity http, AuthenticationWebFilter webFilter) {
return http.authorizeExchange().anyExchange().permitAll().and()
.csrf().disable()
.build();
}

Resources