You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 22:54:52