Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring boot OAuth2 custom roles on Resource server

TL;DR: How to assign users custom roles/authorities on Resource server side (that means without JWT) based on their access_token?

The whole story: I have a working Auth server and a client (which is SPA), which can obtain access_token from the Auth server. With that access_token the client can request data on my Resource server (which is separated from Auth server). The Resource server can get username from Auth server using the access_token.

I can access the username in code by injection Authentication object into method like this:

@RequestMapping("/ping")
fun pingPong(auth: Authentication): String = "pong, " + auth.name

My question is how to add my custom roles or authorities (auth.authorities - there is only USER_ROLE) to this object which would be managed on the Resource server, not Auth server, based on the username.

I have tried several ways to do it but none has helped. The most promising was this:

@Configuration
@EnableWebSecurity
@EnableResourceServer
class ResourceServerConfigurer(val userDetailsService: MyUserDetailsService) : ResourceServerConfigurerAdapter() {

    override fun configure(http: HttpSecurity) {
        http.userDetailsService(userDetailsService) // userDetailsService is autowired
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().authorizeRequests()
                .antMatchers("/", "/index.html").permitAll()
                .anyRequest().authenticated()
    }
}

And my custom UserDetailsService:

@Service
class UserDetailsService : org.springframework.security.core.userdetails.UserDetailsService {

    override fun loadUserByUsername(username: String): UserDetails {
        return org.springframework.security.core.userdetails.User(username, "password", getAuthorities(username))

    }

    private fun getAuthorities(user: String): Set<GrantedAuthority> {
        val authorities = HashSet<GrantedAuthority>()
        authorities.addAll(listOf(
                SimpleGrantedAuthority("ROLE_ONE"),  //let's grant some roles to everyone
                SimpleGrantedAuthority("ROLE_TWO")))
        return authorities
    }
}

Everything worked (I mean I was successfully authenticated) except that I still had only ROLE_USER. Next what I tried was providing a custom implementation of AbstractUserDetailsAuthenticationProvider:

@Bean
fun authenticationProvider(): AbstractUserDetailsAuthenticationProvider {
    return object : AbstractUserDetailsAuthenticationProvider() {
        override fun retrieveUser(username: String, authentication: UsernamePasswordAuthenticationToken): UserDetails {
            return User(username, "password", getAuthorities(username))

        }

        private fun getAuthorities(user: String): Set<GrantedAuthority> {
            val authorities = HashSet<GrantedAuthority>()
            authorities.addAll(listOf(
                    SimpleGrantedAuthority("ROLE_ONE"),
                    SimpleGrantedAuthority("ROLE_TWO")))
            return authorities
        }

        override fun additionalAuthenticationChecks(userDetails: UserDetails, authentication: UsernamePasswordAuthenticationToken?) {
        }
    }
}

with same result, only the ROLE_USER was present.

I would really appreciate any ideas from you guys how add some roles to the Authentication object after the access_token was validated and username obtained from Auth server.

like image 980
hawi Avatar asked Dec 03 '17 12:12

hawi


1 Answers

Solution by OP.

First of all I needed to provide custom PrincipalExtractor and AuthoritiesExtractor implementations. But to make Spring use them it is necessary in configuration NOT to use security.oauth2.resource.token-info-uri but security.oauth2.resource.user-info-uri instead (I really didn't expect this to be one of the roots of my problem).
Finally the security config must be done in ResourceServerConfigurerAdapter, not in WebSecurityConfigurerAdapter.

The final code looks like this:

    @SpringBootApplication
    @RestController
    class MyApplication {
    
        @RequestMapping("/ping")
        fun pingPong(user: Authentication): String {
            return "pong, " + user.name + " - " + user.authorities.joinToString()
        }
    }
    
    @Configuration
    @EnableWebSecurity
    @EnableResourceServer
    class ResourceServerConfigurer : ResourceServerConfigurerAdapter() {
    
        override fun configure(http: HttpSecurity) {
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and().authorizeRequests()
                    .antMatchers("/", "/index.html").permitAll()
                    .anyRequest().authenticated()
        }
    
        @Bean
        fun principalExtractor() = PrincipalExtractor {
            return@PrincipalExtractor it["name"]
        }
    
        @Bean
        fun authoritiesExtractor() = AuthoritiesExtractor {
            return@AuthoritiesExtractor AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ONE,ROLE_TWO")
        }
    }
    
    fun main(args: Array<String>) {
        SpringApplication.run(MyApplication::class.java, *args)
    }
like image 96
2 revs, 2 users 98% Avatar answered Oct 16 '22 19:10

2 revs, 2 users 98%