Spring WebMvcTest with post returns 403 - spring

I'm wondering where the issue is with my code, every time I run a post test (irrespective of what controller it targets, or method), I return a 403 error, when in some cases I expect a 401, and in others a 200 response (with auth).
This is a snippet from my controller:
#RestController
#CrossOrigin("*")
#RequestMapping("/user")
class UserController #Autowired constructor(val userRepository: UserRepository) {
#PostMapping("/create")
fun addUser(#RequestBody user: User): ResponseEntity<User> {
return ResponseEntity.ok(userRepository.save(user))
}
}
And my unit test targeting this controller
#RunWith(SpringRunner::class)
#WebMvcTest(UserController::class)
class UserControllerTests {
#Autowired
val mvc: MockMvc? = null
#MockBean
val repository: UserRepository? = null
val userCollection = mutableListOf<BioRiskUser>()
#Test
fun testAddUserNoAuth() {
val user = BioRiskUser(
0L,
"user",
"password",
mutableListOf(Role(
0L,
"administrator"
)))
repository!!
`when`(repository.save(user)).thenReturn(createUser(user))
mvc!!
mvc.perform(post("/create"))
.andExpect(status().isUnauthorized)
}
private fun createUser(user: BioRiskUser): BioRiskUser? {
user.id=userCollection.count().toLong()
userCollection.add(user)
return user
}
}
What am I missing?
As requested, my security config...
#Configuration
#EnableWebSecurity
class SecurityConfig(private val userRepository: UserRepository, private val userDetailsService: UserDetailsService) : WebSecurityConfigurerAdapter() {
#Bean
override fun authenticationManagerBean(): AuthenticationManager {
return super.authenticationManagerBean()
}
override fun configure(auth: AuthenticationManagerBuilder) {
auth.authenticationProvider(authProvider())
}
override fun configure(http: HttpSecurity) {
http
.csrf().disable()
.cors()
.and()
.httpBasic()
.realmName("App Realm")
.and()
.authorizeRequests()
.antMatchers("/img/*", "/error", "/favicon.ico", "/doc")
.anonymous()
.anyRequest().authenticated()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutSuccessUrl("/user")
.permitAll()
}
#Bean
fun authProvider(): DaoAuthenticationProvider {
val authProvider = CustomAuthProvider(userRepository)
authProvider.setUserDetailsService(userDetailsService)
authProvider.setPasswordEncoder(encoder())
return authProvider
}
}
and the auth provider
class CustomAuthProvider constructor(val userRepository: UserRepository) : DaoAuthenticationProvider() {
override fun authenticate(authentication: Authentication?): Authentication {
authentication!!
val user = userRepository.findByUsername(authentication.name)
if (!user.isPresent) {
throw BadCredentialsException("Invalid username or password")
}
val result = super.authenticate(authentication)
return UsernamePasswordAuthenticationToken(user, result.credentials, result.authorities)
}
override fun supports(authentication: Class<*>?): Boolean {
return authentication?.equals(UsernamePasswordAuthenticationToken::class.java) ?: false
}
}

In my case, the csrf-Protection seems to be still active in my WebMvcTest (even if disabled in your configuration).
So to workaround this, I simply changed my WebMvcTest to something like:
#Test
public void testFoo() throws Exception {
MvcResult result = mvc.perform(
post("/foo").with(csrf()))
.andExpect(status().isOk())
.andReturn();
// ...
}
So the missing .with(csrf()) was the problem in my case.

You need to add #ContextConfiguration(classes=SecurityConfig.class) to the top of your UserControllerTests class after the #WebMvcTest(UserController::class) annotation.

