Spring 引导 2 和 Spring 安全 5 的多重身份验证
Multi-Factor Authentication with Spring Boot 2 and Spring Security 5
我想将带有 TOTP 软令牌的多因素身份验证添加到 Angular 和 Spring 应用程序,同时使所有内容尽可能接近 Spring 启动安全启动程序。
令牌验证在本地进行(使用 aerogear-otp-java 库),没有第三方 API 提供商。
为用户设置令牌有效,但通过利用 Spring 安全身份验证 Manager/Providers 验证它们无效。
TL;DR
- 将额外的 AuthenticationProvider 集成到 Spring Boot Security Starter 配置的系统中的官方方法是什么?
- 推荐的防止重放攻击的方法是什么?
长版
API 有一个端点 /auth/token
,前端可以通过提供用户名和密码从中获取 JWT 令牌。响应还包括身份验证状态,可以是 AUTHENTICATED 或 PRE_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
,我通过 http.authenticationProvider()
添加自定义 AuthenticationProvider
。根据 JavaDoc,这似乎是正确的位置:
Allows adding an additional AuthenticationProvider to be used
我的 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 安全性不接收我的身份验证提供程序?调试控制器显示 DaoAuthenticationProvider
是 AuthenticationProviderManager
中唯一的身份验证提供程序。
如果我将我的 MfaAuthenticationProvider
作为 bean 公开,它是 只有 注册的提供者,所以我得到相反的结果:
No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken.
那么,如何同时获得两者?
我的问题
将额外的 AuthenticationProvider
集成到 Spring Boot Security Starter 配置的系统中的推荐方法是什么,这样我就可以同时获得,DaoAuthenticationProvider
和我自己的习惯 MfaAuthenticationProvider
?我想保留 Spring Boot Scurity Starter 的默认设置,另外还有我自己的提供程序。
防止重放攻击
我知道 OTP 算法本身并不能在代码有效的时间片内防止重放攻击; RFC 6238 明确了这一点
The verifier MUST NOT accept the second attempt of the OTP after the successful validation has been issued for the first OTP, which ensures one-time only use of an OTP.
我想知道是否有推荐的实施保护的方法。由于 OTP 令牌是基于时间的,我正在考虑将最后一次成功登录存储在用户模型上,并确保每 30 秒时间片只有一次成功登录。这当然意味着用户模型上的同步。有更好的方法吗?
谢谢。
--
PS:因为这是一个关于安全的问题,所以我正在寻找来自可靠 and/or 官方来源的答案。谢谢你。
为了回答我自己的问题,经过进一步研究,这就是我实现它的方式。
我有一个提供程序作为实现 AuthenticationProvider
的 pojo。故意不是 Bean/Component。否则 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 中获取用户名,并将其发送到 authenticationManagerBuilder 的 authenticate()
方法,该方法选择 MfaAuthenticationProvider
因为它可以处理 OneTimePasswordAuthenticationToken
.
var authenticationToken = new OneTimePasswordAuthenticationToken(usernameFromJwt, providedOtp);
var authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
我想将带有 TOTP 软令牌的多因素身份验证添加到 Angular 和 Spring 应用程序,同时使所有内容尽可能接近 Spring 启动安全启动程序。
令牌验证在本地进行(使用 aerogear-otp-java 库),没有第三方 API 提供商。
为用户设置令牌有效,但通过利用 Spring 安全身份验证 Manager/Providers 验证它们无效。
TL;DR
- 将额外的 AuthenticationProvider 集成到 Spring Boot Security Starter 配置的系统中的官方方法是什么?
- 推荐的防止重放攻击的方法是什么?
长版
API 有一个端点 /auth/token
,前端可以通过提供用户名和密码从中获取 JWT 令牌。响应还包括身份验证状态,可以是 AUTHENTICATED 或 PRE_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
,我通过 http.authenticationProvider()
添加自定义 AuthenticationProvider
。根据 JavaDoc,这似乎是正确的位置:
Allows adding an additional AuthenticationProvider to be used
我的 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 安全性不接收我的身份验证提供程序?调试控制器显示 DaoAuthenticationProvider
是 AuthenticationProviderManager
中唯一的身份验证提供程序。
如果我将我的 MfaAuthenticationProvider
作为 bean 公开,它是 只有 注册的提供者,所以我得到相反的结果:
No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken.
那么,如何同时获得两者?
我的问题
将额外的 AuthenticationProvider
集成到 Spring Boot Security Starter 配置的系统中的推荐方法是什么,这样我就可以同时获得,DaoAuthenticationProvider
和我自己的习惯 MfaAuthenticationProvider
?我想保留 Spring Boot Scurity Starter 的默认设置,另外还有我自己的提供程序。
防止重放攻击
我知道 OTP 算法本身并不能在代码有效的时间片内防止重放攻击; RFC 6238 明确了这一点
The verifier MUST NOT accept the second attempt of the OTP after the successful validation has been issued for the first OTP, which ensures one-time only use of an OTP.
我想知道是否有推荐的实施保护的方法。由于 OTP 令牌是基于时间的,我正在考虑将最后一次成功登录存储在用户模型上,并确保每 30 秒时间片只有一次成功登录。这当然意味着用户模型上的同步。有更好的方法吗?
谢谢。
--
PS:因为这是一个关于安全的问题,所以我正在寻找来自可靠 and/or 官方来源的答案。谢谢你。
为了回答我自己的问题,经过进一步研究,这就是我实现它的方式。
我有一个提供程序作为实现 AuthenticationProvider
的 pojo。故意不是 Bean/Component。否则 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 中获取用户名,并将其发送到 authenticationManagerBuilder 的 authenticate()
方法,该方法选择 MfaAuthenticationProvider
因为它可以处理 OneTimePasswordAuthenticationToken
.
var authenticationToken = new OneTimePasswordAuthenticationToken(usernameFromJwt, providedOtp);
var authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);