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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25794680/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 01:09:04  来源:igfitidea点击:

multiple authentication mechanisms in a single app using java config

javaspring-securityspring-security-ldap

提问by adeelmahmood

Currently I have a single authentication mechanism in my application which is to use LDAP for authentication and authorization. My security configuration looks like this

目前,我的应用程序中有一个身份验证机制,即使用 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);
    }
}
}

There are situations though where users might come in with a session token which can authentication from a session key server and valid token returns a username which can then be used to load authrization information from LDAP for that user. So my second authentication mechanism should happen first where if a session token is present in http headers it should perform the token authentication and then ldap lookup and if no session token is present it should just fall to current authentication mechanism. How can I add this second layer of authentication.

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

采纳答案by Matt MacLean

I spent quite some time wrapping my head around spring-security when using pure java configuration. There are a few steps involved in getting this to work. It should be something along these lines. The basic process is as follows:

在使用纯 Java 配置时,我花了相当长的时间来思考 spring-security。要使其发挥作用,需要执行几个步骤。它应该是沿着这些路线的。基本流程如下:

  • Create custom filters to check requests for specific authorization information

  • Each filter returns null (if no authorization of that type is found), or a custom AbstractAuthenticationToken

  • If a filter returns a token, each AuthenticationProvider's supports(class) method will be invoked with that token returning true|false if it should try authentication

  • attemptAuthentication will then be called on the AuthenticationProvider which supports the token. Here you do any service calls to authenticate the user. You can then throw LoginException's or call authentication.setAuthenticated(true) and return the token for a successful authentication.

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

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

  • 如果过滤器返回一个令牌,每个 AuthenticationProvider 的支持(类)方法将被调用,该令牌返回 true|false 如果它应该尝试身份验证

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

I have been using this setup for a while supporting various authentication methods (signed request, username/password, oauth etc) and it works quite well.

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

You can also pass AuthenticationSuccessHandler's and AuthenticationFailuersHandler's to the custom security filters to provide custom redirection strategies and failure handling.

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

Also be sure to setup the ant matchers in the filter's constructors to control what url patterns the filters apply too. For example, an ldap request filter would probably need to be check with any request "/*" whereas a username/password filter can just be checked on POST's to /login or something similar.

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

Example Code:

示例代码:

1) Create custom AuthenticationToken's for each type of authentication you want to support

1) 为您想要支持的每种类型的身份验证创建自定义 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) Create custom security filters for each type

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) Create custom AuthenticationProviders

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) Configure spring security

4)配置spring security

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

Hope that helps!

希望有帮助!

回答by Brice Roncace

Another, option to add a second authentication provider: Simply specify another one on the AuthenticationManagerBuilder. Because the @EnableWebSecurityannotation is itself annotated with EnableGlobalAuthenticationyou can configure the global instance of AuthenticationManagerBuilder. (See the javadocsfor more details.)

另一种添加第二个身份验证提供程序的选项:只需在AuthenticationManagerBuilder. 因为@EnableWebSecurity注解本身是带注解的,EnableGlobalAuthentication你可以配置AuthenticationManagerBuilder. (有关更多详细信息,请参阅javadoc。)

For example, here we have an LDAP authentication provider as well as an in memory (hard-coded) authentication provider (this is something we do in development to have local users to test with):

例如,这里我们有一个 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);
        }
      }
    }

回答by Mir3

I want to just add to mclema's answer. You may need to add override for successful authentication and continue the filter chain or else user gets redirected to default url ("/") instead of the original one (eg: /myrest/server/somemethod)

我只想补充 mclema 的答案。您可能需要为成功的身份验证添加覆盖并继续过滤器链,否则用户将被重定向到默认网址(“/”)而不是原始网址(例如:/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);
}

回答by LeO

The accepted answer has the issue that the current request is not granted ie. only for the following requests the session is established! Therefore I needed to configure in point 2

接受的答案存在当前请求未获准的问题,即。仅针对以下请求建立会话!因此我需要在第 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);
    }
}

otherwisethe current request is not yet authenticated although a session is already created!!! (And the Providers I do not need, i.e. adding filter is sufficient.)

否则当前请求尚未通过身份验证,尽管已经创建了会话!!!(和我不需要的提供者,即添加过滤器就足够了。)