Your problem comes from the CSRF, if you enable debug logging the problem will become obvious, and it comes from the fact that #WebMvcTest load only the web layer and not the whole context, your KeycloakWebSecurityConfigurerAdapter is not loaded.
The loaded config comes from org.springframework.boot.autoconfigure.security.servlet.DefaultConfigurerAdapter (= to org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
WebSecurityConfigurerAdapter contains crsf().
As of today you have 3 options to resolve this:
Options 1
Create a WebSecurityConfigurerAdapter inside your test class.
The solution suits you if you have only few #WebMvcTest annotated class in your project.
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = {MyController.class})
public class MyControllerTest {
#TestConfiguration
static class DefaultConfigWithoutCsrf extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable();
}
}
...
}
Options 2
Create a WebSecurityConfigurerAdapter inside a superclass and make your test extend from it.
The solution suits you if you have multiple #WebMvcTest annotated class in your project.
#Import(WebMvcTestWithoutCsrf.DefaultConfigWithoutCsrf.class)
public interface WebMvcCsrfDisabler {
static class DefaultConfigWithoutCsrf extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable();
}
}
}
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = {MyControllerTest .class})
public class MyControllerTest implements WebMvcCsrfDisabler {
...
}
Options 3
Use the spring-security csrf SecurityMockMvcRequestPostProcessors.
This solution is bulky and prone to error, checking for permission denial and forgeting the with(csrf()) will result in false positive test.
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = {MyController.class})
public class MyControllerTest {
...
#Test
public void myTest() {
mvc.perform(post("/path")
.with(csrf()) // <=== THIS IS THE PART THAT FIX CSRF ISSUE
.content(...)
)
.andExpect(...);
}
}

Here's an issue:
override fun configure(http: HttpSecurity) {
http
.csrf().disable()
.cors()
.and()
.httpBasic()
.realmName("App Realm")
.and()
.authorizeRequests()
.antMatchers("/img/*", "/error", "/favicon.ico", "/doc")
.anonymous()
.anyRequest().authenticated()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutSuccessUrl("/user")
.permitAll()
}
More particularly here:
.anyRequest().authenticated()
You're requiring for each request to be authenticated, therefore you get 403.
This tutorial explains well how to perform testing with mock user.
The easy way is to have something like this:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class SecuredControllerRestTemplateIntegrationTest {
#Autowired
private val template: TestRestTemplate
#Test
fun createUser(): Unit {
val result = template.withBasicAuth("username", "password")
.postForObject("/user/create", HttpEntity(User(...)), User.class)
assertEquals(HttpStatus.OK, result.getStatusCode())
}
}

Related

Unable to resolve CORS errors

