I would have one question regarding the configuration of spring-security-oauth2 2.0.7 please. I am doing the Authentication using LDAP via a GlobalAuthenticationConfigurerAdapter:
@SpringBootApplication
@Controller
@SessionAttributes("authorizationRequest")
public class AuthorizationServer extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(AuthorizationServer.class, args);
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.addViewController("/oauth/confirm_access").setViewName("authorize");
}
@Configuration
public static class JwtConfiguration {
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory(
new ClassPathResource("keystore.jks"), "foobar".toCharArray())
.getKeyPair("test");
converter.setKeyPair(keyPair);
return converter;
}
@Bean
public JwtTokenStore jwtTokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
}
@Configuration
@EnableAuthorizationServer
public static class OAuth2Config extends AuthorizationServerConfigurerAdapter implements EnvironmentAware {
private static final String ENV_OAUTH = "authentication.oauth.";
private static final String PROP_CLIENTID = "clientid";
private static final String PROP_SECRET = "secret";
private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";
private RelaxedPropertyResolver propertyResolver;
@Inject
private AuthenticationManager authenticationManager;
@Inject
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Inject
private JwtTokenStore jwtTokenStore;
@Inject
private UserDetailsService userDetailsService;
@Override
public void setEnvironment(Environment environment) {
this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(jwtTokenStore);
tokenServices.setAuthenticationManager(authenticationManager);
return tokenServices;
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager).tokenStore(jwtTokenStore).accessTokenConverter(
jwtAccessTokenConverter).userDetailsService(userDetailsService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess(
"isAuthenticated()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(propertyResolver.getProperty(PROP_CLIENTID))
.scopes("read", "write")
.authorities(AuthoritiesConstants.ADMIN, AuthoritiesConstants.USER)
.authorizedGrantTypes("authorization_code", "refresh_token", "password")
.secret(propertyResolver.getProperty(PROP_SECRET))
.accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800));
}
}
@Configuration
@Order(-10)
protected static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().loginPage("/login").permitAll()
.and()
.requestMatchers().antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access")
.and()
.authorizeRequests().anyRequest().authenticated();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
}
@Configuration
protected static class AuthenticationConfiguration extends
GlobalAuthenticationConfigurerAdapter {
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.contextSource().ldif("classpath:test-server.ldif");
}
}
}
While the refresh token works fine with the release 2.0.6 of spring-security-oauth2, it does not work anymore with the version 2.0.7.
As read here, one should set the AuthenticationManager
to be used when trying to get a new access token during the refresh.
As far as I understand, this has something to do with the following change of spring-security-oauth2.
I unfortunately did not manage to set it up properly.
org.springframework.security.oauth2.provider.token.DefaultTokenServices#setAuthenticationManager
is called and gets an AuthenticationManager
injected. I an not sure I understand how the LdapUserDetailsService
is then going to be injected. The only thing I see is that the PreAuthenticatedAuthenticationProvider
is going to be called while trying to re-authenticate the user during the token refresh call.
Can anyone advise me on how to do it please?
ps: The exception I am getting is the following:
p.PreAuthenticatedAuthenticationProvider : PreAuthenticated authentication request: org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken@5775: Principal: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@441d5545: Principal: bob; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER
o.s.s.o.provider.endpoint.TokenEndpoint : Handling error: IllegalStateException, UserDetailsService is required.
What you need for the OAuth piece is to create an LdapUserDetailsService
with the same query as you authenticator and inject it into the AuthorizationServerEndpointsConfigurer
. I don't think there's any support for creating a UserDetailService
in @Configuration
style (might be worth opening a ticket for that in JIRA), but it looks like you can do it in XML.
I had a similar issue when I was implementing a a OAuth2 server with JWT tokens with a custom AuthenticationProvider
instead of a UserDetailsService
implementation to solve login authentications.
But lately I found that the error Spring raises is correct if you want the refresh_token
working correctly. For an AuthenticationProvider
implementation is impossible to refresh a token with a refresh_token
, because in that kind of implementation you have to resolve if the password is correct, but the refresh token doesn't have that information. However, UserDetailsService
is agnostic of the password.
The version 2.0.6 of spring-security-oauth2
works because never checks the user grants, just checks if the refresh token is valid (signed with the private key), but, if the user was deleted from the system after a first login, with a refresh token the deleted user will have infinite time access to your system, that is a big security issue.
Take a look to the issue I reported with this: https://github.com/spring-projects/spring-security-oauth/issues/813
As advised by Dave Syer, I created a custom LdapUserDetailsService
.
The working solution can be found under the following tag.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<context:property-placeholder location="application.yml"/>
<bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<constructor-arg value="${authentication.ldap.url}" />
</bean>
<bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<constructor-arg index="0" value="${authentication.ldap.userSearchBase}" />
<constructor-arg index="1" value="uid={0}" />
<constructor-arg index="2" ref="contextSource"/>
</bean>
<bean id="ldapAuthoritiesPopulator" class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
<constructor-arg index="0" ref="contextSource"/>
<constructor-arg index="1" value="${authentication.ldap.groupSearchBase}"/>
<property name="groupSearchFilter" value="${authentication.ldap.groupSearchFilter}"/>
</bean>
<bean id="myUserDetailsService"
class="org.springframework.security.ldap.userdetails.LdapUserDetailsService">
<constructor-arg index="0" ref="userSearch"/>
<constructor-arg index="1" ref="ldapAuthoritiesPopulator"/>
</bean>
</beans>
authentication:
ldap:
url: ldap://127.0.0.1:33389/dc=springframework,dc=org
userSearchBase:
userDnPatterns: uid={0},ou=people
groupSearchBase: ou=groups
groupSearchFilter: (uniqueMember={0})
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