refactor(security): 移除验证码服务中的验证码字段并删除安全相关服务类

This commit is contained in:
Ray.Hao
2026-06-28 00:58:33 +08:00
parent 773ee88490
commit d121ab4efc
6 changed files with 0 additions and 452 deletions

View File

@@ -71,7 +71,6 @@ public class CaptchaService {
return CaptchaInfo.builder()
.captchaId(captchaId)
.captchaBase64(imageBase64Data)
.captchaCode(captchaCode)
.build();
}

View File

@@ -1,154 +0,0 @@
package com.youlai.boot.framework.security.config;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.hutool.core.util.ArrayUtil;
import com.youlai.boot.framework.captcha.service.CaptchaService;
import com.youlai.boot.framework.security.filter.CaptchaValidationFilter;
import com.youlai.boot.framework.security.filter.TokenAuthenticationFilter;
import com.youlai.boot.framework.security.handler.MyAccessDeniedHandler;
import com.youlai.boot.framework.security.handler.MyAuthenticationEntryPoint;
import com.youlai.boot.framework.security.provider.SmsAuthenticationProvider;
import com.youlai.boot.framework.security.provider.WxMaAuthenticationProvider;
import com.youlai.boot.framework.security.token.TokenManager;
import com.youlai.boot.framework.security.service.SysUserDetailsService;
import com.youlai.boot.system.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* Spring Security 配置类
*
* @author Ray.Hao
* @since 2023/2/17
*/
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final RedisTemplate<String, Object> redisTemplate;
private final PasswordEncoder passwordEncoder;
private final TokenManager tokenManager;
private final UserService userService;
private final SysUserDetailsService userDetailsService;
private final CaptchaService captchaService;
private final SecurityProperties securityProperties;
/**
* 配置安全过滤链 SecurityFilterChain
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(requestMatcherRegistry -> {
// 配置无需登录即可访问的公开接口(配置文件方式)
String[] ignoreUrls = securityProperties.getIgnoreUrls();
if (ArrayUtil.isNotEmpty(ignoreUrls)) {
requestMatcherRegistry.requestMatchers(ignoreUrls).permitAll();
}
// 其他所有请求需登录后访问
requestMatcherRegistry.anyRequest().authenticated();
}
)
.exceptionHandling(configurer ->
configurer
.authenticationEntryPoint(new MyAuthenticationEntryPoint()) // 未认证异常处理器
.accessDeniedHandler(new MyAccessDeniedHandler()) // 无权限访问异常处理器
)
// 禁用默认的 Spring Security 特性,适用于前后端分离架构
.sessionManagement(configurer ->
configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态认证,不使用 Session
)
.csrf(AbstractHttpConfigurer::disable) // 禁用 CSRF 防护,前后端分离无需此防护机制
.formLogin(AbstractHttpConfigurer::disable) // 禁用默认的表单登录功能,前后端分离采用 Token 认证方式
.httpBasic(AbstractHttpConfigurer::disable) // 禁用 HTTP Basic 认证,避免弹窗式登录
// 禁用 X-Frame-Options 响应头,允许页面被嵌套到 iframe 中
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
// 验证码校验过滤器
.addFilterBefore(new CaptchaValidationFilter(captchaService), UsernamePasswordAuthenticationFilter.class)
// 验证和解析过滤器
.addFilterBefore(new TokenAuthenticationFilter(tokenManager), UsernamePasswordAuthenticationFilter.class)
.build();
}
/**
* 配置Web安全自定义器以忽略特定请求路径的安全性检查。
* <p>
* 该配置用于指定哪些请求路径不经过Spring Security过滤器链。通常用于静态资源文件。
*/
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> {
String[] unsecuredUrls = securityProperties.getUnsecuredUrls();
if (ArrayUtil.isNotEmpty(unsecuredUrls)) {
web.ignoring().requestMatchers(unsecuredUrls);
}
};
}
/**
* 默认密码认证的 Provider
*/
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
return daoAuthenticationProvider;
}
/**
* 短信验证码认证 Provider
*/
@Bean
public SmsAuthenticationProvider smsAuthenticationProvider() {
return new SmsAuthenticationProvider(userService, redisTemplate);
}
/**
* 微信小程序认证 Provider
*/
@Bean
public WxMaAuthenticationProvider wechatMiniAuthenticationProvider(
WxMaService wxMaService,
SysUserDetailsService sysUserDetailsService
) {
return new WxMaAuthenticationProvider(wxMaService, sysUserDetailsService);
}
/**
* 认证管理器
*/
@Bean
public AuthenticationManager authenticationManager(
DaoAuthenticationProvider daoAuthenticationProvider,
SmsAuthenticationProvider smsAuthenticationProvider,
WxMaAuthenticationProvider wxMaAuthenticationProvider
) {
return new ProviderManager(
daoAuthenticationProvider,
smsAuthenticationProvider,
wxMaAuthenticationProvider
);
}
}