Assumptions
We are developing a web application with the following library.
When a request is sent from the front end to the back end, a CORS error occurs.
Frontend: Vue.js (Version: 3)
Backend: SpringBoot (version: 2.7.6)
Authentication: SpringSecurity
What we want to achieve
We would like to resolve the following CORS errors that occur when a request is sent from the front-end side to the back-end side.
Access to XMLHttpRequest at 'http://localhost:8085/users/profile/1' from origin 'http://localhost:8888' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Source code
Send request to Spring in Vue.js (Edit.vue)
onClickDelete() {
const path = 'users/profile/'
axios.delete(
process.env.VUE_APP_ROOT_API + path + this.$store.state.user_id,{
headers: {
"Authorization": "Bearer " + this.$store.state.jwt_token,
},
})
.then(response => {
})
.catch(error => {
console.log(error)
})
},
Receiving process in Spring (UsersController.java)
#RestController
#RequestMapping("/users/profile")
public class UsersController {
#DeleteMapping("/{user_id}")
#ResponseStatus(code = HttpStatus.NO_CONTENT, value = HttpStatus.NO_CONTENT)
public void profiledelete(#PathVariable("user_id") Long id) throws Exception {
}
}
SpringSecurity configuration file (WebSecurityConfig.java)
#Profile("production")
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserRepository userRepository;
private final JsonRequestAuthenticationProvider jsonRequestAuthenticationProvider;
#Value("${security.secret-key:secret}")
private String secretKey = "secret";
public WebSecurityConfig(JsonRequestAuthenticationProvider jsonRequestAuthenticationProvider// ,
) {
this.jsonRequestAuthenticationProvider = jsonRequestAuthenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
JsonRequestAuthenticationFilter jsonAuthFilter =
new JsonRequestAuthenticationFilter(userRepository);
jsonAuthFilter.setAuthenticationManager(authenticationManagerBean());
http.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues());
http.addFilter(jsonAuthFilter);
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler())
.and()
.csrf().
disable()
.addFilterBefore(tokenFilter(), UsernamePasswordAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
;
}
What we tried
#CrossOrigin to the process (UsersController.java) that receives the process in Spring
What we did
Receive process in Spring (UsersController.java)
#RestController
#RequestMapping("/users/profile")
#CrossOrigin
public class UsersController {
#DeleteMapping("/{user_id}")
#ResponseStatus(code = HttpStatus.NO_CONTENT, value = HttpStatus.NO_CONTENT)
public void profiledelete(#PathVariable("user_id") Long id) throws Exception {
}
}
Result
The CORS error is still displayed.
Additional Information
Before SpringSecurity was installed, I think that granting #CrossOrigin on the Spring side solved the CORS error.
When the GET method is used in other requests, it succeeds without any CORS errors with the Spring side.
This seems to be an issue with your setup with spring security.
There are two primary ways to fix this error; however, I would also recommend upgrading to a newer version of spring security, because WebSecurityConfigurerAdapter has now been deprecated.
Primary method
CORS on Spring security (2.x)
#Profile("production")
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserRepository userRepository;
private final JsonRequestAuthenticationProvider jsonRequestAuthenticationProvider;
#Value("${security.secret-key:secret}")
private String secretKey = "secret";
public WebSecurityConfig(JsonRequestAuthenticationProvider jsonRequestAuthenticationProvider// ,
) {
this.jsonRequestAuthenticationProvider = jsonRequestAuthenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
JsonRequestAuthenticationFilter jsonAuthFilter =
new JsonRequestAuthenticationFilter(userRepository);
jsonAuthFilter.setAuthenticationManager(authenticationManagerBean());
http.cors().configurationSource(request -> {
var cors = new CorsConfiguration();
cors.setAllowedOrigins(List.of("*"));
cors.setAllowedMethods(List.of("GET","POST", "PUT", "DELETE", "OPTIONS"));
cors.setAllowedHeaders(List.of("*"));
return cors;
});
http.addFilter(jsonAuthFilter);
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler())
.and()
.csrf().
disable()
.addFilterBefore(tokenFilter(), UsernamePasswordAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
;
}
CORS disable
#Profile("production")
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserRepository userRepository;
private final JsonRequestAuthenticationProvider jsonRequestAuthenticationProvider;
#Value("${security.secret-key:secret}")
private String secretKey = "secret";
public WebSecurityConfig(JsonRequestAuthenticationProvider jsonRequestAuthenticationProvider// ,
) {
this.jsonRequestAuthenticationProvider = jsonRequestAuthenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
JsonRequestAuthenticationFilter jsonAuthFilter =
new JsonRequestAuthenticationFilter(userRepository);
jsonAuthFilter.setAuthenticationManager(authenticationManagerBean());
http.cors().disable();
http.addFilter(jsonAuthFilter);
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler())
.and()
.csrf().
disable()
.addFilterBefore(tokenFilter(), UsernamePasswordAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
;
}
CORS on Spring security (3.x)
#Configuration
public class WebConfiguration implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("*");
}
}
Always go for the second method.

Authentication manager is null after upgrading spring security

