Spring Security 7.x.x结合Spring Authserver授权码流程MFA配置咨询
在Spring Security 7.x.x中为Spring Authserver授权码流程配置MFA
以下是适配授权码流程的MFA实现方案,核心是将MFA验证嵌入用户名密码登录后的流程,并利用SavedRequestAwareSuccessHandler恢复原始授权请求:
1. 定义MFA相关的认证组件
首先需要创建MFA专用的Authentication令牌和Provider,以处理TOTP验证逻辑:
TOTP认证令牌类
public class TotpAuthenticationToken extends AbstractAuthenticationToken { private final String token; private final UserDetails userDetails; public TotpAuthenticationToken(String token, UserDetails userDetails) { super(userDetails.getAuthorities()); this.token = token; this.userDetails = userDetails; setAuthenticated(false); } @Override public Object getCredentials() { return this.token; } @Override public Object getPrincipal() { return this.userDetails; } }
TOTP认证Provider
@Component public class TotpAuthenticationProvider implements AuthenticationProvider { private final UserRepository userRepository; public TotpAuthenticationProvider(UserRepository userRepository) { this.userRepository = userRepository; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { TotpAuthenticationToken totpToken = (TotpAuthenticationToken) authentication; UserDetails user = totpToken.getUserDetails(); String submittedToken = totpToken.getToken(); UserEntity userEntity = userRepository.findByUsername(user.getUsername()) .orElseThrow(() -> new BadCredentialsException("用户不存在")); // 使用Google Authenticator库验证TOTP令牌 GoogleAuthenticator ga = new GoogleAuthenticator(); boolean isValid = ga.authorize(userEntity.getTotpSecret(), Integer.parseInt(submittedToken)); if (!isValid) { throw new BadCredentialsException("无效的TOTP令牌"); } return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); } @Override public boolean supports(Class<?> authentication) { return TotpAuthenticationToken.class.isAssignableFrom(authentication); } }
2. 配置SecurityFilterChain,整合MFA到登录流程
在Security配置中,拦截用户名密码登录成功后的逻辑,判断用户是否需要MFA,触发验证流程:
@Configuration @EnableWebSecurity public class SecurityConfig { private final TotpAuthenticationProvider totpAuthenticationProvider; private final SavedRequestAwareSuccessHandler successHandler; private final UserRepository userRepository; public SecurityConfig(TotpAuthenticationProvider totpAuthenticationProvider, SavedRequestAwareSuccessHandler successHandler, UserRepository userRepository) { this.totpAuthenticationProvider = totpAuthenticationProvider; this.successHandler = successHandler; this.userRepository = userRepository; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/login", "/mfa/**", "/error").permitAll() .anyRequest().authenticated() ) .formLogin(form -> form .loginPage("/login") .successHandler((request, response, authentication) -> { UserDetails user = (UserDetails) authentication.getPrincipal(); UserEntity userEntity = userRepository.findByUsername(user.getUsername()) .orElseThrow(); if (userEntity.isMfaEnabled()) { // 将已验证的用户信息暂存到Session,跳转MFA验证页 request.getSession().setAttribute("MFA_USER", user); response.sendRedirect("/mfa/verify"); } else { // 无MFA需求,直接进入授权流程 successHandler.onAuthenticationSuccess(request, response, authentication); } }) ) .authenticationProvider(totpAuthenticationProvider) .sessionManagement(session -> session .sessionFixation().newSession() ); return http.build(); } }
3. 实现MFA验证端点
创建控制器处理MFA令牌提交,验证通过后恢复原始授权请求:
@Controller @RequestMapping("/mfa") public class MfaController { private final AuthenticationManager authenticationManager; private final SavedRequestAwareSuccessHandler successHandler; public MfaController(AuthenticationManager authenticationManager, SavedRequestAwareSuccessHandler successHandler) { this.authenticationManager = authenticationManager; this.successHandler = successHandler; } @GetMapping("/verify") public String showMfaVerifyPage() { return "mfa-verify"; // 对应的Thymeleaf/HTML页面 } @PostMapping("/verify") public void verifyTotpToken(@RequestParam("token") String token, HttpServletRequest request, HttpServletResponse response) throws Exception { UserDetails user = (UserDetails) request.getSession().getAttribute("MFA_USER"); if (user == null) { response.sendRedirect("/login"); return; } // 触发MFA认证 Authentication authentication = new TotpAuthenticationToken(token, user); Authentication authenticated = authenticationManager.authenticate(authentication); // 设置认证上下文并清理Session临时数据 SecurityContextHolder.getContext().setAuthentication(authenticated); request.getSession().removeAttribute("MFA_USER"); // 利用SavedRequestAwareSuccessHandler自动重定向回原始/oauth2/authorize请求 successHandler.onAuthenticationSuccess(request, response, authenticated); } }
4. 配置Spring Authserver客户端
确保客户端配置为授权码类型,回调地址正确:
@Configuration @EnableAuthorizationServer public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("demo-client") .secret("{noop}demo-secret") .authorizedGrantTypes("authorization_code") .scopes("read", "write") .redirectUris("http://localhost:8081/login/oauth2/code/demo-client") .autoApprove(false); } // 按需配置TokenStore、AuthorizationCodeServices等组件 }
关键注意点
- 授权码流程依赖Session,因此不能禁用Session,
SavedRequestAwareSuccessHandler需要通过Session获取原始的授权请求(包括state、redirect_uri等参数) - Spring Security 7.x的MFA功能并非仅支持隐式流程,只是官方示例针对隐式场景,通过自定义登录成功处理器和MFA验证逻辑,可无缝适配授权码流程
- 避免手动构造重定向URL,利用
SavedRequestAwareSuccessHandler能自动处理请求参数,减少错误
内容的提问来源于stack exchange,提问作者Kuan Chen
相关产品推荐
相关产品推荐

