fix: 补充缺失的 security provider/service 和异常类
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
package com.youlai.boot.framework.security.exception;
|
||||
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
|
||||
/**
|
||||
* 短信验证码异常
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2025/3/1
|
||||
*/
|
||||
public class SmsCaptchaException extends AuthenticationException {
|
||||
public SmsCaptchaException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user