I am writing application in spring boot 2.7.4 version which has new version of spring security. So I need to rewrite my old code to the new one.
Here is my old security configuration with WebSecurityConfigurerAdapter
#Configuration
#EnableWebSecurity
class AppWebConfig(
val customUserDetailsService: CustomUserDetailsService,
val passwordEncoder: PasswordEncoder,
val tokensService: TokensService
) : WebSecurityConfigurerAdapter() {
#Throws(Exception::class)
override fun configure(auth: AuthenticationManagerBuilder) {
auth
.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder)
}
#Throws(Exception::class)
override fun configure(http: HttpSecurity) {
http
.cors()
.and()
.csrf().disable()
.exceptionHandling()
//urls permissions...
.addFilter(AppAuthorizationFilter(authenticationManager(), tokensServicee))
}
}
And here is the new code
#Configuration
#EnableWebSecurity
class AppWebConfig(
val tokensService: TokensService,
) {
#Bean
#Throws(Exception::class)
fun authenticationManager(authenticationConfiguration: AuthenticationConfiguration): AuthenticationManager? {
return authenticationConfiguration.authenticationManager
}
#Bean
#Throws(Exception::class)
protected fun fitlerChain(http: HttpSecurity): SecurityFilterChain {
val authenticationManager = http.getSharedObject(AuthenticationManager::class.java)
return http
.cors()
.and()
.csrf().disable()
//urls permissions...
.addFilter(AppAuthorizationFilter(authenticationManager, tokensService))
.build()
}
Here is the AppAuthorizationFilter which hasn't changed in both versions and where the authenticationManager is used:
class AppAuthorizationFilter(
authenticationManager: AuthenticationManager,
tokensService: TokensService,
) : BasicAuthenticationFilter(authenticationManager) {
private val tokensService: TokensService
init { this.tokensService = tokensService }
#Throws(IOException::class, ServletException::class)
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
chain: FilterChain,
) {
val header = request.getHeader(Objects.requireNonNull(HttpHeaders.AUTHORIZATION))
if (header != null) {
val authorizedUser = tokensService.parseAccessToken(header)
SecurityContextHolder.getContext().authentication = authorizedUser
}
chain.doFilter(request, response)
}
}
And the problem is with authenticationManager from AppWebConfig. I get the error that this its null.
Caused by: java.lang.NullPointerException: authenticationManager must not be null at com.app.security.config.WebConfig.fitlerChain(WebConfig.kt:68)
I tried the solution I showed above by getting authenticationManager from shared objects http.getSharedObject(AuthenticationManager::class.java) but it does not work as you can see.
I solved the problem by getting authenticationManager from WebApplicationContext but I am not sure if it is the best way to do it
val authenticationManager = applicationContext.getBean("authenticationManager") as AuthenticationManager
Try to add this annotation (#Primary) to you authenticationManager bean. If didn't work, then go with the solution you mentioned.
#Bean
#Primary // try to add this
#Throws(Exception::class)
fun authenticationManager(authenticationConfiguration: AuthenticationConfiguration): AuthenticationManager? {
return authenticationConfiguration.authenticationManager
}
Please access the authentication manager once you build it or after it builds.
public SecurityFilterChain securityFilterChain(HttpSecurity http, ResourceProtectAware protectAware) throws Exception {
protectAware.addUrlAuthorization(http);
http.cors();
http.httpBasic().authenticationEntryPoint(entryPoint());
// http.addFilterBefore(spnegoAuthenticationFilter(configuration.getAuthenticationManager()), BasicAuthenticationFilter.class);
DefaultSecurityFilterChain build = http.build();
//access it after you build.
AuthenticationManager sharedObject = http.getSharedObject(AuthenticationManager.class);
return build;
}

How to add a ResourceServer to an existing spring security config

I use the following security configuration for authorization. I now want to add token secured endpoints to this project.
However, whenever I send a request to the secured endpoint I always find myself redirected to the login-page ("/oauth_login") as if I were unauthorized.
What am I doing wrong here? I tried to debug this and it seems the overriden decode() function of my accessTokenConverter is never called when I try to access the endpoint with a valid token.
This is the security config I already have:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#PropertySource("application.properties")
#Order(1)
class SecurityConfig(
private val userDetailsService: CustomUserDetailsService,
private val inMemoryClientRegistrationRepository: InMemoryClientRegistrationRepository,
private val secretAuthenticationFilter: SecretAuthenticationFilter
) : WebSecurityConfigurerAdapter() {
#Bean
#Throws(Exception::class)
override fun authenticationManager() = super.authenticationManager()
#Throws(Exception::class)
override fun configure(auth: AuthenticationManagerBuilder?) {
auth!!.userDetailsService(userDetailsService)
.passwordEncoder(BCryptPasswordEncoder(10))
}
#Throws(Exception::class)
override fun configure(http: HttpSecurity) {
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
http.addFilterAfter(secretAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
http
.authorizeRequests()
.antMatchers("/oauth_login")
.permitAll()
.antMatchers("/accounts/password")
.permitAll()
.anyRequest()
.authenticated()
.permitAll()
.and()
.logout()
.logoutUrl("/logoutconfirm")
.logoutSuccessUrl("/oauth_login")
.invalidateHttpSession(true)
.and()
.oauth2Login().loginPage("/oauth_login")
.authorizationEndpoint()
.baseUri("/oauth2/authorize-client")
.authorizationRequestRepository(authorizationRequestRepository())
.authorizationRequestResolver(CustomAuthorizationRequestResolver(inMemoryClientRegistrationRepository, "/oauth2/authorize-client"))
.and()
.tokenEndpoint()
.accessTokenResponseClient(accessTokenResponseClient())
.and()
.defaultSuccessUrl("/loginSuccess")
.failureUrl("/loginFailure")
.addObjectPostProcessor(object : ObjectPostProcessor<Any> {
override fun <O : Any> postProcess(obj: O) = when (obj) {
is OAuth2LoginAuthenticationProvider -> CustomOAuth2LoginAuthenticationProvider(obj) as O
is LoginUrlAuthenticationEntryPoint -> customizeLoginUrlAuthenticationEntryPoint(obj) as O
else -> obj
}
})
}
This is the ResourceServerConfig I want to add:
#Configuration
#EnableResourceServer
#Order(2)
class ResourceServerConfig(
private val defaultTokenServices: DefaultTokenServices
) : ResourceServerConfigurerAdapter() {
override fun configure(config: ResourceServerSecurityConfigurer) {
config.tokenServices(tokenServices())
}
override fun configure(http: HttpSecurity) {
http.authorizeRequests().anyRequest().authenticated()
}
fun tokenStore() = JwtTokenStore(accessTokenConverter())
fun accessTokenConverter(): JwtAccessTokenConverter {
val converter = object : JwtAccessTokenConverter() {
override fun decode(token: String) = if (token.isCorrectJwtToken(token)) {
super.decode(token)
} else {
mapOf()
}
}
val keyStoreKeyFactory = KeyStoreKeyFactory(ClassPathResource("mykeys.jks"),
"mykeys".toCharArray())
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mykeys"))
return converter
}
fun tokenServices() = DefaultTokenServices().apply {
setTokenStore(tokenStore())
setSupportRefreshToken(true)
}
This is the secured endpoint I want to be able to access with a valid token:
#PreAuthorize("hasAuthority('ROLE_USER')")
#PostMapping("/accounts/password")
fun updatePassword(#RequestBody newPassword: JsonWrappedValue<String>): Boolean {
// Update password
}
I found a solution to my problem.
I merged all configs into a single file so the security config looks like that
#EnableAuthorizationServer
#EnableResourceServer
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#PropertySource("application.properties")
class SecurityConfig(
private val accountRepository: AccountRepository,
private val userDetailsService: CustomUserDetailsService,
private val inMemoryClientRegistrationRepository: InMemoryClientRegistrationRepository,
private val secretAuthenticationFilter: SecretAuthenticationFilter
) : AuthorizationServerConfigurer, ResourceServerConfigurer, WebSecurityConfigurerAdapter()
This made me able to finally reach my endpoint with a token but destroyed my social login.
Then I had to fix the configure(http: HttpSecurity) method as the ResourceServerConfig's default implementation sets the SessionCreationPolicy to SessionCreationPolicy.Never.
This caused my social login to get destroyed because the request parameters containing redirectUri and so on could not be restored. After adding
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
to configure(http: HTTPSecurity) the complete configuration works well.

Load different authentication provider for certain url

I want to load different authentication providers for different urls. For example, if I have url matching "/foo/something", then load FooProvider and for "bar/something" -> BarProvider. The problem is that when I hit "bar/something" url, the sessionScope parameter (I pass it in constructor of provider) is still "foo". That means, FooProvider is loaded, but this is not what I expect.
Is there something I'm missing? Thanks in advance.
abstract class TokenAuthenticationProvider (
protected val sessionScope: SessionScope
) : AuthenticationProvider {
private fun authenticateToken(authentication: TokenAuthentication): Authentication {
println("sessionScope $sessionScope")
}
}
#Component
class FooAuthenticationProvider : TokenAuthenticationProvider (sessionScope = SessionScope.Foo)
#Component
class BarAuthenticationProvider : TokenAuthenticationProvider (sessionScope = SessionScope.Bar)
#Configuration
#EnableWebSecurity
class WebSecurityConfiguration #Autowired constructor(
private val fooProvider: FooProvider,
private val barProvider: BarProvider,
private val authFilter: AuthFilter,
private val corsFilter: CustomCorsFilter
) : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
...
http.authorizeRequests()
.antMatchers("foo/**")
.fullyAuthenticated()
.and()
.authenticationProvider(fooProvider)
http.authorizeRequests()
.antMatchers("bar/**")
.fullyAuthenticated()
.and()
.authenticationProvider(barProvider)
...
}
override fun configure(auth: AuthenticationManagerBuilder) {
auth.authenticationProvider(fooProvider)
auth.authenticationProvider(barProvider)
}
}
You need to configure two WebSecurityConfigurerAdapter and add an antMatcher at the top level of HttpSecurity.
#Configuration
#Order(1)
class FooWebSecurityConfiguration(val provider: FooProvider) : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http.antMatcher("/foo/**")
.authorizeRequests()
.antMatchers("/foo/**")
.fullyAuthenticated()
.and()
.authenticationProvider(provider)
}
}
#Configuration
#Order(2)
class BarWebSecurityConfiguration(val provider: BarProvider) : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http.antMatcher("/bar/**")
.authorizeRequests()
.antMatchers("/bar/**")
.fullyAuthenticated()
.and()
.authenticationProvider(provider)
}
}

