使用 Java 配置的单个应用程序中的多种身份验证机制

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

@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);
    }
}
}

但在某些情况下,用户可能会附带一个会话令牌,该令牌可以从会话密钥服务器进行身份验证,而有效令牌返回一个用户名,然后可以使用该用户名从 LDAP 加载该用户的初始化信息。因此,我的第二种身份验证机制应该首先发生,如果http标头中存在会话令牌,则应执行令牌身份验证,然后执行ldap查找,如果没有会话令牌,则应将其归结为当前的身份验证机制。如何添加第二层身份验证。


答案 1

在使用纯java配置时,我花了相当多的时间在弹簧安全性上。要使其正常工作,需要执行几个步骤。它应该是沿着这些路线的东西。基本流程如下:

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

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

  • 如果筛选器返回一个令牌,则将调用每个身份验证提供程序的支持(类)方法,如果该令牌应尝试身份验证,则该令牌将返回 true|false

  • 然后,将在支持令牌的身份验证提供程序上调用尝试身份验证。在这里,您可以执行任何服务调用以对用户进行身份验证。然后,您可以抛出 LoginException 或调用 authentication.setAuthenticated(true),并返回令牌以成功进行身份验证。

我一直在使用此设置一段时间,支持各种身份验证方法(签名请求,用户名/密码,oauth等),并且效果很好。

您还可以将身份验证成功处理程序和身份验证Failuers处理程序传递到自定义安全筛选器,以提供自定义重定向策略和故障处理。

此外,请务必在过滤器的构造函数中设置蚂蚁匹配器,以控制过滤器也应用的 url 模式。例如,ldap 请求过滤器可能需要与任何请求“/*”进行检查,而用户名/密码过滤器只需在 POST 的 /login 或类似内容上进行检查即可。

示例代码:

1) 为要支持的每种身份验证类型创建自定义身份验证令牌

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) 创建自定义身份验证提供程序

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();
    }
}

希望有所帮助!


答案 2

另一个用于添加第二个身份验证提供程序的选项:只需在 上指定另一个身份验证提供程序即可。因为注释本身带有注释,所以可以配置 的全局实例。(有关更多详细信息,请参阅 javadocsAuthenticationManagerBuilder@EnableWebSecurityEnableGlobalAuthenticationAuthenticationManagerBuilder

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

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {

      @Value("${user.role}")
      private String userRole; // i.e. 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);
        }
      }
    }

推荐