Authenticate with the API key in the header for each request when calling Web API with Spring Security.
An example in Java is posted in a personal blog post [Spring] Authorizing Web API using Spring Security. This article also includes an example of permission checking by @PreAuthorize.
Set the session policy to stateless with the configure method of WebSecurityConfigurerAdapter. Specifically, set SessionCreationPolicy.STATELESS as follows.
@Configuration
@EnableWebSecurity
class SecurityConfig: WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity?) {
http
?.authorizeRequests()
?.antMatchers("/hello/**")?.hasAnyRole("USER", "ADMIN")
?.antMatchers("/admin/**")?.hasRole("ADMIN")
?.anyRequest()
?.authenticated()
?.and()
?.addFilter(preAuthenticatedProcessingFilter())
?.exceptionHandling()
?.authenticationEntryPoint(http401UnauthorizedEntryPoint())
//Authenticate for each request
http?.sessionManagement()?.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
....
Use Spring Security's Pre-Authentication (https://docs.spring.io/spring-security/site/docs/4.2.x-SNAPSHOT/reference/html/preauth.html). Create a filter that uses AbstractPreAuthenticatedProcessingFilter and AuthenticationUserDetailsService to get the API key from the request header and a service that authenticates with the API key.
Create a class that inherits AbstractPreAuthenticatedProcessingFilter and create a process to extract API key in request header in getPreAuthenticatedCredentials method.
class MyPreAuthenticatedProcessingFilter: AbstractPreAuthenticatedProcessingFilter() {
//Get API key
override fun getPreAuthenticatedCredentials(request: HttpServletRequest?): Any {
return request?.getHeader("X-Auth-Token") ?: ""
}
override fun getPreAuthenticatedPrincipal(request: HttpServletRequest?): Any {
return ""
}
}
Create a class that inherits AuthenticationUserDetailsService as shown below, and create an authentication process using the acquired API key. Specify PreAuthenticatedAuthenticationToken for the type parameter. Here, the appropriate process is written for testing, but the process to verify the API key using an external API or DB is written.
class MyAuthenticationUserDetailService: AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {
override fun loadUserDetails(token: PreAuthenticatedAuthenticationToken?): UserDetails {
//API key obtained by MyPreAuthenticatedProcessingFilter
val credential = token?.credentials
if (credential.toString() == "") {
throw UsernameNotFoundException("User not found")
}
return when (credential) {
"token1" -> User("user", "", AuthorityUtils.createAuthorityList("ROLE_USER"))
"token2" -> User("admin", "", AuthorityUtils.createAuthorityList("ROLE_ADMIN"))
else -> User("user", "", AuthorityUtils.createAuthorityList("ROLE_INVALID") )
}
}
}
Add Bean definition as follows so that the created filter and service will be DI.
@Configuration
@EnableWebSecurity
class SecurityConfig: WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity?) {
http
?.authorizeRequests()
?.antMatchers("/hello/**")?.hasAnyRole("USER", "ADMIN")
?.antMatchers("/admin/**")?.hasRole("ADMIN")
?.anyRequest()
?.authenticated()
?.and()
?.addFilter(preAuthenticatedProcessingFilter())
http?.sessionManagement()?.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
//service
@Bean
fun authenticationUserDetailsService(): AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {
return MyAuthenticationUserDetailService()
}
//Filter registration
@Bean
fun preAuthenticatedAuthenticationProvider(): PreAuthenticatedAuthenticationProvider {
return PreAuthenticatedAuthenticationProvider().apply {
setPreAuthenticatedUserDetailsService(authenticationUserDetailsService())
setUserDetailsChecker(AccountStatusUserDetailsChecker())
}
}
//filter
@Bean
fun preAuthenticatedProcessingFilter(): AbstractPreAuthenticatedProcessingFilter {
return MyPreAuthenticatedProcessingFilter().apply {
setAuthenticationManager(authenticationManager())
}
}
}
Recommended Posts