我的应用程序提供的oauth2令牌服务与以下github项目中提供的服务相同:https://github.com/iainporter/oauth2-provider
它基于Spring Security OAuth2。
我提供了UserDetailsService的自定义实现:
<bean id="userService" class="org.example.core.service.DBUserServiceImpl" />
以及以下用户身份验证管理器:
<sec:authentication-manager alias="userAuthenticationManager">
<sec:authentication-provider user-service-ref="userService">
<sec:password-encoder ref="passwordEncoder" />
</sec:authentication-provider>
</sec:authentication-manager>
现在我想提供其他用户身份验证方法(其他UserDetailsService),例如:
<bean id="otherUserService" class="org.example.core.service.LDAPUserServiceImpl" />
不幸的是我没有找到如何在文档中做到这一点的方法。在请求级别,我想区分哪个方法(哪个用户服务)使用:
答案 0 :(得分:6)
您需要使用DelegatingAuthenticationEntryPoint
配置多个入口点。这意味着您可以使用多种方式进行身份验证。以下是示例代码:
DBUser入口点:
public class DBUserAuthencticationEntryPoint extends BasicAuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
super.commence(request, response, authException);
}
}
LDAP入口点:
public class LDAPAuthencticationEntryPoint extends BasicAuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
super.commence(request, response, authException);
}
}
然后你需要创建RequestMatcher
来选择正确的入口点(基于标题/域名):
DBUser请求匹配器:
RequestMatcher dbUserMatcher = new RequestMatcher() {
@Override
public boolean matches(HttpServletRequest request) {
// Logic to identify a DBUser kind of reqeust
}
};
LDAP用户请求匹配器:
RequestMatcher ldapMatcher = new RequestMatcher() {
@Override
public boolean matches(HttpServletRequest request) {
// Logic to identify a LDAP kind of reqeust
}
};
现在我们需要将这些匹配器和入口点添加到DelegatingAuthenticationEntryPoint
。在运行时DelegatingAuthenticationEntryPoint
选择入口点,并根据返回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);
现在使用DelegatingAuthenticationEntryPoint
方法将HttpSecurity
映射到configure()
:
@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);
}
}
配置提供程序管理器:
@Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(provider1, provider2);
}
答案 1 :(得分:2)
我找到了与Mithun提供的解决方案不同的解决方案。
应用程序上下文包含由不同身份验证提供程序启动的用户身份验证管理器:
<sec:authentication-manager alias="userAuthenticationManager">
<sec:authentication-provider ref="customerAuthProvider" />
<sec:authentication-provider ref="adminAuthProvider" />
</sec:authentication-manager>
其中customerAuthProvider和adminAuthProvider是具有不同userDetails服务的DaoAuthenticationProvider的扩展:
<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>
您需要做的就是覆盖&#34;支持&#34;指示当前身份验证提供程序是否能够处理特定身份验证的方法:
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);
}
}
最后,您需要扩展令牌机。就我而言,我扩展了ResourceOwnerPasswordTokenGranter,这意味着它支持&#34;密码&#34;授权:
<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>
您可以使用TokenRequest对象来区分要实例化的Authentication类(AdminUsernamePasswordAuthenticationToken或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
}
}