first commit
This commit is contained in:
@@ -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 "获取失败";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user