View File

@@ -1,89 +0,0 @@
package com.youlai.boot.framework.security.provider;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.boot.common.constant.RedisConstants;
import com.youlai.boot.framework.security.exception.SmsCaptchaException;
import com.youlai.boot.framework.security.model.SmsAuthenticationToken;
import com.youlai.boot.framework.security.model.SysUserDetails;
import com.youlai.boot.framework.security.model.UserAuthInfo;
import com.youlai.boot.system.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
/**
* 短信验证码认证 Provider
*
* @author Ray.Hao
* @since 2.17.0
*/
@Slf4j
public class SmsAuthenticationProvider implements AuthenticationProvider {
private final UserService userService;
private final RedisTemplate<String, Object> redisTemplate;
public SmsAuthenticationProvider(UserService userService, RedisTemplate<String, Object> redisTemplate) {
this.userService = userService;
this.redisTemplate = redisTemplate;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String mobile = (String) authentication.getPrincipal();
String inputVerifyCode = (String) authentication.getCredentials();
if (StrUtil.isBlank(mobile)) {
log.warn("短信验证码登录失败:手机号为空");
throw new SmsCaptchaException("手机号不能为空");
}
if (StrUtil.isBlank(inputVerifyCode)) {
log.warn("短信验证码登录失败:验证码为空,手机号={}", mobile);
throw new SmsCaptchaException("验证码不能为空");
}
UserAuthInfo userAuthInfo = userService.getAuthInfoByMobile(mobile);
if (userAuthInfo == null) {
log.warn("短信验证码登录失败:用户不存在,手机号={}", mobile);
throw new UsernameNotFoundException("用户不存在");
}
if (ObjectUtil.notEqual(userAuthInfo.getStatus(), 1)) {
log.warn("短信验证码登录失败:用户已禁用,用户名={}", userAuthInfo.getUsername());
throw new DisabledException("用户已被禁用");
}
String cacheKey = StrUtil.format(RedisConstants.Captcha.SMS_LOGIN_CODE, mobile);
String cachedVerifyCode = (String) redisTemplate.opsForValue().get(cacheKey);
if (cachedVerifyCode == null) {
log.warn("短信验证码登录失败:验证码已过期,手机号={}", mobile);
throw new SmsCaptchaException("验证码已过期,请重新获取");
}
if (!StrUtil.equals(inputVerifyCode, cachedVerifyCode)) {
log.warn("短信验证码登录失败:验证码错误,手机号={}", mobile);
throw new SmsCaptchaException("验证码错误");
}
redisTemplate.delete(cacheKey);
SysUserDetails userDetails = new SysUserDetails(userAuthInfo);
log.info("短信验证码登录成功:用户名={},手机号={}", userAuthInfo.getUsername(), mobile);
return SmsAuthenticationToken.authenticated(userDetails, userDetails.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return SmsAuthenticationToken.class.isAssignableFrom(authentication);
}
}

View File

@@ -1,85 +0,0 @@
package com.youlai.boot.framework.security.provider;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.hutool.core.util.ObjectUtil;
import com.youlai.boot.framework.security.exception.NeedBindMobileException;
import com.youlai.boot.framework.security.model.SysUserDetails;
import com.youlai.boot.framework.security.model.UserAuthInfo;
import com.youlai.boot.framework.security.model.WxMaAuthenticationToken;
import com.youlai.boot.framework.security.service.SysUserDetailsService;
import com.youlai.boot.system.model.entity.UserSocial;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
/**
* 微信小程序认证 Provider
*/
@Slf4j
@RequiredArgsConstructor
public class WxMaAuthenticationProvider implements AuthenticationProvider {
private final WxMaService wxMaService;
private final SysUserDetailsService sysUserDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String code = (String) authentication.getPrincipal();
if (code == null || code.isEmpty()) {
log.warn("微信小程序登录失败code为空");
throw new IllegalArgumentException("code不能为空");
}
try {
WxMaJscode2SessionResult session = wxMaService.jsCode2SessionInfo(code);
String openid = session.getOpenid();
String sessionKey = session.getSessionKey();
log.info("微信小程序登录openid={}", openid);
UserSocial userSocial = sysUserDetailsService.getWechatMiniBindInfo(openid);
if (userSocial == null) {
log.info("微信小程序登录用户未绑定手机号openid={}", openid);
throw new NeedBindMobileException(openid, sessionKey);
}
UserAuthInfo userAuthInfo = sysUserDetailsService.getAuthInfoByWechatOpenid(openid);
if (userAuthInfo == null) {
log.warn("微信小程序登录失败用户不存在openid={}", openid);
throw new UsernameNotFoundException("用户不存在");
}
if (ObjectUtil.notEqual(userAuthInfo.getStatus(), 1)) {
log.warn("微信小程序登录失败用户已禁用username={}", userAuthInfo.getUsername());
throw new DisabledException("用户已被禁用");
}
sysUserDetailsService.updateWechatSessionKey(userSocial.getId(), sessionKey);
SysUserDetails userDetails = new SysUserDetails(userAuthInfo);
log.info("微信小程序登录成功username={}, openid={}", userAuthInfo.getUsername(), openid);
return WxMaAuthenticationToken.authenticated(userDetails, userDetails.getAuthorities());
} catch (WxErrorException e) {
log.error("微信小程序登录失败调用微信接口异常code={}", code, e);
throw new IllegalArgumentException("微信登录失败:" + e.getMessage());
}
}
@Override
public boolean supports(Class<?> authentication) {
return WxMaAuthenticationToken.class.isAssignableFrom(authentication);
}
}

