diff --git a/youlai-framework/src/main/java/com/youlai/boot/framework/captcha/service/CaptchaService.java b/youlai-framework/src/main/java/com/youlai/boot/framework/captcha/service/CaptchaService.java index 23882f79..32ff0416 100644 --- a/youlai-framework/src/main/java/com/youlai/boot/framework/captcha/service/CaptchaService.java +++ b/youlai-framework/src/main/java/com/youlai/boot/framework/captcha/service/CaptchaService.java @@ -71,7 +71,6 @@ public class CaptchaService { return CaptchaInfo.builder() .captchaId(captchaId) .captchaBase64(imageBase64Data) - .captchaCode(captchaCode) .build(); } diff --git a/youlai-framework/src/main/java/com/youlai/boot/framework/security/config/SecurityConfig.java b/youlai-framework/src/main/java/com/youlai/boot/framework/security/config/SecurityConfig.java deleted file mode 100644 index b64b6de0..00000000 --- a/youlai-framework/src/main/java/com/youlai/boot/framework/security/config/SecurityConfig.java +++ /dev/null @@ -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 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安全自定义器,以忽略特定请求路径的安全性检查。 - *

- * 该配置用于指定哪些请求路径不经过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 - ); - } - -} diff --git a/youlai-framework/src/main/java/com/youlai/boot/framework/security/provider/SmsAuthenticationProvider.java b/youlai-framework/src/main/java/com/youlai/boot/framework/security/provider/SmsAuthenticationProvider.java deleted file mode 100644 index c674a8c6..00000000 --- a/youlai-framework/src/main/java/com/youlai/boot/framework/security/provider/SmsAuthenticationProvider.java +++ /dev/null @@ -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 redisTemplate; - - public SmsAuthenticationProvider(UserService userService, RedisTemplate 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); - } -} diff --git a/youlai-framework/src/main/java/com/youlai/boot/framework/security/provider/WxMaAuthenticationProvider.java b/youlai-framework/src/main/java/com/youlai/boot/framework/security/provider/WxMaAuthenticationProvider.java deleted file mode 100644 index 071cd243..00000000 --- a/youlai-framework/src/main/java/com/youlai/boot/framework/security/provider/WxMaAuthenticationProvider.java +++ /dev/null @@ -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); - } - -} diff --git a/youlai-framework/src/main/java/com/youlai/boot/framework/security/service/PermissionService.java b/youlai-framework/src/main/java/com/youlai/boot/framework/security/service/PermissionService.java deleted file mode 100644 index cb50a3fb..00000000 --- a/youlai-framework/src/main/java/com/youlai/boot/framework/security/service/PermissionService.java +++ /dev/null @@ -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 roleCodes = SecurityUtils.getRoles(); - if (CollectionUtil.isEmpty(roleCodes)) { - return false; - } - - Set 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; - } - -} diff --git a/youlai-framework/src/main/java/com/youlai/boot/framework/security/service/SysUserDetailsService.java b/youlai-framework/src/main/java/com/youlai/boot/framework/security/service/SysUserDetailsService.java deleted file mode 100644 index 926c3772..00000000 --- a/youlai-framework/src/main/java/com/youlai/boot/framework/security/service/SysUserDetailsService.java +++ /dev/null @@ -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); - } -}