first commit

This commit is contained in:
yy
2026-09-11 17:34:14 +08:00
commit dfb943919d
622 changed files with 26659 additions and 0 deletions
@@ -0,0 +1,285 @@
package com.metalloop.modules.auth.controller;
import cn.dev33.satoken.session.SaSession;
import com.metalloop.common.auth.Authentication;
import com.metalloop.common.auth.handler.LoginSuccessHandler;
import com.metalloop.common.auth.handler.LogoutSuccessHandler;
import com.metalloop.common.auth.handler.PasswordCheckFailureHandler;
import com.metalloop.common.auth.handler.VerifyCodeHandler;
import com.metalloop.common.auth.model.AuthenticationMiniAppRequest;
import com.metalloop.common.auth.password.PasswordDecryptor;
import com.metalloop.common.auth.sso.SsoCodeService;
import com.metalloop.common.auth.sso.model.SsoUserInfo;
import com.metalloop.common.auth.userdetails.UserDetails;
import com.metalloop.common.auth.userdetails.UserDetailsService;
import com.metalloop.common.constant.SecurityConstants;
import com.metalloop.common.enums.LoginType;
import com.metalloop.common.exception.*;
import com.metalloop.common.model.Result;
import com.metalloop.common.utils.IPHelper;
import com.metalloop.modules.auth.model.req.AuthenticationRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 认证授权端点
*
* @author zhaowenhao
* @since 2023-03-19
*/
public class AuthController {
private final Log logger = LogFactory.getLog(getClass());
private final LoginType loginType;
private final UserDetailsService userDetailsService;
private final LoginSuccessHandler loginSuccessHandler;
private final LogoutSuccessHandler logoutSuccessHandler;
private final PasswordDecryptor passwordDecryptor;
private final PasswordEncoder passwordEncoder;
private final VerifyCodeHandler verifyCodeHandler;
private final PasswordCheckFailureHandler passwordCheckFailureHandler;
/**
* 单点登陆授权码服务
*/
private SsoCodeService ssoCodeService;
/**
* 单点登陆授权码超时时间,单位:秒
*/
private int ssoCodeTimeOut = 300;
public void initSso(SsoCodeService ssoCodeService, int ssoCodeTimeOut) {
this.ssoCodeService = ssoCodeService;
this.ssoCodeTimeOut = ssoCodeTimeOut;
}
public AuthController(LoginType loginType, UserDetailsService userDetailsService, LoginSuccessHandler loginSuccessHandler,
LogoutSuccessHandler logoutSuccessHandler, PasswordDecryptor passwordDecryptor, PasswordEncoder passwordEncoder
, VerifyCodeHandler verifyCodeHandler, PasswordCheckFailureHandler passwordCheckFailureHandler) {
this.loginType = loginType;
this.userDetailsService = userDetailsService;
this.loginSuccessHandler = loginSuccessHandler;
this.logoutSuccessHandler = logoutSuccessHandler;
this.passwordDecryptor = passwordDecryptor;
this.passwordEncoder = passwordEncoder;
this.verifyCodeHandler = verifyCodeHandler;
this.passwordCheckFailureHandler = passwordCheckFailureHandler;
}
@ResponseBody
public Object getVerifyCode(@RequestBody @Validated AuthenticationRequest request, HttpServletRequest httpServletRequest) {
// 用户名、密码校验
UserDetails userDetails = loginVerify(request, httpServletRequest);
if (verifyCodeHandler.createVerifyCode(userDetails.getPhoneNum())) {
return Result.ok("发送验证码成功");
} else {
return Result.fail("发送验证码失败");
}
}
@ResponseBody
public Object login(@RequestBody @Validated AuthenticationRequest request, HttpServletRequest httpServletRequest) {
// 用户名、密码校验
UserDetails userDetails = loginVerify(request, httpServletRequest);
// 在sa-token中进行登录操作
loginType.getStpLogic().login(userDetails.getUserId());
String accessToken = loginType.getStpLogic().getTokenValue();
SaSession session = loginType.getStpLogic().getSession();
// 将登录成功的认证信息封装至session中
Authentication authentication = Authentication.withUserDetails(userDetails).accessToken(accessToken).build();
session.set(SecurityConstants.SESSION_KEY_AUTHENTICATION, authentication);
// 登录成功响应结果处理器
return loginSuccessHandler.onSuccess(authentication);
}
/**
* 用户名、密码校验
*/
private UserDetails loginVerify(AuthenticationRequest request, HttpServletRequest servletRequest) {
String username = request.getUsername();
String password = request.getPassword();
// 获取用户ip
String ip = IPHelper.getIpAddr(servletRequest);
// 从 UserDetailsService 加载用户并验证用户名和密码
UserDetails userDetails;
try {
userDetails = userDetailsService.loadUserByUsername(username);
} catch (UsernameNotFoundException ex) {
this.logger.debug("未找到用户: '" + username + "'");
throw new BadCredentialsException("用户名或密码错误");
}
if (userDetails == null) {
this.logger.debug("未找到用户: '" + username + "'");
throw new BadCredentialsException("用户名或密码错误");
}
// 验证用户账号状态
if (!userDetails.isAccountNonLocked()) {
this.logger.debug("账号被锁定");
passwordCheckFailureHandler.checkAccountLockStatus(ip, userDetails);
}
if (!userDetails.isEnabled()) {
this.logger.debug("账号被禁用");
throw new DisabledException("账号被禁用");
}
if (!userDetails.isAccountNonExpired()) {
this.logger.debug("账号已过期");
throw new AccountExpiredException("账号已过期");
}
if (!userDetails.isCredentialsNonExpired()) {
this.logger.debug("密码已过期");
throw new CredentialsExpiredException("密码已过期");
}
// 验证密码
String presentedPassword = password;
// 密码解密
if (passwordDecryptor != null) {
presentedPassword = passwordDecryptor.decrypt(presentedPassword);
}
// 密码匹配
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("密码错误");
throw new BadCredentialsException("用户名或密码错误");
}
return userDetails;
}
/**
* 用户名、密码校验
*/
private UserDetails loginVerifyForMiniApp(AuthenticationMiniAppRequest request, HttpServletRequest servletRequest) {
String username = request.getUsername();
String password = request.getPassword();
// 获取用户ip
String ip = IPHelper.getIpAddr(servletRequest);
// 从 UserDetailsService 加载用户并验证用户名和密码
UserDetails userDetails;
try {
userDetails = userDetailsService.loadUserByUsername(username);
} catch (UsernameNotFoundException ex) {
this.logger.debug("未找到用户: '" + username + "'");
throw new BadCredentialsException("用户名或密码错误");
}
if (userDetails == null) {
this.logger.debug("未找到用户: '" + username + "'");
throw new BadCredentialsException("用户名或密码错误");
}
// 验证用户账号状态
if (!userDetails.isAccountNonLocked()) {
this.logger.debug("账号被锁定");
passwordCheckFailureHandler.checkAccountLockStatus(ip, userDetails);
}
if (!userDetails.isEnabled()) {
this.logger.debug("账号被禁用");
throw new DisabledException("账号被禁用");
}
if (!userDetails.isAccountNonExpired()) {
this.logger.debug("账号已过期");
throw new AccountExpiredException("账号已过期");
}
if (!userDetails.isCredentialsNonExpired()) {
this.logger.debug("密码已过期");
throw new CredentialsExpiredException("密码已过期");
}
// 验证密码
String presentedPassword = password;
// 密码解密
if (passwordDecryptor != null) {
presentedPassword = passwordDecryptor.decrypt(presentedPassword);
}
// 密码匹配
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("密码错误");
throw new BadCredentialsException("用户名或密码错误");
}
return userDetails;
}
@ResponseBody
public Object logout() {
Object o = loginType.getStpLogic().getSession().get(SecurityConstants.SESSION_KEY_AUTHENTICATION);
Authentication authentication = null;
if (o instanceof Authentication) {
authentication = (Authentication) o;
}
loginType.getStpLogic().logout();
// 登出成功响应结果处理器
return logoutSuccessHandler.onSuccess(authentication);
}
@ResponseBody
public Object miniAppLogout() {
Object o = loginType.getStpLogic().getSession().get(SecurityConstants.SESSION_KEY_AUTHENTICATION);
Authentication authentication = null;
if (o instanceof Authentication) {
authentication = (Authentication) o;
}
loginType.getStpLogic().logout();
// 登出成功响应结果处理器
return logoutSuccessHandler.onSuccess(authentication);
}
@ResponseBody
public Object getSsoCode() {
// 验证当前会话是否有效
if (loginType.getStpLogic().isLogin()) {
Object o = loginType.getStpLogic().getSession().get(SecurityConstants.SESSION_KEY_AUTHENTICATION);
Authentication authentication = null;
if (o instanceof Authentication) {
authentication = (Authentication) o;
}
if (authentication != null) {
// 签发授权码
String authorizationCode = ssoCodeService.generateCode(authentication.getUserId(), ssoCodeTimeOut);
return Result.ok(authorizationCode);
}
}
return Result.fail("当前会话无效");
}
@ResponseBody
public Object getSsoUserInfo(@RequestParam("code") String code) {
// 验证授权码并获取绑定的用户信息
SsoUserInfo userInfo = ssoCodeService.validateCode(code);
// 登录成功响应结果处理器
return Result.ok(userInfo);
}
}
@@ -0,0 +1,37 @@
package com.metalloop.modules.auth.controller;
import com.metalloop.common.auth.encrypt.KeyProperties;
import com.metalloop.common.auth.encrypt.KeyStoreKeyFactory;
import org.springframework.web.bind.annotation.ResponseBody;
import java.security.KeyPair;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Base64;
/**
* 获取公钥端点
*
* @author zhaowenhao
* @since 2023-03-19
*/
public class RsaPublicKeyController {
private final KeyProperties keyProperties;
public RsaPublicKeyController(KeyProperties keyProperties) {
this.keyProperties = keyProperties;
}
@ResponseBody
public String getPublicKey() {
if (keyProperties != null) {
KeyPair keyPair = new KeyStoreKeyFactory(keyProperties.getKeyStore().getLocation(), keyProperties.getKeyStore().getSecret().toCharArray()).getKeyPair(keyProperties.getKeyStore().getAlias());
PublicKey publicKey = keyPair.getPublic();
RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
byte[] publicKeyBytes = rsaPublicKey.getEncoded();
return Base64.getEncoder().encodeToString(publicKeyBytes);
}
return "获取失败";
}
}