refactor: 扩展 spring security 实现微信一键登录认证
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
package com.youlai.boot.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author haoxr
|
||||||
|
* @since 2024/12/3
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class PasswordEncoderConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码编码器
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.youlai.boot.config;
|
package com.youlai.boot.config;
|
||||||
|
|
||||||
|
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||||
import cn.hutool.captcha.generator.CodeGenerator;
|
import cn.hutool.captcha.generator.CodeGenerator;
|
||||||
import cn.hutool.core.collection.CollectionUtil;
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
import com.youlai.boot.common.constant.SecurityConstants;
|
import com.youlai.boot.common.constant.SecurityConstants;
|
||||||
@@ -7,15 +8,20 @@ import com.youlai.boot.config.property.SecurityProperties;
|
|||||||
import com.youlai.boot.core.filter.RateLimiterFilter;
|
import com.youlai.boot.core.filter.RateLimiterFilter;
|
||||||
import com.youlai.boot.core.security.exception.MyAccessDeniedHandler;
|
import com.youlai.boot.core.security.exception.MyAccessDeniedHandler;
|
||||||
import com.youlai.boot.core.security.exception.MyAuthenticationEntryPoint;
|
import com.youlai.boot.core.security.exception.MyAuthenticationEntryPoint;
|
||||||
import com.youlai.boot.core.security.filter.JwtValidationFilter;
|
import com.youlai.boot.core.security.extension.WeChatAuthenticationProvider;
|
||||||
import com.youlai.boot.core.security.filter.CaptchaValidationFilter;
|
import com.youlai.boot.core.security.filter.CaptchaValidationFilter;
|
||||||
|
import com.youlai.boot.core.security.filter.JwtValidationFilter;
|
||||||
|
import com.youlai.boot.core.security.service.SysUserDetailsService;
|
||||||
import com.youlai.boot.shared.auth.service.impl.JwtTokenService;
|
import com.youlai.boot.shared.auth.service.impl.JwtTokenService;
|
||||||
import com.youlai.boot.system.service.ConfigService;
|
import com.youlai.boot.system.service.ConfigService;
|
||||||
|
import com.youlai.boot.system.service.UserService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
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.authentication.configuration.AuthenticationConfiguration;
|
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
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.builders.HttpSecurity;
|
||||||
@@ -24,7 +30,6 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
|
|||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
@@ -48,6 +53,10 @@ public class SecurityConfig {
|
|||||||
private final SecurityProperties securityProperties;
|
private final SecurityProperties securityProperties;
|
||||||
private final ConfigService configService;
|
private final ConfigService configService;
|
||||||
private final JwtTokenService jwtTokenService;
|
private final JwtTokenService jwtTokenService;
|
||||||
|
private final WxMaService wxMaService;
|
||||||
|
private final UserService userService;
|
||||||
|
private final SysUserDetailsService userDetailsService;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
@@ -69,14 +78,12 @@ public class SecurityConfig {
|
|||||||
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
|
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
|
||||||
|
|
||||||
;
|
|
||||||
// 限流过滤器
|
// 限流过滤器
|
||||||
http.addFilterBefore(new RateLimiterFilter(redisTemplate, configService), UsernamePasswordAuthenticationFilter.class);
|
.addFilterBefore(new RateLimiterFilter(redisTemplate, configService), UsernamePasswordAuthenticationFilter.class)
|
||||||
// 验证码校验过滤器
|
// 验证码校验过滤器
|
||||||
http.addFilterBefore(new CaptchaValidationFilter(redisTemplate, codeGenerator), UsernamePasswordAuthenticationFilter.class);
|
.addFilterBefore(new CaptchaValidationFilter(redisTemplate, codeGenerator), UsernamePasswordAuthenticationFilter.class)
|
||||||
// JWT 校验过滤器
|
// JWT 校验过滤器
|
||||||
http.addFilterBefore(new JwtValidationFilter(jwtTokenService), UsernamePasswordAuthenticationFilter.class);
|
.addFilterBefore(new JwtValidationFilter(jwtTokenService), UsernamePasswordAuthenticationFilter.class);
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
@@ -94,21 +101,32 @@ public class SecurityConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 密码编码器
|
* 默认密码认证的 Provider
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
public DaoAuthenticationProvider daoAuthenticationProvider() {
|
||||||
return new BCryptPasswordEncoder();
|
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
|
||||||
|
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
|
||||||
|
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
|
||||||
|
daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
|
||||||
|
return daoAuthenticationProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AuthenticationManager 手动注入
|
* 微信认证 Provider
|
||||||
*
|
|
||||||
* @param authenticationConfiguration 认证配置
|
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
|
public WeChatAuthenticationProvider weChatAuthenticationProvider() {
|
||||||
return authenticationConfiguration.getAuthenticationManager();
|
return new WeChatAuthenticationProvider(userService, wxMaService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动注入 AuthenticationManager,支持多种认证方式
|
||||||
|
* - DaoAuthenticationProvider:用户名密码认证
|
||||||
|
* - WeChatAuthenticationProvider:微信认证
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authenticationManager() {
|
||||||
|
return new ProviderManager(daoAuthenticationProvider(), weChatAuthenticationProvider());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package com.youlai.boot.core.security.extension;
|
||||||
|
|
||||||
|
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||||
|
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.youlai.boot.core.security.model.SysUserDetails;
|
||||||
|
import com.youlai.boot.system.model.dto.UserAuthInfo;
|
||||||
|
import com.youlai.boot.system.service.UserService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.chanjar.weixin.common.error.WxErrorException;
|
||||||
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
|
import org.springframework.security.authentication.CredentialsExpiredException;
|
||||||
|
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 WeChatAuthenticationProvider implements AuthenticationProvider {
|
||||||
|
|
||||||
|
private final UserService userService;
|
||||||
|
|
||||||
|
private final WxMaService wxMaService;
|
||||||
|
|
||||||
|
|
||||||
|
public WeChatAuthenticationProvider(UserService userService, WxMaService wxMaService) {
|
||||||
|
this.userService = userService;
|
||||||
|
this.wxMaService = wxMaService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信认证逻辑,参考 Spring Security 认证密码校验流程
|
||||||
|
*
|
||||||
|
* @param authentication 认证对象
|
||||||
|
* @return 认证后的 Authentication 对象
|
||||||
|
* @throws AuthenticationException 认证异常
|
||||||
|
* @see org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider#authenticate(Authentication)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||||
|
String code = (String) authentication.getPrincipal();
|
||||||
|
|
||||||
|
// 通过微信服务端验证 code 并获取用户会话信息
|
||||||
|
WxMaJscode2SessionResult sessionInfo;
|
||||||
|
try {
|
||||||
|
sessionInfo = wxMaService.getUserService().getSessionInfo(code);
|
||||||
|
} catch (WxErrorException e) {
|
||||||
|
throw new CredentialsExpiredException("微信登录 code 无效或已失效,请重新获取");
|
||||||
|
}
|
||||||
|
|
||||||
|
String openId = sessionInfo.getOpenid();
|
||||||
|
if (StrUtil.isBlank(openId)) {
|
||||||
|
throw new UsernameNotFoundException("未能获取到微信 OpenID,请稍后重试");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据微信 OpenID 查询用户信息
|
||||||
|
UserAuthInfo userAuthInfo = userService.getUserAuthInfoByOpenId(openId);
|
||||||
|
|
||||||
|
if (userAuthInfo == null) {
|
||||||
|
// TODO: 用户不存在则注册,这里需要获取用户手机号并与现有用户绑定
|
||||||
|
userService.registerOrBindWechatUser(openId);
|
||||||
|
|
||||||
|
// 再次查询用户信息,确保用户注册成功
|
||||||
|
userAuthInfo = userService.getUserAuthInfoByOpenId(openId);
|
||||||
|
if (userAuthInfo == null) {
|
||||||
|
throw new UsernameNotFoundException("用户注册失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查用户状态是否有效
|
||||||
|
if (ObjectUtil.notEqual(userAuthInfo.getStatus(), 1)) {
|
||||||
|
throw new DisabledException("用户已被禁用");
|
||||||
|
}
|
||||||
|
// 这里因为已经根据 code 从微信小程序获取到 openid 不需要再经过系统认证,所以直接生成
|
||||||
|
|
||||||
|
// 构建认证后的用户详情信息
|
||||||
|
SysUserDetails userDetails = new SysUserDetails(userAuthInfo);
|
||||||
|
|
||||||
|
// 创建已认证的 WeChatAuthenticationToken
|
||||||
|
WeChatAuthenticationToken weChatAuthenticationToken = WeChatAuthenticationToken.authenticated(
|
||||||
|
userDetails,
|
||||||
|
userDetails.getAuthorities()
|
||||||
|
);
|
||||||
|
return weChatAuthenticationToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> authentication) {
|
||||||
|
return WeChatAuthenticationToken.class.isAssignableFrom(authentication);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.youlai.boot.core.security.extension;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信认证 Token
|
||||||
|
*
|
||||||
|
* @author Ray.Hao
|
||||||
|
* @since 2024/12/2
|
||||||
|
*/
|
||||||
|
public class WeChatAuthenticationToken extends AbstractAuthenticationToken {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 621L;
|
||||||
|
private final Object principal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信认证 Token (未认证)
|
||||||
|
*
|
||||||
|
* @param principal 微信用户信息
|
||||||
|
*/
|
||||||
|
public WeChatAuthenticationToken(Object principal) {
|
||||||
|
// 没有授权信息时,设置为 null
|
||||||
|
super(null);
|
||||||
|
this.principal = principal;
|
||||||
|
// 默认未认证
|
||||||
|
this.setAuthenticated(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信认证 Token (已认证)
|
||||||
|
*
|
||||||
|
* @param principal 微信用户信息
|
||||||
|
* @param authorities 授权信息
|
||||||
|
*/
|
||||||
|
public WeChatAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
|
||||||
|
super(authorities);
|
||||||
|
this.principal = principal;
|
||||||
|
// 认证通过
|
||||||
|
super.setAuthenticated(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证通过
|
||||||
|
*
|
||||||
|
* @param principal 微信用户信息
|
||||||
|
* @param authorities 授权信息
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static WeChatAuthenticationToken authenticated(Object principal, Collection<? extends GrantedAuthority> authorities) {
|
||||||
|
return new WeChatAuthenticationToken(principal, authorities);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getCredentials() {
|
||||||
|
// 微信认证不需要密码
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPrincipal() {
|
||||||
|
return this.principal;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ import org.springframework.stereotype.Service;
|
|||||||
/**
|
/**
|
||||||
* 系统用户认证 DetailsService
|
* 系统用户认证 DetailsService
|
||||||
*
|
*
|
||||||
* @author Ray
|
* @author Ray.Hao
|
||||||
* @since 2021/10/19
|
* @since 2021/10/19
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@@ -39,7 +39,6 @@ public class SysUserDetailsService implements UserDetailsService {
|
|||||||
}
|
}
|
||||||
return new SysUserDetails(userAuthInfo);
|
return new SysUserDetails(userAuthInfo);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
|
||||||
// 记录异常日志
|
// 记录异常日志
|
||||||
log.error("认证异常:{}", e.getMessage());
|
log.error("认证异常:{}", e.getMessage());
|
||||||
// 抛出异常
|
// 抛出异常
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import com.youlai.boot.common.constant.SecurityConstants;
|
|||||||
import com.youlai.boot.common.constant.SystemConstants;
|
import com.youlai.boot.common.constant.SystemConstants;
|
||||||
import com.youlai.boot.common.exception.BusinessException;
|
import com.youlai.boot.common.exception.BusinessException;
|
||||||
import com.youlai.boot.common.result.ResultCode;
|
import com.youlai.boot.common.result.ResultCode;
|
||||||
|
import com.youlai.boot.core.security.extension.WeChatAuthenticationToken;
|
||||||
import com.youlai.boot.core.security.util.SecurityUtils;
|
import com.youlai.boot.core.security.util.SecurityUtils;
|
||||||
import com.youlai.boot.shared.auth.enums.CaptchaTypeEnum;
|
import com.youlai.boot.shared.auth.enums.CaptchaTypeEnum;
|
||||||
import com.youlai.boot.shared.auth.model.RefreshTokenRequest;
|
import com.youlai.boot.shared.auth.model.RefreshTokenRequest;
|
||||||
@@ -57,24 +58,44 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录
|
* 用户名密码登录
|
||||||
*
|
*
|
||||||
* @param username 用户名
|
* @param username 用户名
|
||||||
* @param password 密码
|
* @param password 密码
|
||||||
* @return 登录结果
|
* @return 访问令牌
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public AuthTokenResponse login(String username, String password) {
|
public AuthTokenResponse login(String username, String password) {
|
||||||
// 创建认证令牌对象
|
// 1. 创建用于密码认证的令牌(未认证)
|
||||||
UsernamePasswordAuthenticationToken authenticationToken =
|
UsernamePasswordAuthenticationToken authenticationToken =
|
||||||
new UsernamePasswordAuthenticationToken(username.toLowerCase().trim(), password);
|
new UsernamePasswordAuthenticationToken(username.trim(), password);
|
||||||
// 执行用户认证,认证成功返回的Authentication是SysUserDetailsService#loadUserByUsername获取到的的UserDetails
|
// 2. 执行认证(认证中)
|
||||||
Authentication authentication = authenticationManager.authenticate(authenticationToken);
|
Authentication authentication = authenticationManager.authenticate(authenticationToken);
|
||||||
// 认证成功后生成JWT令牌
|
|
||||||
|
// 3. 认证成功后生成 JWT 令牌,并存入 Security 上下文,供登录日志 AOP 使用(已认证)
|
||||||
AuthTokenResponse authTokenResponse = tokenService.generateToken(authentication);
|
AuthTokenResponse authTokenResponse = tokenService.generateToken(authentication);
|
||||||
// 将认证信息存入Security上下文,便于在AOP(如日志记录)中获取当前用户信息
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
// 返回包含JWT令牌的登录结果
|
return authTokenResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信一键授权登录
|
||||||
|
*
|
||||||
|
* @param code 微信登录code
|
||||||
|
* @return 访问令牌
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AuthTokenResponse wechatLogin(String code) {
|
||||||
|
// 1. 创建用户微信认证的令牌(未认证)
|
||||||
|
WeChatAuthenticationToken authenticationToken = new WeChatAuthenticationToken(code);
|
||||||
|
|
||||||
|
// 2. 执行认证(认证中)
|
||||||
|
Authentication authentication = authenticationManager.authenticate(authenticationToken);
|
||||||
|
|
||||||
|
// 3. 认证成功后生成 JWT 令牌,并存入 Security 上下文,供登录日志 AOP 使用(已认证)
|
||||||
|
AuthTokenResponse authTokenResponse = tokenService.generateToken(authentication);
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
|
||||||
return authTokenResponse;
|
return authTokenResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,54 +179,5 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
return tokenService.refreshToken(refreshToken);
|
return tokenService.refreshToken(refreshToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 微信小程序登录
|
|
||||||
*
|
|
||||||
* @param code 微信登录code
|
|
||||||
* @return 访问令牌
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public AuthTokenResponse wechatLogin(String code) {
|
|
||||||
// 1. 通过code获取微信access_token
|
|
||||||
WxMaJscode2SessionResult sessionInfo = null;
|
|
||||||
try {
|
|
||||||
sessionInfo = wxMaService.getUserService().getSessionInfo(code);
|
|
||||||
} catch (WxErrorException e) {
|
|
||||||
log.error("微信小程序登录失败", e);
|
|
||||||
throw new BusinessException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
String openId = sessionInfo.getOpenid();
|
|
||||||
if (StrUtil.isBlank(openId)) {
|
|
||||||
throw new BusinessException("微信授权失败");
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo 获取微信用户信息
|
|
||||||
// WxMaUserInfo userInfo = wxMaService.getUserService().getUserInfo(sessionInfo.getSessionKey(), sessionInfo.getOpenid());
|
|
||||||
|
|
||||||
// 2. 根据openId查询用户信息,如果不存在则注册新用户
|
|
||||||
User user = userService.getUserByOpenId(openId);
|
|
||||||
if (Objects.isNull(user)) {
|
|
||||||
String name = "微信用户" + IdUtil.simpleUUID();
|
|
||||||
UserForm newUser = new UserForm();
|
|
||||||
newUser.setOpenId(openId);
|
|
||||||
newUser.setNickname(name);
|
|
||||||
newUser.setUsername(name);
|
|
||||||
boolean result = userService.saveUser(newUser);
|
|
||||||
if (!result) {
|
|
||||||
throw new BusinessException("微信用户注册失败");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UsernamePasswordAuthenticationToken authenticationToken =
|
|
||||||
new UsernamePasswordAuthenticationToken(user.getUsername().toLowerCase().trim(), SystemConstants.DEFAULT_PASSWORD);
|
|
||||||
// 执行用户认证
|
|
||||||
Authentication authentication = authenticationManager.authenticate(authenticationToken);
|
|
||||||
// 认证成功后生成JWT令牌
|
|
||||||
AuthTokenResponse authTokenResponse = tokenService.generateToken(authentication);
|
|
||||||
// 将认证信息存入Security上下文,便于在AOP(如日志记录)中获取当前用户信息
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
|
||||||
// 返回包含JWT令牌的登录结果
|
|
||||||
return authTokenResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,14 @@ public interface UserMapper extends BaseMapper<User> {
|
|||||||
*/
|
*/
|
||||||
UserAuthInfo getUserAuthInfo(String username);
|
UserAuthInfo getUserAuthInfo(String username);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据微信openid获取用户认证信息
|
||||||
|
*
|
||||||
|
* @param openid 微信openid
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
UserAuthInfo getUserAuthInfoByOpenId(String openid);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取导出用户列表
|
* 获取导出用户列表
|
||||||
*
|
*
|
||||||
@@ -64,4 +72,6 @@ public interface UserMapper extends BaseMapper<User> {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
UserBO getUserProfile(Long userId);
|
UserBO getUserProfile(Long userId);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,29 +7,57 @@ import java.util.Set;
|
|||||||
/**
|
/**
|
||||||
* 用户认证信息
|
* 用户认证信息
|
||||||
*
|
*
|
||||||
* @author haoxr
|
* @author Ray.Hao
|
||||||
* @since 2022/10/22
|
* @since 2022/10/22
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class UserAuthInfo {
|
public class UserAuthInfo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
private Long userId;
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户名
|
||||||
|
*/
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 昵称
|
||||||
|
*/
|
||||||
private String nickname;
|
private String nickname;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门ID
|
||||||
|
*/
|
||||||
private Long deptId;
|
private Long deptId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户密码
|
||||||
|
*/
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态(1:启用;0:禁用)
|
||||||
|
*/
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户所属的角色集合
|
||||||
|
*/
|
||||||
private Set<String> roles;
|
private Set<String> roles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户拥有的权限集合
|
||||||
|
*/
|
||||||
private Set<String> perms;
|
private Set<String> perms;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限范围,用于控制用户可以访问的数据级别
|
||||||
|
*
|
||||||
|
* @see com.youlai.boot.common.enums.DataScopeEnum
|
||||||
|
*/
|
||||||
private Integer dataScope;
|
private Integer dataScope;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public class User extends BaseEntity {
|
|||||||
private Integer isDeleted;
|
private Integer isDeleted;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 微信openid
|
* 微信 OpenID
|
||||||
*/
|
*/
|
||||||
private String openId;
|
private String openid;
|
||||||
}
|
}
|
||||||
@@ -160,10 +160,18 @@ public interface UserService extends IService<User> {
|
|||||||
List<Option<String>> listUserOptions();
|
List<Option<String>> listUserOptions();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据openId获取用户信息
|
* 根据 openid 获取用户认证信息
|
||||||
*
|
*
|
||||||
* @param openId openId
|
* @param username 用户名
|
||||||
* @return {@link User}
|
* @return {@link UserAuthInfo}
|
||||||
*/
|
*/
|
||||||
User getUserByOpenId(String openId);
|
|
||||||
|
UserAuthInfo getUserAuthInfoByOpenId(String username);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据微信 OpenID 注册或绑定用户
|
||||||
|
*
|
||||||
|
* @param openId 微信 OpenID
|
||||||
|
*/
|
||||||
|
void registerOrBindWechatUser(String openId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import java.util.stream.Collectors;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class ConfigServiceImpl extends ServiceImpl<ConfigMapper, Config> implements ConfigService {
|
public class ConfigServiceImpl extends ServiceImpl<ConfigMapper, Config> implements ConfigService {
|
||||||
|
|
||||||
private final ConfigMapper configMapper;
|
|
||||||
|
|
||||||
private final ConfigConverter configConverter;
|
private final ConfigConverter configConverter;
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import com.youlai.boot.common.model.Option;
|
|||||||
import com.youlai.boot.shared.mail.service.MailService;
|
import com.youlai.boot.shared.mail.service.MailService;
|
||||||
import com.youlai.boot.shared.sms.service.SmsService;
|
import com.youlai.boot.shared.sms.service.SmsService;
|
||||||
import com.youlai.boot.system.model.entity.User;
|
import com.youlai.boot.system.model.entity.User;
|
||||||
|
import com.youlai.boot.system.model.entity.UserRole;
|
||||||
import com.youlai.boot.system.model.form.*;
|
import com.youlai.boot.system.model.form.*;
|
||||||
import com.youlai.boot.config.property.AliyunSmsProperties;
|
import com.youlai.boot.config.property.AliyunSmsProperties;
|
||||||
import com.youlai.boot.system.converter.UserConverter;
|
import com.youlai.boot.system.converter.UserConverter;
|
||||||
@@ -61,8 +62,6 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
|||||||
|
|
||||||
private final UserRoleService userRoleService;
|
private final UserRoleService userRoleService;
|
||||||
|
|
||||||
private final UserConverter userConverter;
|
|
||||||
|
|
||||||
private final RoleMenuService roleMenuService;
|
private final RoleMenuService roleMenuService;
|
||||||
|
|
||||||
private final RoleService roleService;
|
private final RoleService roleService;
|
||||||
@@ -79,6 +78,8 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
|||||||
|
|
||||||
private final TokenService tokenService;
|
private final TokenService tokenService;
|
||||||
|
|
||||||
|
private final UserConverter userConverter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户分页列表
|
* 获取用户分页列表
|
||||||
*
|
*
|
||||||
@@ -214,6 +215,58 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 openid 获取用户认证信息
|
||||||
|
*
|
||||||
|
* @param openid 微信
|
||||||
|
* @return {@link UserAuthInfo}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public UserAuthInfo getUserAuthInfoByOpenId(String openid) {
|
||||||
|
UserAuthInfo userAuthInfo = this.baseMapper.getUserAuthInfoByOpenId(openid);
|
||||||
|
if (userAuthInfo != null) {
|
||||||
|
Set<String> roles = userAuthInfo.getRoles();
|
||||||
|
if (CollectionUtil.isNotEmpty(roles)) {
|
||||||
|
Set<String> perms = roleMenuService.getRolePermsByRoleCodes(roles);
|
||||||
|
userAuthInfo.setPerms(perms);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取最大范围的数据权限
|
||||||
|
Integer dataScope = roleService.getMaximumDataScope(roles);
|
||||||
|
userAuthInfo.setDataScope(dataScope);
|
||||||
|
}
|
||||||
|
return userAuthInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据微信 OpenID 注册或绑定用户
|
||||||
|
* <p>
|
||||||
|
* TODO 根据手机号绑定用户
|
||||||
|
*
|
||||||
|
* @param openId 微信 OpenID
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void registerOrBindWechatUser(String openId) {
|
||||||
|
User user = this.getOne(
|
||||||
|
new LambdaQueryWrapper<User>().eq(User::getOpenid, openId)
|
||||||
|
);
|
||||||
|
if (user == null) {
|
||||||
|
user = new User();
|
||||||
|
user.setNickname("微信用户"); // 默认昵称
|
||||||
|
user.setUsername(openId); // TODO 后续替换为手机号
|
||||||
|
user.setOpenid(openId);
|
||||||
|
user.setGender(0); // 保密
|
||||||
|
user.setUpdateBy(SecurityUtils.getUserId());
|
||||||
|
user.setPassword(SystemConstants.DEFAULT_PASSWORD);
|
||||||
|
this.save(user);
|
||||||
|
// 为了默认系统管理员角色,这里按需调整,实际情况绑定已存在的系统用户,另一种情况是给默认游客角色,然后由系统管理员设置用户的角色
|
||||||
|
UserRole userRole = new UserRole();
|
||||||
|
userRole.setUserId(user.getId());
|
||||||
|
userRole.setRoleId(1L); // TODO 系统管理员
|
||||||
|
userRoleService.save(userRole);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取导出用户列表
|
* 获取导出用户列表
|
||||||
*
|
*
|
||||||
@@ -458,14 +511,4 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
|||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据openId获取用户信息
|
|
||||||
*
|
|
||||||
* @param openId openId
|
|
||||||
* @return {@link User}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public User getUserByOpenId(String openId) {
|
|
||||||
return this.getOne(new LambdaQueryWrapper<User>().eq(User::getOpenId, openId));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@
|
|||||||
</collection>
|
</collection>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<!-- 根据用户名获取认证信息 -->
|
<!-- 根据用户名获取用户的认证信息 -->
|
||||||
<select id="getUserAuthInfo" resultMap="UserAuthMap">
|
<select id="getUserAuthInfo" resultMap="UserAuthMap">
|
||||||
SELECT
|
SELECT
|
||||||
t1.id userId,
|
t1.id userId,
|
||||||
@@ -141,6 +141,24 @@
|
|||||||
t1.username = #{username} AND t1.is_deleted = 0
|
t1.username = #{username} AND t1.is_deleted = 0
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- 根据微信openid获取用户的认证信息 -->
|
||||||
|
<select id="getUserAuthInfoByOpenId" resultMap="UserAuthMap">
|
||||||
|
SELECT
|
||||||
|
t1.id userId,
|
||||||
|
t1.username,
|
||||||
|
t1.nickname,
|
||||||
|
t1.PASSWORD,
|
||||||
|
t1.STATUS,
|
||||||
|
t1.dept_id ,
|
||||||
|
t3.CODE
|
||||||
|
FROM
|
||||||
|
sys_user t1
|
||||||
|
LEFT JOIN sys_user_role t2 ON t2.user_id = t1.id
|
||||||
|
LEFT JOIN sys_role t3 ON t3.id = t2.role_id
|
||||||
|
WHERE
|
||||||
|
t1.username = #{username} AND t1.is_deleted = 0
|
||||||
|
</select>
|
||||||
|
|
||||||
<!-- 获取用户导出列表 -->
|
<!-- 获取用户导出列表 -->
|
||||||
<select id="listExportUsers" resultType="com.youlai.boot.system.model.dto.UserExportDTO">
|
<select id="listExportUsers" resultType="com.youlai.boot.system.model.dto.UserExportDTO">
|
||||||
SELECT
|
SELECT
|
||||||
@@ -196,4 +214,5 @@
|
|||||||
u.id = #{userId} AND u.is_deleted = 0
|
u.id = #{userId} AND u.is_deleted = 0
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
Reference in New Issue
Block a user