I have Spring Boot (1.2.1.RELEASE) application that serves OAuth2 (2.0.6.RELEASE) authorization and resource server in one application instance. It uses custom UserDetailsService
implementation that makes use of MongoTemplate
to search users in MongoDB. Authentication with grant_type=password
on /oauth/token
works like a charm, as well as authorization with Authorization: Bearer {token}
header while calling for specific resources.
Now I want to add simple OAuth confirm dialog to the server, so I can authenticate and authorize e.g. Swagger UI calls in api-docs for protected resources. Here is what I did so far:
@Configuration
@SessionAttributes("authorizationRequest")
class OAuth2ServerConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.addViewController("/oauth/confirm_access").setViewName("authorize");
}
@Configuration
@Order(2)
protected static class LoginConfig extends WebSecurityConfigurerAdapter implements ApplicationEventPublisherAware {
@Autowired
UserDetailsService userDetailsService
@Autowired
PasswordEncoder passwordEncoder
ApplicationEventPublisher applicationEventPublisher
@Bean
DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider()
provider.passwordEncoder = passwordEncoder
provider.userDetailsService = userDetailsService
return provider
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManagerBean())
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder())
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
//return super.authenticationManagerBean()
ProviderManager providerManager = new ProviderManager([daoAuthenticationProvider()], super.authenticationManagerBean())
providerManager.setAuthenticationEventPublisher(new DefaultAuthenticationEventPublisher(applicationEventPublisher))
return providerManager
}
@Bean
public PasswordEncoder passwordEncoder() {
new BCryptPasswordEncoder(5)
}
}
@Configuration
@EnableResourceServer
protected static class ResourceServer extends ResourceServerConfigurerAdapter {
@Value('${oauth.resourceId}')
private String resourceId
@Autowired
@Qualifier('authenticationManagerBean')
private AuthenticationManager authenticationManager
@Override
public void configure(HttpSecurity http) throws Exception {
http.setSharedObject(AuthenticationManager.class, authenticationManager)
http.csrf().disable()
http.httpBasic().disable()
http.formLogin().loginPage("/login").permitAll()
//http.authenticationProvider(daoAuthenticationProvider())
http.anonymous().and()
.authorizeRequests()
.antMatchers('/login/**').permitAll()
.antMatchers('/uaa/register/**').permitAll()
.antMatchers('/uaa/activate/**').permitAll()
.antMatchers('/uaa/password/**').permitAll()
.antMatchers('/uaa/account/**').hasAuthority('ADMIN')
.antMatchers('/api-docs/**').permitAll()
.antMatchers('/admin/**').hasAuthority('SUPERADMIN')
.anyRequest().authenticated()
//http.sessionManagement().sessionCreationPolicy(STATELESS)
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(resourceId)
resources.authenticationManager(authenticationManager)
}
}
@Configuration
@EnableAuthorizationServer
protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Value('${oauth.clientId}')
private String clientId
@Value('${oauth.secret:}')
private String secret
@Value('${oauth.resourceId}')
private String resourceId
@Autowired
@Qualifier('authenticationManagerBean')
private AuthenticationManager authenticationManager
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
return new JwtAccessTokenConverter();
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.checkTokenAccess("permitAll()")
oauthServer.allowFormAuthenticationForClients()
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.accessTokenConverter(accessTokenConverter())
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(clientId)
.secret(secret)
.authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
.authorities("USER", "ADMIN")
.scopes("read", "write", "trust")
.resourceIds(resourceId)
}
}
}
Main problem is that I cannot make both (web login form and OAuth2 authorization token in header) running. If ResourceServer
gets higher priority, then OAuth2 token authorization works, but I can't login using web form. On the other hand if I set the higher priority to LoginConfig
class, then OAuth2 token authorization stops working.
I figured out that in that case the problem is caused by non-registered OAuth2AuthenticationProcessingFilter
. I tried to registered it manually in ResourceServer.configure(HttpSecurity http)
method, but it didn't work - I could see the filter on FilterChain list, but it didn't get triggered. It wasn't good way to fix it, because there is a lot of other magic done during the ResourceServer initialization so I moved to the second case.
In that case the main problem is that by default UsernamePasswordAuthenticationFilter
cannot find a properly configured AuthenticationProvider
instance (in ProviderManager
). When I tried to add it manually by:
http.authenticationProvide(daoAuthenticationProvider())
it gets one, but in this case there is no AuthenticationEventPublisher
defined and successful authentication cannot be published to other components. And in fact in the next iteration it gets replaced by AnonymousAuthenticationToken
. That's why I tried to define manually AuthenticationManager
instance with DaoAuthenticationProvider
inside:
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
//return super.authenticationManagerBean()
ProviderManager providerManager = new ProviderManager([daoAuthenticationProvider()], super.authenticationManagerBean())
providerManager.setAuthenticationEventPublisher(new DefaultAuthenticationEventPublisher(applicationEventPublisher))
return providerManager
}
I thought it will work, but there is a different problem with providing AuthenticationManager
instance to registered filters. It turns out that each filter has authenticationManager
injected manually using sharedObjects
component:
authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
The problem here is that you are not guaranteed to have a proper instance set, because there is a simple HashMap (check it on GitHub) used to store specific shared object and it can be change any time. I tried to set it in:
http.setSharedObject(AuthenticationManager.class, authenticationManager)
but before I get to the place where it is being read, it's already replaced by default implementation. I checked it with the debugger and it looks like that for each new filter there is a new instance of authentication manager.
My question is: am I doing it correctly? How can I set up authorization server with the resources server integrated in one application with login form (OAuth2 dialog) working? Maybe it can be done in a different and much easier way. I would be thankful for any help.
The Oracle API Gateway can be used as an Authorization Server and as a Resource Server. An Authorization Server issues tokens to client applications on behalf of a Resource Owner for use in authenticating subsequent API calls to the Resource Server.
What Is a Resource Server? In the context of OAuth 2.0, a resource server is an application that protects resources via OAuth tokens. These tokens are issued by an authorization server, typically to a client application. The job of the resource server is to validate the token before serving a resource to the client.
Spring security secures all HTTP endpoints by default. A user has to login in a default HTTP form. To enable Spring Boot security, we add spring-boot-starter-security to the dependencies.
OAuth (Open Authorization) is an open standard for token-based authentication and authorization which is used to provide single sign-on (SSO). OAuth allows an end user's account information to be used by third-party services, such as Facebook, without exposing the user's password.
Here is the solution to the problem. Take a look at this exemplary Groovy
class:
@Configuration
@EnableResourceServer
class ResourceServer extends ResourceServerConfigurerAdapter {
@Value('${oauth.resourceId}')
private String resourceId
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
http.httpBasic().disable()
http.requestMatchers().antMatchers('/admin/**', '/uaa/**')
.and().authorizeRequests()
.antMatchers('/uaa/authenticated/**').authenticated()
.antMatchers('/uaa/register/**').permitAll()
.antMatchers('/uaa/activate/**').permitAll()
.antMatchers('/uaa/password/**').permitAll()
.antMatchers('/uaa/auth/**').permitAll()
.antMatchers('/uaa/account/**').hasAuthority('ADMIN')
.antMatchers('/admin/**').hasAuthority('ADMIN')
.anyRequest().authenticated()
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(resourceId);
}
}
Basically, to run OAuth2.0 authentication parallel with web form authentication, you have to put
http.requestMatchers().antMatchers('/path/1/**', '/path/2/**')
to configuration class. My previous configuration missed this important part so only OAuth2.0 took a part in authentication process.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With