View File

@@ -1,59 +0,0 @@
package com.youlai.boot.framework.security.service;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.boot.framework.security.util.SecurityUtils;
import com.youlai.boot.system.service.RoleMenuService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.PatternMatchUtils;
import java.util.Set;
/**
* Spring Security 权限校验组件
*
* @author Ray.Hao
* @since 0.0.1
*/
@Component("ss")
@RequiredArgsConstructor
@Slf4j
public class PermissionService {
private final RoleMenuService roleMenuService;
/**
* 判断当前登录用户是否拥有操作权限
*/
public boolean hasPerm(String requiredPerm) {
if (StrUtil.isBlank(requiredPerm)) {
return false;
}
if (SecurityUtils.isRoot()) {
return true;
}
Set<String> roleCodes = SecurityUtils.getRoles();
if (CollectionUtil.isEmpty(roleCodes)) {
return false;
}
Set<String> rolePerms = roleMenuService.getRolePermsByRoleCodes(roleCodes);
if (CollectionUtil.isEmpty(rolePerms)) {
return false;
}
boolean hasPermission = rolePerms.stream()
.anyMatch(rolePerm -> PatternMatchUtils.simpleMatch(rolePerm, requiredPerm));
if (!hasPermission) {
log.warn("用户无操作权限userId={}, username={}, requiredPerm={}",
SecurityUtils.getUserId(), SecurityUtils.getUsername(), requiredPerm);
}
return hasPermission;
}
}

View File

@@ -1,64 +0,0 @@
package com.youlai.boot.framework.security.service;
import com.youlai.boot.framework.security.model.SysUserDetails;
import com.youlai.boot.framework.security.model.UserAuthInfo;
import com.youlai.boot.system.enums.SocialPlatformEnum;
import com.youlai.boot.system.model.entity.UserSocial;
import com.youlai.boot.system.service.UserSocialService;
import com.youlai.boot.system.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* 系统用户认证 DetailsService
*
* @author Ray.Hao
* @since 2021/10/19
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class SysUserDetailsService implements UserDetailsService {
private final UserService userService;
private final UserSocialService userSocialService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
try {
UserAuthInfo userAuthInfo = userService.getAuthInfoByUsername(username);
if (userAuthInfo == null) {
throw new UsernameNotFoundException(username);
}
return new SysUserDetails(userAuthInfo);
} catch (Exception e) {
log.error("认证异常:{}", e.getMessage());
throw e;
}
}
/**
* 根据微信小程序openid查询绑定信息
*/
public UserSocial getWechatMiniBindInfo(String openid) {
return userSocialService.getByPlatformAndOpenid(SocialPlatformEnum.WECHAT_MINI, openid);
}
/**
* 根据微信小程序openid获取用户认证信息
*/
public UserAuthInfo getAuthInfoByWechatOpenid(String openid) {
return userSocialService.getAuthInfoByOpenid(SocialPlatformEnum.WECHAT_MINI, openid);
}
/**
* 更新微信小程序session_key
*/
public void updateWechatSessionKey(Long bindId, String sessionKey) {
userSocialService.updateSessionKey(bindId, sessionKey);
}
}