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
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());
}
}
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 {}
}
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.
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.
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();
}