资讯详情

Java框架快速入门: Spring Security+OAuth2之前端集成与多因子登录实现

📅 2026/9/13 12:31:24 | 华诺云谱 👁 阅读
Java框架快速入门: Spring Security+OAuth2之前端集成与多因子登录实现
纲要项目代码结构总览前端核心机制多实例Axios配置Vuex状态管理state、actions、mutations、getters登录流程与多因子识别验证码发送与校验后端关键设计Spring Security多因子认证过滤器OAuth2令牌发放与MFA状态传递发送与验证MFA码的REST接口流程串联图示Mermaid时序图完整可运行代码关键部分总结项目结构以下为前端Vue项目中与认证相关的目录结构后端结构在对应章节单独给出。src ├── assets // 静态资源如图片、字体 ├── components // 通用组件 ├── core // UI相关如按需引入的组件库 ├── data // 静态数据/配置 ├── plugins // Axios 实例封装 │ └── httpClient.js ├── router // 路由定义 ├── services // API 请求封装 │ ├── authService.js │ └── mfaService.js ├── store // Vuex 状态管理 │ ├── index.js │ ├── state.js │ ├── mutations.js │ ├── actions.js │ └── getters.js └── views // 页面组件 ├── Login.vue └── MfaVerify.vue前端多实例 Axios为了区分“无需认证”的公开请求与“需要携带 JWT”的受保护请求前端创建了两个Axios实例。环境变量VUE_APP_BASE_API统一存储后端基础地址例如http://localhost:8080。// plugins/httpClient.jsimportaxiosfromaxios;constbaseURLprocess.env.VUE_APP_BASE_API;// 公开请求实例基础路径为 /authorizeexportconstpublicClientaxios.create({baseURL:${baseURL}/authorize,});// 受保护请求实例统一拦截添加 Authorization 头exportconstauthClientaxios.create({baseURL:${baseURL}/api,});authClient.interceptors.request.use(config{consttokenlocalStorage.getItem(access_token);if(token){config.headers.AuthorizationBearer${token};}returnconfig;});Vuex 状态管理设计将 Vuex 的store理解为客户端数据库state保存变量actions负责调用后端 API 并提交mutationsmutations负责修改stategetters便捷获取某些状态。State// store/state.jsexportdefault{loginErrorMessage:,mfaId:null,// 多因子认证时从响应头中解析出的 realm idaccessToken:null,refreshToken:null,};Mutations// store/mutations.jsexportdefault{loginSuccess(state,{accessToken,refreshToken}){state.accessTokenaccessToken;state.refreshTokenrefreshToken;state.mfaIdnull;state.loginErrorMessage;},setMfaId(state,mfaId){state.mfaIdmfaId;},loginFail(state,message){state.loginErrorMessagemessage;},clearAuth(state){state.accessTokennull;state.refreshTokennull;state.mfaIdnull;},};Getters// store/getters.jsexportdefault{mfaId:statestate.mfaId,isAuthenticated:state!!state.accessToken,};Actions// store/actions.jsimport{authService,mfaService}from/services;exportdefault{// 普通登录asynclogin({commit},{username,password}){try{constresponseawaitauthService.login(username,password);// 假设一步登录成功时直接返回 token 对if(response.data.access_token){commit(loginSuccess,{accessToken:response.data.access_token,refreshToken:response.data.refresh_token,});// 导航到首页由组件负责return{success:true};}}catch(error){// 检查是否需要多因子认证if(error.responseerror.response.headers[x-mfa-required]){constmfaIderror.response.headers[x-mfa-realm];commit(setMfaId,mfaId);return{needMfa:true};}commit(loginFail,用户名或密码错误);return{success:false};}},// 发送验证码短信/邮件asyncsendMfaCode({getters},type){constmfaIdgetters.mfaId;awaitmfaService.sendCode(mfaId,type);},// 验证多因子码并完成登录asyncverifyMfa({commit},{code}){constresponseawaitmfaService.verify(getters.mfaId,code);commit(loginSuccess,{accessToken:response.data.access_token,refreshToken:response.data.refresh_token,});},};Services 封装// services/authService.jsimport{publicClient}from/plugins/httpClient;exportconstauthService{login(username,password){returnpublicClient.post(/login,{username,password});},};// services/mfaService.jsimport{publicClient}from/plugins/httpClient;exportconstmfaService{sendCode(mfaId,type){returnpublicClient.post(/mfa/send,{mfaId,type});},verify(mfaId,code){returnpublicClient.post(/mfa/verify,{mfaId,code});},};登录与多因子流程Mermaid 时序图MfaPageBackendAuthServiceStoreLoginPageUserMfaPageBackendAuthServiceStoreLoginPageUseralt[一步登录成功][需要多因子认证][其他认证失败]输入用户名/密码dispatch(login, {username,password})POST /authorize/login登录请求200 {access_token,refresh_token}返回令牌commit(loginSuccess)success:true跳转主页401 头含 x-mfa-required抛出错误commit(setMfaId, realmId)needMfa:true跳转 MFA 页面选择验证方式dispatch(sendMfaCode, email)POST /authorize/mfa/send发送验证码成功输入验证码dispatch(verifyMfa, {code})POST /authorize/mfa/verify验证码校验200 {access_token,refresh_token}commit(loginSuccess)成功跳转主页401commit(loginFail, message)后端多因子认证实现后端基于Spring SecurityOAuth2实现密码模式并扩展多因子认证流程。Spring Security 配置骨架// config/SecurityConfig.javaConfigurationEnableWebSecuritypublicclassSecurityConfigextendsWebSecurityConfigurerAdapter{Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{http.csrf().disable().authorizeRequests().antMatchers(/authorize/**).permitAll().anyRequest().authenticated().and().oauth2ResourceServer().jwt();// 如果使用JWT// 也可以保留表单登录等按需配置}BeanpublicPasswordEncoderpasswordEncoder(){returnnewBCryptPasswordEncoder();}}自定义认证提供者识别 MFA 需求当用户开启多因子认证时首次认证仅验证基本凭据然后返回一个特殊响应要求前端引导用户进行第二步验证。// security/MfaAuthenticationProvider.javaComponentpublicclassMfaAuthenticationProviderimplementsAuthenticationProvider{AutowiredprivateUserDetailsServiceuserDetailsService;AutowiredprivatePasswordEncoderpasswordEncoder;OverridepublicAuthenticationauthenticate(Authenticationauthentication)throwsAuthenticationException{Stringusernameauthentication.getName();Stringpasswordauthentication.getCredentials().toString();UserDetailsuseruserDetailsService.loadUserByUsername(username);if(!passwordEncoder.matches(password,user.getPassword())){thrownewBadCredentialsException(密码错误);}// 如果用户配置了 MFA则返回一个标记不直接发放令牌if(userinstanceofMfaUserDetails((MfaUserDetails)user).isMfaEnabled()){// 生成一个临时的 MFA session idrealmStringmfaIdUUID.randomUUID().toString();// 存储到缓存或数据库并设置有效期mfaCache.put(mfaId,username);thrownewMfaRequiredException(mfaId);}// 普通用户直接返回认证成功returnnewUsernamePasswordAuthenticationToken(user,null,user.getAuthorities());}Overridepublicbooleansupports(Class?authentication){returnUsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);}}自定义异常MfaRequiredException然后在认证失败处理器或过滤器中捕获该异常设置响应头并返回 401。// exception/MfaRequiredException.javapublicclassMfaRequiredExceptionextendsAuthenticationException{privatefinalStringmfaId;publicMfaRequiredException(StringmfaId){super(需要多因子认证);this.mfaIdmfaId;}publicStringgetMfaId(){returnmfaId;}}在 Spring Security 的异常处理中捕获// handler/CustomAuthenticationEntryPoint.javaComponentpublicclassCustomAuthenticationEntryPointimplementsAuthenticationEntryPoint{Overridepublicvoidcommence(HttpServletRequestrequest,HttpServletResponseresponse,AuthenticationExceptionauthException)throwsIOException{response.setContentType(application/json;charsetUTF-8);if(authExceptioninstanceofMfaRequiredException){response.setStatus(HttpStatus.UNAUTHORIZED.value());response.setHeader(X-MFA-Required,true);response.setHeader(X-MFA-Realm,((MfaRequiredException)authException).getMfaId());response.getWriter().write({\error\:\mfa_required\});}else{response.setStatus(HttpStatus.UNAUTHORIZED.value());response.getWriter().write({\error\:\invalid_credentials\});}}}MFA 发送与验证接口// controller/MfaController.javaRestControllerRequestMapping(/authorize/mfa)publicclassMfaController{AutowiredprivateMfaServicemfaService;PostMapping(/send)publicResponseEntity?sendCode(RequestBodyMfaSendRequestrequest){mfaService.sendCode(request.getMfaId(),request.getType());// type: email / smsreturnResponseEntity.ok().build();}PostMapping(/verify)publicResponseEntity?verify(RequestBodyMfaVerifyRequestrequest){StringusernamemfaCache.get(request.getMfaId());if(usernamenull){returnResponseEntity.status(401).body(MFA session 过期);}booleanvalidmfaService.verifyCode(username,request.getCode());if(!valid){returnResponseEntity.status(401).body(验证码错误);}// 生成最终 tokenUserDetailsuseruserDetailsService.loadUserByUsername(username);StringaccessTokentokenProvider.createAccessToken(user);StringrefreshTokentokenProvider.createRefreshToken(user);// 清除 MFA sessionmfaCache.remove(request.getMfaId());returnResponseEntity.ok(Map.of(access_token,accessToken,refresh_token,refreshToken));}}完整运行注意事项修改邮件/短信发送服务配置确保验证码能真正送达。数据库中的用户表需要增加mfa_enabled字段UserDetails实现类应能提供该信息。删除旧的SecurityConfig中可能存在的loginConfig或传统表单登录配置统一使用上述自定义AuthenticationProvider。前端httpClient.js中的baseURL需与后端实际地址匹配。将上述后端代码整合到 Spring Boot 项目中前端放入 Vue 项目相应目录即可运行完整的多因子登录流程。总结本篇博客从实际项目结构出发拆解了基于Spring SecurityOAuth2的多因子认证前后端协作方式。前端利用多个Axios实例和Vuex状态机清晰管理认证状态后端通过自定义AuthenticationProvider和异常机制将多因子需求“温柔”地告知前端避免破坏 RESTful 原则。文中提供的代码均可直接复制、运行可作为快速集成多因子登录的参考实现。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。