Reputation: 1635
My application provides oauth2 token service identical to this one provided in the following github project: https://github.com/iainporter/oauth2-provider
It is based on Spring Security OAuth2.
I provided my custom implementation of UserDetailsService:
<bean id="userService" class="org.example.core.service.DBUserServiceImpl" />
and the following user authentication manager:
<sec:authentication-manager alias="userAuthenticationManager">
<sec:authentication-provider user-service-ref="userService">
<sec:password-encoder ref="passwordEncoder" />
</sec:authentication-provider>
</sec:authentication-manager>
Now I would like to provide other method of user authentication (other UserDetailsService), for example:
<bean id="otherUserService" class="org.example.core.service.LDAPUserServiceImpl" />
Unfortunately I didn't find a way how to do it in documentation. On the request level I would like to distinguish which method (which user service) to use by either:
Upvotes: 5
Views: 11196
Reputation: 1635
I found different solution than solution provided by Mithun.
Application context contains user authentication manager initiated with different authentication providers:
<sec:authentication-manager alias="userAuthenticationManager">
<sec:authentication-provider ref="customerAuthProvider" />
<sec:authentication-provider ref="adminAuthProvider" />
</sec:authentication-manager>
where customerAuthProvider and adminAuthProvider are extensions of DaoAuthenticationProvider with different userDetails Service:
<bean id="customerAuthProvider" class="org.example.security.authentication.provider.CustomerAuthenticationProvider">
<property name="userDetailsService" ref="userService" />
<property name="passwordEncoder" ref="passwordEncoder" />
</bean>
<bean id="adminAuthProvider" class="org.example.security.authentication.provider.AdminAuthenticationProvider">
<property name="userDetailsService" ref="otherUserService" />
</bean>
All you need to do is to override "supports" method that indicates whether current authentication provider is able to handle specific authentication:
public class CustomerAuthenticationProvider extends DaoAuthenticationProvider {
@Override
public boolean supports ( Class<?> authentication ) {
return CustomerUsernamePasswordAuthenticationToken.isAssignableFrom(authentication);
}
}
public class AdminAuthenticationProvider extends DaoAuthenticationProvider {
@Override
public boolean supports ( Class<?> authentication ) {
return AdminUsernamePasswordAuthenticationToken.isAssignableFrom(authentication);
}
}
At the end you need to extend token granter. In my case I extended ResourceOwnerPasswordTokenGranter which means that it supports "password" grant:
<oauth:authorization-server client-details-service-ref="client-details-service" token-services-ref="tokenServices">
<oauth:refresh-token/>
<oauth:custom-grant token-granter-ref="customPasswordTokenGranter"/>
</oauth:authorization-server>
You can use TokenRequest object to distinguish which Authentication class to instantiate (AdminUsernamePasswordAuthenticationToken or CustomerUsernamePasswordAuthenticationToken)
public class CustomResourceOwnerPasswordTokenGranter extends ResourceOwnerPasswordTokenGranter {
protected OAuth2Authentication getOAuth2Authentication ( ClientDetails client, TokenRequest tokenRequest ) {
Map parameters = tokenRequest.getRequestParameters();
String username = (String) parameters.get("username");
String password = (String) parameters.get("password");
String realmName = (String) parameters.get("realm_name");
Authentication userAuth = createAuthentication(username, password, realmName);
try {
userAuth = this.authenticationManager.authenticate(userAuth);
} catch ( AccountStatusException ase ) {
throw new InvalidGrantException(ase.getMessage());
} catch ( BadCredentialsException e ) {
throw new InvalidGrantException(e.getMessage());
}
if ( ( userAuth == null ) || ( ! ( userAuth.isAuthenticated() ) ) ) {
throw new InvalidGrantException("Could not authenticate user: " + username);
}
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
private Authentication createAuthentication ( String username, String password, String realmName ) throws InvalidGrantException {
// TODO: decide basing on realm name
}
}
Upvotes: 3
Reputation: 8067
You need to use DelegatingAuthenticationEntryPoint
to configure multiple entry points. Which means you can have multiple ways of authenticating. Following is the sample code:
DBUser entry point:
public class DBUserAuthencticationEntryPoint extends BasicAuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
super.commence(request, response, authException);
}
}
LDAP entry point:
public class LDAPAuthencticationEntryPoint extends BasicAuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
super.commence(request, response, authException);
}
}
Then you need to create RequestMatcher
s to pick the correct entry point (based on header/realm name):
DBUser request matcher:
RequestMatcher dbUserMatcher = new RequestMatcher() {
@Override
public boolean matches(HttpServletRequest request) {
// Logic to identify a DBUser kind of reqeust
}
};
LDAP user requset matcher:
RequestMatcher ldapMatcher = new RequestMatcher() {
@Override
public boolean matches(HttpServletRequest request) {
// Logic to identify a LDAP kind of reqeust
}
};
Now we need to add these matchers and entry points to DelegatingAuthenticationEntryPoint
. In runtime DelegatingAuthenticationEntryPoint
picks up the entry point and does the authentication based on the matcher which return true
.
DBUserAuthencticationEntryPoint dbUserEntryPoint = new DBUserAuthencticationEntryPoint();
LDAPAuthencticationEntryPoint ldapEntryPoint = new LDAPAuthencticationEntryPoint();
LinkedHashMap<RequestMatcher,AuthenticationEntryPoint> entryPoints = new LinkedHashMap<RequestMatcher,AuthenticationEntryPoint>();
entryPoints.put(ldapMatcher, ldapEntryPoint);
entryPoints.put(dbUserMatcher, dbUserEntryPoint);
DelegatingAuthenticationEntryPoint delegatingAuthenticationEntryPoint = new DelegatingAuthenticationEntryPoint(entryPoints);
Now map the DelegatingAuthenticationEntryPoint
to HttpSecurity
in the configure()
method:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.
authorizeRequests().
regexMatchers("/login.*").permitAll().
regexMatchers("/api.*").fullyAuthenticated().
and().
formLogin().loginPage("/login").
and().
exceptionHandling().authenticationEntryPoint(delegatingAuthenticationEntryPoint);
}
}
Configure the provider manager:
@Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(provider1, provider2);
}
Upvotes: 6