Not able to recognize user ROLE when redirecting page using Spring Security

I am working on my project with Spring security and Thymeleaf. I have basic Spring Security integration.
SecurityConfig.java
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private DataSource dataSource;
#Autowired
public void configureGlobal (AuthenticationManagerBuilder auth) throws Exception
{
auth
.jdbcAuthentication()
.dataSource(dataSource);
}
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/success", true)
.and()
.httpBasic();
}
}
SecurityWebApplicationInitializer.java
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer
{
public SecurityWebApplicationInitializer(){
super(SecurityConfig.class);
}
}
Controller.java
#Controller
public class HomeController {
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String loginPage(Model model) {
return "login";
}
#RequestMapping("/success")
public String loginPageRedirect(HttpServletRequest httpServletRequest){
if(httpServletRequest.isUserInRole("ROLE_ADMIN")) {
return "index1";
} else if(httpServletRequest.isUserInRole("ROLE_USER")) {
return "index2";
} else {
return "index3";
}
}
}
When I have successful login my user is redirected, but to wrong page. My user has role ROLE_USER but method loginPageRedirect is redirecting him to page index3 when it should be index2. I guess my user role is not recognize. How can I do that? Should I add something as parameter to loginPageRedirect so it recognizes role?
I found solution that works for me.
I edited my loginPageRedirect method like this:
#RequestMapping("/success")
public void loginPageRedirect(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException, ServletException {
String role = authResult.getAuthorities().toString();
if(role.contains("ROLE_ADMIN")){
response.sendRedirect(response.encodeRedirectURL(request.getContextPath() + "/index1"));
}
else if(role.contains("ROLE_USER")) {
response.sendRedirect(response.encodeRedirectURL(request.getContextPath() + "/index2"));
}
}
Hope it helps someone with same issue :)

Resources