Spring Secuirty Oauth 2 - Multiple User Authentication Services

My application provides an oauth2 token service identical to this one presented in the following github project: https://github.com/iainporter/oauth2-provider

It is based on OAuth2 Spring Security.

I introduced 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 another user authentication method (another UserDetailsService), for example:

 <bean id="otherUserService" class="org.example.core.service.LDAPUserServiceImpl" /> 

Unfortunately, I did not find a way to do this in the documentation. At the request level, I would like to distinguish which method (which user service) to use either:

  • request parameter
  • http header (e.g. RealmName)
+5
source share
2 answers

To configure multiple entry points, you must use DelegatingAuthenticationEntryPoint . This means that you can have several authentication methods. The following is 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 a RequestMatcher to select the correct entry point (based on the header / area name):

DBUser Query Connector:

 RequestMatcher dbUserMatcher = new RequestMatcher() { @Override public boolean matches(HttpServletRequest request) { // Logic to identify a DBUser kind of reqeust } }; 

Requset negotiator for LDAP user:

 RequestMatcher ldapMatcher = new RequestMatcher() { @Override public boolean matches(HttpServletRequest request) { // Logic to identify a LDAP kind of reqeust } }; 

Now we need to add these matches and entry points to the DelegatingAuthenticationEntryPoint . At run time, DelegatingAuthenticationEntryPoint selects an entry point and performs authentication based on a connector that returns 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 the 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 Provider Manager:

 @Bean public AuthenticationManager authenticationManager() { return new ProviderManager(Arrays.asList(provider1, provider2); } 
+5
source

I found a different solution than the solution provided by Mithun.

The application context contains a user authentication manager initiated by 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 using another 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 have to do is override the β€œsupports” method, which indicates whether the current authentication provider can 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); } } 

In the end, you need to decrypt the marker. In my case, I extended ResourceOwnerPasswordTokenGranter, which means that it supports "password":

 <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 the TokenRequest object to distinguish the authentication class for instantiation (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 } } 
+2
source

Source: https://habr.com/ru/post/1210956/


All Articles