使用Spring Boot 2和Spring Security 5进行多重身份验证


11

我想在Angular&Spring应用程序中添加具有TOTP软令牌的多因素身份验证,同时使所有内容尽可能接近Spring Boot Security Starter的默认值。

令牌验证在本地进行(使用aerogear-otp-java库),没有第三方API提供程序。

为用户设置令牌是可行的,但无法通过利用Spring Security Authentication Manager / Providers来验证令牌。

TL; DR

  • 将额外的AuthenticationProvider集成到Spring Boot Security Starter配置的系统中的正式方法是什么?
  • 建议采用什么方法来防止重放攻击?

长版

该API具有一个端点/auth/token,前端可以通过提供用户名和密码从该端点获取JWT令牌。该响应还包括一个身份验证状态,可以为AUTHENTICATEDPRE_AUTHENTICATED_MFA_REQUIRED

如果用户需要MFA,则使用授予的单个授权PRE_AUTHENTICATED_MFA_REQUIRED和5分钟的到期时间来颁发令牌。这使用户可以访问端点/auth/mfa-token,在端点上可以从其Authenticator应用程序提供TOTP代码,并获取经过完全身份验证的令牌来访问站点。

提供者和令牌

我创建了MfaAuthenticationProvider实现AuthenticationProvider以下内容的自定义:

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // validate the OTP code
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return OneTimePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }

还有一个OneTimePasswordAuthenticationToken扩展AbstractAuthenticationToken名,用于保存用户名(取自签名的JWT)和OTP代码。

设定档

我有我的自定义WebSecurityConfigurerAdapter,我AuthenticationProvider通过添加我的自定义http.authenticationProvider()。根据JavaDoc,这似乎是正确的位置:

允许添加一个额外的AuthenticationProvider来使用

我的相关部分SecurityConfig看起来像这样。

    @Configuration
    @EnableWebSecurity
    @EnableJpaAuditing(auditorAwareRef = "appSecurityAuditorAware")
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        private final TokenProvider tokenProvider;

        public SecurityConfig(TokenProvider tokenProvider) {
            this.tokenProvider = tokenProvider;
        }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authenticationProvider(new MfaAuthenticationProvider());

        http.authorizeRequests()
            // Public endpoints, HTML, Assets, Error Pages and Login
            .antMatchers("/", "favicon.ico", "/asset/**", "/pages/**", "/api/auth/token").permitAll()

            // MFA auth endpoint
            .antMatchers("/api/auth/mfa-token").hasAuthority(ROLE_PRE_AUTH_MFA_REQUIRED)

            // much more config

控制者

AuthController已经将AuthenticationManagerBuilder注入是拉动它一起。

@RestController
@RequestMapping(AUTH)
public class AuthController {
    private final TokenProvider tokenProvider;
    private final AuthenticationManagerBuilder authenticationManagerBuilder;

    public AuthController(TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) {
        this.tokenProvider = tokenProvider;
        this.authenticationManagerBuilder = authenticationManagerBuilder;
    }

    @PostMapping("/mfa-token")
    public ResponseEntity<Token> mfaToken(@Valid @RequestBody OneTimePassword oneTimePassword) {
        var username = SecurityUtils.getCurrentUserLogin().orElse("");
        var authenticationToken = new OneTimePasswordAuthenticationToken(username, oneTimePassword.getCode());
        var authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);

        // rest of class

但是,针对发布/auth/mfa-token导致此错误:

"error": "Forbidden",
"message": "Access Denied",
"trace": "org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for de.....OneTimePasswordAuthenticationToken

为什么Spring Security无法选择我的身份验证提供程序?调试控制器将向我显示这DaoAuthenticationProvider是中唯一的身份验证提供程序AuthenticationProviderManager

如果我将自己公开MfaAuthenticationProvider为bean,则它是唯一注册的Provider,所以我得到相反的结果:

No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken. 

那么,我如何同时获得两者呢?

我的问题

建议将附加组件集成AuthenticationProviderSpring Boot Security Starter配置的系统中的推荐方法是什么,这样我就可以得到DaoAuthenticationProvider和我自己的自定义MfaAuthenticationProvider?我想保留Spring Boot Scurity Starter的默认值,并另外拥有自己的提供程序。

防止重放攻击

我知道,OTP算法本身并不能在代码有效的时间段内防止重放攻击。RFC 6238明确了这一点

对于第一个OTP发出成功的验证后,验证者不得接受OTP的第二次尝试,以确保一次性使用一次OTP。

我想知道是否有推荐的方法来实现保护。由于OTP令牌是基于时间的,因此我正在考虑将最后一次成功的登录存储在用户的模型上,并确保每30秒的时间片只有一次成功的登录。当然,这意味着在用户模型上进行同步。还有更好的方法吗?

谢谢。

-

PS:由于这是关于安全性的问题,我正在寻找可靠和/或官方来源的答案。谢谢。

Answers:


0

为了回答我自己的问题,这是经过进一步研究后我如何实施的。

我有一个提供者作为实现的pojo AuthenticationProvider。故意不是Bean /组件。否则,Spring将其注册为唯一的提供程序。

public class MfaAuthenticationProvider implements AuthenticationProvider {
    private final AccountService accountService;

    @Override
    public Authentication authenticate(Authentication authentication) {
        // here be code 
        }

在我的SecurityConfig中,我让Spring自动接线AuthenticationManagerBuilder并手动注入MfaAuthenticationProvider

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
       private final AuthenticationManagerBuilder authenticationManagerBuilder;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // other code  
        authenticationManagerBuilder.authenticationProvider(getMfaAuthenticationProvider());
        // more code
}

// package private for testing purposes. 
MfaAuthenticationProvider getMfaAuthenticationProvider() {
    return new MfaAuthenticationProvider(accountService);
}

在标准身份验证之后,如果用户启用了MFA,则将使用授予的权限PRE_AUTHENTICATED_MFA_REQUIRED对他们进行预身份验证。这使他们可以访问单个端点/auth/mfa-token。该端点从有效的JWT和提供的TOTP中获取用户名,并将其发送到authenticate()authenticationManagerBuilder 的方法,后者选择MfaAuthenticationProvider可以处理的OneTimePasswordAuthenticationToken

    var authenticationToken = new OneTimePasswordAuthenticationToken(usernameFromJwt, providedOtp);
    var authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.