使用java配置在单个应用程序中的多个身份validation机制

目前我的应用程序中有一个身份validation机制,即使用LDAP进行身份validation和授权。 我的安全配置如下所示

@Configuration @EnableWebMvcSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .anyRequest().fullyAuthenticated() .and() .httpBasic(); } @Configuration protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter { @Value("${ldap-${env}.manager.dn}") private String managerDn; @Value("${ldap-${env}.manager.pass}") private String managerPass; @Value("${ldap-${env}.server.url}") private String url; @Value("${ldap.password.attribute:userPassword}") private String passwordAttr; @Override public void init(AuthenticationManagerBuilder auth) throws Exception { auth.ldapAuthentication().userDnPatterns("uid={0},ou=people").groupSearchBase("ou=groups") .groupSearchFilter("(member={0})").userSearchBase("ou=people").userSearchFilter("(uid={0})") .userDetailsContextMapper(new CustomLdapPersonContextMapper()) // .passwordCompare() // .passwordAttribute(passwordAttr) // .passwordEncoder(new PlaintextPasswordEncoder()) // .and() .contextSource().managerDn(managerDn).managerPassword(managerPass).url(url); } } } 

在某些情况下,用户可能会使用会话令牌进入会话令牌,该会话令牌可以从会话密钥服务器进行身份validation,并且有效令牌会返回用户名,然后可以使用该用户名从LDAP为该用户加载身份validation信息。 所以我的第二个身份validation机制应该首先发生,如果http头中存在会话令牌,它应该执行令牌身份validation然后执行ldap查找,如果没有会话令牌,它应该落入当前的身份validation机制。 如何添加第二层身份validation。

在使用纯java配置时,我花了很长时间围绕spring-security。 让这个工作有一些步骤。 它应该是这些方面的东西。 基本流程如下:

  • 创建自定义筛选器以检查特定授权信息的请求

  • 每个filter返回null(如果未找到该类型的授权)或自定义AbstractAuthenticationToken

  • 如果filter返回一个标记,那么将调用每个AuthenticationProvider的支持(类)方法,该标记返回true | false,如果它应该尝试认证

  • 然后将在支持令牌的AuthenticationProvider上调用attemptAuthentication。 在这里,您可以执行任何服务调用来validation用户身份。 然后,您可以抛出LoginException或调用authentication.setAuthenticated(true)并返回令牌以进行成功的身份validation。

我一直在使用此设置支持各种身份validation方法(签名请求,用户名/密码,oauth等),并且它运行良好。

您还可以将AuthenticationSuccessHandler和AuthenticationFailuersHandler传递给自定义安全筛选器,以提供自定义重定向策略和故障处理。

还要确保在filter的构造函数中设置ant匹配器,以控制filter应用的URL模式。 例如,ldap请求filter可能需要检查任何请求“/ *”,而用户名/密码filter可以在POST上检查到/ login或类似的东西。

示例代码:

1)为您要支持的每种类型的身份validation创建自定义AuthenticationToken

 public class LDAPAuthorizationToken extends AbstractAuthenticationToken { private String token; public LDAPAuthorizationToken( String token ) { super( null ); this.token = token; } public Object getCredentials() { return token; } public Object getPrincipal() { return null; } } public class OTPAuthorizationToken extends UsernamePasswordAuthenticationToken { private String otp; public OTPAuthorizationToken( String username, String password, String otp ) { super( username, password ); this.otp = otp; } public String getOTP() { return otp; } } 

2)为每种类型创建自定义安全筛选器

 public class LDAPAuthorizationFilter extends AbstractAuthenticationProcessingFilter { @Autowired private UserDetailsService userDetailsService; public LDAPAuthorizationFilter() { super( "/*" ); // allow any request to contain an authorization header } public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response ) throws AuthenticationException { if ( request.getHeader( "Authorization" ) == null ) { return null; // no header found, continue on to other security filters } // return a new authentication token to be processed by the authentication provider return new LDAPAuthorizationToken( request.getHeader( "Authorization" ) ); } } public class OTPAuthorizationFilter extends AbstractAuthenticationProcessingFilter { @Autowired private UserDetailsService userDetailsService; public OTPAuthorizationFilter() { super( "/otp_login" ); } public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response ) throws AuthenticationException { if ( request.getParameter( "username" ) == null || request.getParameter( "password" ) == null || request.getParameter( "otp" ) == null ) { return null; } // return a new authentication token to be processed by the authentication provider return new OTPAuthorizationToken( request.getParameter( "username" ), request.getParameter( "password" ), request.getParameter( "otp" ) ); } } 

3)创建自定义AuthenticationProviders

 public class LDAPAuthenticationProvider implements AuthenticationProvider { @Autowired private MyAuthenticationService sampleService; @Override public Authentication authenticate( Authentication authentication ) throws AuthenticationException { LDAPAuthorizationToken auth = (LDAPAuthorizationToken)authentication; String username = sampleService.verifyToken( auth.getCredentials() ); if ( username == null ) { throw new LoginException( "Invalid Token" ); } auth.setAuthenticated( true ); return auth; } @Override public boolean supports( Class authentication ) { if ( authentication.isAssignableFrom( LDAPAuthorizationToken.class ) ) { return true; } return false; } } public class OTPAuthenticationProvider implements AuthenticationProvider { @Autowired private MyAuthenticationService sampleService; @Override public Authentication authenticate( Authentication authentication ) throws AuthenticationException { OTPAuthorizationToken auth = (OTPAuthorizationToken)authentication; String error = sampleService.loginWithOTP( auth.getPrincipal(), auth.getCredentials(), auth.getOTP() ); if ( error != null ) { throw new LoginException( error ); } auth.setAuthenticated( true ); return auth; } @Override public boolean supports( Class authentication ) { if ( authentication.isAssignableFrom( OTPAuthorizationToken.class ) ) { return true; } return false; } } 

4)配置弹簧安全性

 public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure( HttpSecurity http ) throws Exception { // configure filters http.addFilterBefore( new LDAPAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class ); http.addFilterBefore( new OTPAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class ); // configure authentication providers http.authenticationProvider( new LDAPAuthenticationProvider() ); http.authenticationProvider( new OTPAuthenticationProvider() ); // disable csrf http.csrf().disable(); // setup security http.authorizeRequests() .anyRequest() .fullyAuthenticated() .and().httpBasic(); } } 

希望有所帮助!

另一个添加第二个身份validation提供程序的选项:只需在AuthenticationManagerBuilder上指定另一个身份validation提供程序。 由于@EnableWebSecurity批注本身使用EnableGlobalAuthentication批注, EnableGlobalAuthentication您可以配置AuthenticationManagerBuilder的全局实例。 (有关详细信息,请参阅javadoc 。)

例如,这里我们有一个LDAP身份validation提供程序以及一个内存(硬编码)身份validation提供程序(这是我们在开发中为让本地用户进行测试而做的事情):

  @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Value("${user.role}") private String userRole; // ie ROLE_APP_USER @Value("${include.test.users}") private boolean includeTestUsers; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/**/js/**").permitAll() .antMatchers("/**/images/**").permitAll() .antMatchers("/**/favicon.ico").permitAll() .antMatchers("/**/css/**").permitAll() .antMatchers("/**/fonts/**").permitAll() .antMatchers("/**").hasAnyRole(userRole) .and().formLogin().loginPage("/login").permitAll().and().logout().permitAll(); http.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth, LdapContextSource contextSource) throws Exception { auth.ldapAuthentication() .userSearchBase("OU=Users OU") .userSearchFilter("sAMAccountName={0}") .groupSearchBase("OU=Groups OU") .groupSearchFilter("member={0}") .contextSource(contextSource); if (includeTestUsers) { auth.inMemoryAuthentication().withUser("user").password("u").authorities(userRole); } } } 

我想补充一下mclema的答案。 您可能需要添加覆盖以成功进行身份validation并继续filter链,否则用户将被重定向到默认URL(“/”)而不是原始的(例如:/ myrest / server / somemethod)

 @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { SecurityContext context = SecurityContextHolder.createEmptyContext(); context.setAuthentication(authResult); SecurityContextHolder.setContext(context); chain.doFilter(request, response); } 

接受的答案存在当前请求未被授予的问题,即。 仅针对以下请求建立会话! 因此我需要在第2点进行配置

 public class MyAuthorizationFilter extends AbstractAuthenticationProcessingFilter { public MyAuthorizationFilter() { super( "/*" ); // allow any request to contain an authorization header } public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response ) throws AuthenticationException { if ( request.getHeader( "Authorization" ) == null ) { return null; // no header found, continue on to other security filters } // required to use the token myNewToken = new MyAuthorizationToken( request.getHeader( "Authorization" ) ); // and set in the current context ==> the current request is as well authorized SecurityContextHolder.getContext().setAuthentication(myNewToken); // return a new authentication token to be processed by the authentication provider return myNewToken; } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { // try to authenticate the current request attemptAuthentication((HttpServletRequest) req, (HttpServletResponse) res); super.doFilter(req, res, chain); } } 

否则当前请求尚未经过身份validation,尽管会话已经创建!!! (以及我不需要的提供商,即添加filter就足够了。)