oauth-重写-Oauth异常

This commit is contained in:
2025-09-21 22:07:48 +08:00
parent a8aa415858
commit cce6bfd896
17 changed files with 241 additions and 501 deletions

View File

@@ -640,7 +640,7 @@ public enum BusiStatus {
DEVICE_ERROR(500, "设备为空"),
REGISTER_NETEASE_FAIL(500, "注册云信IM账号失败"),
UPDATE_NETEASE_ROOM_FAIL(500, "云信聊天室信息更新失败"),

View File

@@ -1,84 +0,0 @@
package com.accompany.oauth.constant;
/**
* OAuth常量类
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
public final class OAuthConstants {
/**
* Token相关常量
*/
public static final class Token {
public static final String BEARER_PREFIX = "Bearer ";
public static final String ACCESS_TOKEN = "access_token";
public static final String REFRESH_TOKEN = "refresh_token";
public static final String TOKEN_TYPE = "token_type";
public static final String EXPIRES_IN = "expires_in";
public static final String SCOPE = "scope";
// Redis Key前缀
public static final String ACCESS_TOKEN_PREFIX = "oauth:access_token:";
public static final String REFRESH_TOKEN_PREFIX = "oauth:refresh_token:";
public static final String USER_TOKEN_PREFIX = "oauth:user_token:";
private Token() {}
}
/**
* 授权类型常量
*/
public static final class GrantType {
public static final String PASSWORD = "password";
public static final String VERIFY_CODE = "verify_code";
public static final String EMAIL = "email";
public static final String OPENID = "openid";
public static final String REFRESH_TOKEN = "refresh_token";
private GrantType() {}
}
/**
* HTTP头常量
*/
public static final class Headers {
public static final String AUTHORIZATION = "Authorization";
public static final String CLIENT_ID = "Client-Id";
public static final String DEVICE_ID = "Device-Id";
public static final String USER_AGENT = "User-Agent";
private Headers() {}
}
/**
* 权限范围常量
*/
public static final class Scope {
public static final String READ = "read";
public static final String WRITE = "write";
public static final String ALL = "read write";
private Scope() {}
}
/**
* 错误码常量
*/
public static final class ErrorCode {
public static final String INVALID_REQUEST = "invalid_request";
public static final String INVALID_CLIENT = "invalid_client";
public static final String INVALID_GRANT = "invalid_grant";
public static final String UNAUTHORIZED_CLIENT = "unauthorized_client";
public static final String UNSUPPORTED_GRANT_TYPE = "unsupported_grant_type";
public static final String INVALID_SCOPE = "invalid_scope";
public static final String ACCESS_DENIED = "access_denied";
public static final String INVALID_TOKEN = "invalid_token";
public static final String TOKEN_EXPIRED = "token_expired";
private ErrorCode() {}
}
private OAuthConstants() {}
}

View File

@@ -1,36 +0,0 @@
package com.accompany.oauth.exception;
import com.accompany.oauth.constant.OAuthConstants;
/**
* 认证异常
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
public class AuthenticationException extends OAuthException {
public AuthenticationException(String errorDescription) {
super(OAuthConstants.ErrorCode.ACCESS_DENIED, errorDescription);
}
public AuthenticationException(String errorDescription, Throwable cause) {
super(OAuthConstants.ErrorCode.ACCESS_DENIED, errorDescription, cause);
}
public static AuthenticationException invalidCredentials() {
return new AuthenticationException("用户名或密码错误");
}
public static AuthenticationException userNotFound() {
return new AuthenticationException("用户不存在");
}
public static AuthenticationException userFrozen() {
return new AuthenticationException("用户已被冻结");
}
public static AuthenticationException invalidVerifyCode() {
return new AuthenticationException("验证码错误或已过期");
}
}

View File

@@ -1,5 +1,9 @@
package com.accompany.oauth.exception;
import com.accompany.common.status.BusiStatus;
import java.util.Map;
/**
* OAuth认证异常基类
*
@@ -8,26 +12,39 @@ package com.accompany.oauth.exception;
*/
public class OAuthException extends RuntimeException {
private final String errorCode;
private final String errorDescription;
private BusiStatus busiStatus;
private Map<String, String> additionalInformation;
public OAuthException(String errorCode, String errorDescription) {
super(errorDescription);
this.errorCode = errorCode;
this.errorDescription = errorDescription;
public OAuthException(BusiStatus busiStatus) {
super(busiStatus.getMessage());
this.busiStatus = busiStatus;
}
public OAuthException(String errorCode, String errorDescription, Throwable cause) {
super(errorDescription, cause);
this.errorCode = errorCode;
this.errorDescription = errorDescription;
public OAuthException(BusiStatus busiStatus, String message) {
super(message);
this.busiStatus = busiStatus;
}
public String getErrorCode() {
return errorCode;
public OAuthException(BusiStatus busiStatus, Map<String, String> additionalInformation) {
super(busiStatus.getMessage());
this.busiStatus = busiStatus;
this.additionalInformation = additionalInformation;
}
public String getErrorDescription() {
return errorDescription;
public BusiStatus getBusiStatus() {
return busiStatus;
}
public void setBusiStatus(BusiStatus busiStatus) {
this.busiStatus = busiStatus;
}
public Map<String, String> getAdditionalInformation() {
return additionalInformation;
}
public void setAdditionalInformation(Map<String, String> additionalInformation) {
this.additionalInformation = additionalInformation;
}
}

View File

@@ -1,36 +0,0 @@
package com.accompany.oauth.exception;
import com.accompany.oauth.constant.OAuthConstants;
/**
* Token异常
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
public class TokenException extends OAuthException {
public TokenException(String errorCode, String errorDescription) {
super(errorCode, errorDescription);
}
public TokenException(String errorCode, String errorDescription, Throwable cause) {
super(errorCode, errorDescription, cause);
}
public static TokenException invalidToken() {
return new TokenException(OAuthConstants.ErrorCode.INVALID_TOKEN, "无效的Token");
}
public static TokenException tokenExpired() {
return new TokenException(OAuthConstants.ErrorCode.TOKEN_EXPIRED, "Token已过期");
}
public static TokenException invalidRefreshToken() {
return new TokenException(OAuthConstants.ErrorCode.INVALID_GRANT, "无效的刷新Token");
}
public static TokenException tokenGenerationFailed() {
return new TokenException(OAuthConstants.ErrorCode.INVALID_REQUEST, "Token生成失败");
}
}

View File

@@ -1,5 +1,7 @@
package com.accompany.oauth.model;
import lombok.Data;
import java.util.Date;
import java.util.Set;
@@ -9,6 +11,7 @@ import java.util.Set;
* @author Accompany OAuth Team
* @since 1.0.0
*/
@Data
public class TokenValidation {
/**
@@ -40,9 +43,7 @@ public class TokenValidation {
* 错误信息
*/
private String errorMessage;
public TokenValidation() {
}
public TokenValidation(boolean valid) {
this.valid = valid;
@@ -55,70 +56,5 @@ public class TokenValidation {
validation.setClientId(clientId);
return validation;
}
public static TokenValidation invalid(String errorMessage) {
TokenValidation validation = new TokenValidation(false);
validation.setErrorMessage(errorMessage);
return validation;
}
public boolean isValid() {
return valid;
}
public void setValid(boolean valid) {
this.valid = valid;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Set<String> getScopes() {
return scopes;
}
public void setScopes(Set<String> scopes) {
this.scopes = scopes;
}
public Date getExpirationTime() {
return expirationTime;
}
public void setExpirationTime(Date expirationTime) {
this.expirationTime = expirationTime;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
@Override
public String toString() {
return "TokenValidation{" +
"valid=" + valid +
", userId=" + userId +
", scopes=" + scopes +
", expirationTime=" + expirationTime +
", clientId='" + clientId + '\'' +
", errorMessage='" + errorMessage + '\'' +
'}';
}
}

View File

@@ -4,11 +4,11 @@ import com.accompany.common.constant.Constant;
import com.accompany.common.device.DeviceInfo;
import com.accompany.common.redis.RedisKey;
import com.accompany.common.status.BusiStatus;
import com.accompany.core.enumeration.PartitionEnum;
import com.accompany.core.exception.ServiceException;
import com.accompany.core.model.Account;
import com.accompany.core.model.AccountLoginRecord;
import com.accompany.core.model.Users;
import com.accompany.core.mybatismapper.PrettyNumberRecordMapper;
import com.accompany.core.service.SysConfService;
import com.accompany.core.service.account.AccountBlockCheckService;
import com.accompany.core.service.account.AccountService;
@@ -17,8 +17,10 @@ import com.accompany.core.service.account.UserAppService;
import com.accompany.core.service.common.JedisService;
import com.accompany.core.service.region.RegionNetworkService;
import com.accompany.core.service.user.UsersBaseService;
import com.accompany.core.util.I18NMessageSourceUtil;
import com.accompany.email.service.EmailService;
import com.accompany.oauth.constant.LoginTypeEnum;
import com.accompany.oauth.exception.OAuthException;
import com.accompany.sms.service.SmsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -28,6 +30,9 @@ import org.springframework.util.StringUtils;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import static com.accompany.core.enumeration.I18nAlertEnum.ACCOUNT_LOGIN_BLOCK_MSG;
@Slf4j
@Service
@@ -82,12 +87,9 @@ public class AccountLoginService {
Long blockEndTime = accountBlockCheckService.checkReturnEndTime(account.getErbanNo(), account.getPhone(), account.getEmail(), deviceId, ip);
//检查账号、设备号、号段是否封禁
if (null != blockEndTime){
//todo
//CustomOAuth2Exception exception = new CustomOAuth2Exception(CustomOAuth2Exception.ACCOUNT_ERROR, "");
//Integer partitionId = null != users? users.getPartitionId(): PartitionEnum.ENGLISH.getId();
//exception.addAdditionalInformation("reason", I18NMessageSourceUtil.getMessage(ACCOUNT_LOGIN_BLOCK_MSG, new Object[]{account.getErbanNo()}, partitionId));
//exception.addAdditionalInformation("date", String.valueOf(blockEndTime));
//throw exception;
Integer partitionId = null != users? users.getPartitionId(): PartitionEnum.ENGLISH.getId();
Map<String, String> tipMap = Map.of("reason", I18NMessageSourceUtil.getMessage(ACCOUNT_LOGIN_BLOCK_MSG, new Object[]{users.getErbanNo()}, partitionId), "date", String.valueOf(blockEndTime));
throw new OAuthException(BusiStatus.ACCOUNT_ERROR, tipMap);
}
accountManageService.checkAccountCancel(uid);
@@ -158,18 +160,14 @@ public class AccountLoginService {
return;
}
if (!StringUtils.hasText(code)) {
//todo
//throw new CustomOAuth2Exception(CustomOAuth2Exception.VERIFY_CODE_ERROR,
// BusiStatus.VERIFY_CODE_ERROR.getReasonPhrase());
throw new OAuthException(BusiStatus.VERIFY_CODE_ERROR);
}
boolean verifyResult = LoginTypeEnum.PHONE.getValue() == loginType.getValue()?
smsService.verifySmsCode(account.getPhone(), code):
emailService.verifyEmailCode(account.getEmail(), code);
if (!verifyResult) {
//todo
//throw new CustomOAuth2Exception(CustomOAuth2Exception.VERIFY_CODE_ERROR,
// BusiStatus.VERIFY_CODE_ERROR.getReasonPhrase());
throw new OAuthException(BusiStatus.VERIFY_CODE_ERROR);
}
}
@@ -189,8 +187,7 @@ public class AccountLoginService {
String countValue = jedisService.hget(cacheKey, username);
Long currCount = StringUtils.isEmpty(countValue) ? 0L : Long.parseLong(countValue);
if (currCount >= maxCount) {
//todo
//throw new CustomOAuth2Exception(CustomOAuth2Exception.PWD_WRONG_OVER_LIMIT, BusiStatus.PWD_WRONG_OVER_LIMIT.getReasonPhrase());
throw new OAuthException(BusiStatus.PWD_WRONG_OVER_LIMIT);
}
if (!password.equals(accountPassword)) {
@@ -200,12 +197,10 @@ public class AccountLoginService {
}
if (currCount >= maxCount) {
//todo
//throw new CustomOAuth2Exception(CustomOAuth2Exception.PWD_WRONG_OVER_LIMIT, BusiStatus.PWD_WRONG_OVER_LIMIT.getReasonPhrase());
throw new OAuthException(BusiStatus.PWD_WRONG_OVER_LIMIT);
} else {
Long remainCount = maxCount - currCount;
//todo
//throw new CustomOAuth2Exception(CustomOAuth2Exception.PASSWORD_ERROR, String.format(BusiStatus.PASSWORD_ERROR_COUNT.getReasonPhrase(), remainCount));
throw new OAuthException(BusiStatus.PASSWORD_ERROR_COUNT, String.format(BusiStatus.PASSWORD_ERROR_COUNT.getMessage(), remainCount));
}
}
}

View File

@@ -29,9 +29,9 @@ import com.accompany.email.service.EmailService;
import com.accompany.oauth.constant.LoginTypeEnum;
import com.accompany.oauth.dto.DayIpMaxRegisterLimitConfig;
import com.accompany.oauth.dto.RepeatedDeviceIpRegisterLimitConfig;
import com.accompany.oauth.exception.OAuthException;
import com.accompany.sms.service.SmsService;
import com.alibaba.fastjson.JSON;
import com.google.gson.Gson;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -39,6 +39,7 @@ import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Date;
import java.util.Map;
/**
* @author liuguofu
@@ -210,22 +211,19 @@ public class AccountManageService {
private void checkRegisterLimit(String deviceId, String ipAddress) {
if (!StringUtils.hasText(deviceId)) {
//todo
//throw new CustomOAuth2Exception(CustomOAuth2Exception.SIGN_IP_TO_OFTEN, BusiStatus.DEVICE_ERROR.getReasonPhrase());
throw new OAuthException(BusiStatus.DEVICE_ERROR);
}
RepeatedDeviceIpRegisterLimitConfig repeatedConfig = getRepeatedDeviceIpLimitConfig();
if (repeatedConfig.isOpen()) {
long repeatedDeviceNum = accountService.lambdaQuery().eq(Account::getDeviceId, deviceId).count();
if (repeatedDeviceNum >= repeatedConfig.getRepeatedDeviceNumLimit()) {
//todo
//throw new CustomOAuth2Exception(CustomOAuth2Exception.SIGN_IP_TO_OFTEN, BusiStatus.REGISTER_FREQUENT.getReasonPhrase());
throw new OAuthException(BusiStatus.REGISTER_FREQUENT);
}
long repeatedIpNum = accountService.lambdaQuery().eq(Account::getRegisterIp, ipAddress).count();
if (repeatedIpNum >= repeatedConfig.getRepeatedIpNumLimit()) {
//todo
//throw new CustomOAuth2Exception(CustomOAuth2Exception.SIGN_IP_TO_OFTEN, BusiStatus.REGISTER_FREQUENT.getReasonPhrase());
throw new OAuthException(BusiStatus.REGISTER_FREQUENT);
}
}
@@ -234,16 +232,11 @@ public class AccountManageService {
if (config.getOpen()) {
long count = accountService.getRegisterIpCountByOneDay(ipAddress);
if (count >= config.getMax()) {
//todo
//throw new CustomOAuth2Exception(CustomOAuth2Exception.SIGN_IP_TO_OFTEN, BusiStatus.REGISTER_FREQUENT.getReasonPhrase());
throw new OAuthException(BusiStatus.REGISTER_FREQUENT);
}
}
}
private String encryptPassword(String password) {
return MD5.getMD5(password);
}
/**
* 通过手机号码注册,独立账号系统,不掺杂业务
*
@@ -280,7 +273,7 @@ public class AccountManageService {
if (tokenRet.getCode() != 200) {
log.error("手机号码phone=" + phone + "注册异常,异常原因code=" + tokenRet.getCode());
log.info("手机号码phone=" + phone + "注册异常,异常原因code=" + tokenRet.getCode());
throw new ServiceException("手机号码phone=" + phone + "注册异常,异常原因code=" + tokenRet.getCode());
throw new OAuthException(BusiStatus.REGISTER_NETEASE_FAIL);
}
return account;
@@ -294,7 +287,7 @@ public class AccountManageService {
Account account = new Account();
account.setEmail(email);
if (StringUtils.hasText(password)) {
account.setPassword(encryptPassword(password));
account.setPassword(MD5.getMD5(password));
}
account.setNeteaseToken(UUIDUtil.get());
account.setLastLoginTime(date);
@@ -312,7 +305,7 @@ public class AccountManageService {
TokenRet tokenRet = netEaseService.createNetEaseAcc(uidStr, account.getNeteaseToken(), "", "", null);
if (tokenRet.getCode() != 200) {
log.error("邮件email {} 注册异常,异常原因code {}", email, tokenRet.getCode());
throw new ServiceException(BusiStatus.SERVERBUSY);
throw new OAuthException(BusiStatus.REGISTER_NETEASE_FAIL);
}
return account;
@@ -350,27 +343,27 @@ public class AccountManageService {
if (phone.contains("*")) {
Account account = accountService.getById(uid);
if (account == null) {
throw new ServiceException(BusiStatus.USER_NOT_EXISTED);
throw new OAuthException(BusiStatus.USER_NOT_EXISTED);
}
phone = account.getPhone();
if (!CommonUtil.checkPhoneFormat(account.getPhoneAreaCode(), account.getPhone())) {
throw new ServiceException(BusiStatus.ACCOUNT_NOT_BIND_PHONE);
throw new OAuthException(BusiStatus.PHONE_INVALID);
}
}
long count = accountService.countByPhone(phone);
if (count > 1L) {
throw new ServiceException(BusiStatus.PHONE_BIND_TOO_MANY_ACCOUNT);
throw new OAuthException(BusiStatus.PHONE_BIND_TOO_MANY_ACCOUNT);
}
Account account = accountService.getAccountByPhone(phone);
if (null == account || (uid != null && !account.getUid().equals(uid))) {
throw new ServiceException(BusiStatus.PHONE_BIND_ERROR);
throw new OAuthException(BusiStatus.PHONE_BIND_ERROR);
}
//检验验证码
if (!smsService.verifySmsCodeByCache(phone, resetCode)) {
throw new ServiceException(BusiStatus.INVALID_IDENTIFYING_CODE);
throw new OAuthException(BusiStatus.INVALID_IDENTIFYING_CODE);
}
accountService.resetAccountPwd(account.getUid(), password);
@@ -397,19 +390,19 @@ public class AccountManageService {
long count = accountService.countByEmail(email);
if (count > 1L) {
throw new ServiceException(BusiStatus.EMAIL_BIND_TOO_MANY_ACCOUNT);
throw new OAuthException(BusiStatus.EMAIL_BIND_TOO_MANY_ACCOUNT);
}
Account account = accountService.getAccountByEmail(email);
if (null == account) {
throw new ServiceException(BusiStatus.ACCOUNT_NOT_BIND_EMAIL);
throw new OAuthException(BusiStatus.ACCOUNT_NOT_BIND_EMAIL);
} else if (null != uid && !account.getUid().equals(uid)) {
throw new ServiceException(BusiStatus.ACCOUNT_BIND_EMAIL_DIFF);
throw new OAuthException(BusiStatus.ACCOUNT_BIND_EMAIL_DIFF);
}
//检验验证码
if (!emailService.verifyCodeByCache(email, code)) {
throw new ServiceException(BusiStatus.INVALID_IDENTIFYING_EMAIL_CODE);
throw new OAuthException(BusiStatus.INVALID_IDENTIFYING_EMAIL_CODE);
}
accountService.resetAccountPwd(account.getUid(), password);
@@ -429,7 +422,7 @@ public class AccountManageService {
}
String oldPwd = account.getPassword();
password = encryptPassword(password);
password = MD5.getMD5(password);
if (!StringUtils.hasText(password) || !password.equals(oldPwd)) {
throw new ServiceException(BusiStatus.OLD_PASSWORD_ERROR);
}
@@ -480,29 +473,25 @@ public class AccountManageService {
if (ObjectUtil.isNull(userCancelRecord)) {
//获取不到注销账号信息
log.info("获取不到用户{}注销信息", uid);
//todo
//throw new CustomOAuth2Exception(CustomOAuth2Exception.ACCOUNT_CANCEL_INFO_NOT_EXIST, BusiStatus.ACCOUNT_CANCEL_INFO_NOT_EXIST.getReasonPhrase());
throw new OAuthException(BusiStatus.ACCOUNT_CANCEL_INFO_NOT_EXIST);
}
log.info("检测到注销账号{}昵称{}于{}尝试登录", users.getErbanNo(), userCancelRecord.getNick(), DateTimeUtil.convertDate(userCancelRecord.getUpdateTime()));
//todo
//CustomOAuth2Exception exception = new CustomOAuth2Exception(CustomOAuth2Exception.ACCOUNT_CANCEL, BusiStatus.ACCOUNT_CANCEL.getReasonPhrase());
//exception.addAdditionalInformation("erbanNo", String.valueOf(users.getErbanNo()));
//exception.addAdditionalInformation("cancelDate", String.valueOf(userCancelRecord.getUpdateTime().getTime()));
//exception.addAdditionalInformation("nick", userCancelRecord.getNick());
//exception.addAdditionalInformation("avatar", userCancelRecord.getAvatar());
Integer surviveTime = Integer.valueOf(sysConfService.getDefaultSysConfValueById(Constant.SysConfId.USER_RECOVER_CREDENTIALS_SURVIVE_TIME, String.valueOf(3 * 60)));
//写入凭证标识
jedisService.setex(RedisKey.cancel_user_recover_credentials.getKey(String.valueOf(users.getErbanNo())), surviveTime, String.valueOf(uid));
//throw exception;
Map<String, String> tipMap = Map.of("erbanNo", String.valueOf(users.getErbanNo()), "cancelDate", String.valueOf(userCancelRecord.getUpdateTime().getTime()),
"nick", userCancelRecord.getNick(), "avatar", userCancelRecord.getAvatar());
throw new OAuthException(BusiStatus.ACCOUNT_CANCEL, tipMap);
}
private DayIpMaxRegisterLimitConfig getIpMaxLimitConfig() {
String config = sysConfService.getSysConfValueById(Constant.SysConfId.IP_MAX_REGISTER_LIMIT_CONFIG);
if (!StringUtils.hasText(config)) {
throw new ServiceException(BusiStatus.ALREADY_NOTEXISTS_CONFIG);
throw new OAuthException(BusiStatus.ALREADY_NOTEXISTS_CONFIG);
}
return JSON.parseObject(config, DayIpMaxRegisterLimitConfig.class);
}
@@ -510,7 +499,7 @@ public class AccountManageService {
private RepeatedDeviceIpRegisterLimitConfig getRepeatedDeviceIpLimitConfig() {
String config = sysConfService.getSysConfValueById(Constant.SysConfId.REPEATED_DEVICE_IP_REGISTER_LIMIT_CONFIG);
if (!StringUtils.hasText(config)) {
throw new ServiceException(BusiStatus.ALREADY_NOTEXISTS_CONFIG);
throw new OAuthException(BusiStatus.ALREADY_NOTEXISTS_CONFIG);
}
return JSON.parseObject(config, RepeatedDeviceIpRegisterLimitConfig.class);
}

View File

@@ -10,7 +10,7 @@ import com.accompany.core.model.Account;
import com.accompany.core.util.KeyStore;
import com.accompany.core.util.MD5;
import com.accompany.oauth.constant.LoginTypeEnum;
import com.accompany.oauth.exception.AuthenticationException;
import com.accompany.oauth.exception.OAuthException;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -22,20 +22,20 @@ import org.springframework.stereotype.Service;
* @since 1.0.0
*/
@Service
public class UserService {
public class AuthenticateService {
@Autowired
private AccountManageService accountManageService;
@Autowired
private AccountLoginService accountLoginService;
/**
* 通过密码认证用户
*
* @param username 手机号
* @param password 密码
* @return 用户详情
* @throws AuthenticationException 认证失败
* @throws OAuthException 认证失败
*/
@SneakyThrows
public Account authenticateByPassword(String username, String password) {
@@ -45,9 +45,7 @@ public class UserService {
Account account = accountManageService.getAccountPyUsername(username, password);
if (account == null) {
// todo
//throw new CustomOAuth2Exception(CustomOAuth2Exception.USER_NOT_EXISTED,
// BusiStatus.USER_NOT_EXISTED.getReasonPhrase());
throw new OAuthException(BusiStatus.USER_NOT_EXISTED);
}
accountLoginService.validPwd(username, password, account.getPassword());
@@ -75,7 +73,7 @@ public class UserService {
* @param code 验证码
* @param deviceInfo
* @return 用户详情
* @throws AuthenticationException 认证失败
* @throws OAuthException 认证失败
*/
public Account authenticateByEmail(String email, String code, DeviceInfo deviceInfo) {
Account account = accountManageService.getOrGenAccountByEmail(email, code, deviceInfo, deviceInfo.getClientIp());
@@ -92,7 +90,7 @@ public class UserService {
* @param openId OpenID
* @param type 第三方类型 (1-微信, 2-Apple等)
* @return 用户详情
* @throws AuthenticationException 认证失败
* @throws OAuthException 认证失败
*/
public Account authenticateByOpenId(Byte type, String openId, String unionId, String idToken, DeviceInfo deviceInfo) {
return accountManageService.getOrGenAccountByOpenid(type, openId, unionId, idToken, deviceInfo);
@@ -103,7 +101,7 @@ public class UserService {
*
* @param uid 用户ID
* @return 用户详情
* @throws AuthenticationException 用户不存在
* @throws OAuthException 用户不存在
*/
public Account getUserByUid(Long uid) {
return accountManageService.getAccountPyUid(uid);
@@ -112,15 +110,15 @@ public class UserService {
/**
* 检查用户状态是否可用
*
* @throws AuthenticationException 用户不可用
* @throws OAuthException 用户不可用
*/
public void checkUserStatus(Account account) {
if (Constant.AccountState.block.equals(account.getState())) {
throw AuthenticationException.userFrozen();
throw new OAuthException(BusiStatus.ACCOUNT_BLOCK_ERROR);
}
if (Constant.AccountState.cancel.equals(account.getState())) {
throw AuthenticationException.userNotFound();
throw new OAuthException(BusiStatus.ACCOUNT_CANCEL);
}
}

View File

@@ -1,10 +1,11 @@
package com.accompany.oauth.service;
import com.accompany.common.status.BusiStatus;
import com.accompany.core.model.Account;
import com.accompany.oauth.constant.GrantTypeEnum;
import com.accompany.oauth.constant.LoginTypeEnum;
import com.accompany.oauth.dto.AuthResult;
import com.accompany.oauth.exception.AuthenticationException;
import com.accompany.oauth.exception.OAuthException;
import com.accompany.oauth.token.TokenManager;
import com.accompany.oauth.model.TokenPair;
import com.accompany.common.device.DeviceInfo;
@@ -21,7 +22,7 @@ import org.springframework.stereotype.Service;
public class AuthenticationService {
@Autowired
private UserService userService;
private AuthenticateService authenticateService;
@Autowired
private AccountLoginService accountLoginService;
@Autowired
@@ -35,7 +36,7 @@ public class AuthenticationService {
* @param code 验证码
* @param deviceInfo 设备信息
* @return 认证结果
* @throws AuthenticationException 认证失败
* @throws OAuthException 认证失败
*/
public AuthResult authenticate(String grantType,
String username, String password,
@@ -50,22 +51,22 @@ public class AuthenticationService {
switch (grantTypeEnum) {
case PASSWORD:
loginTypeEnum = LoginTypeEnum.ID;
account = userService.authenticateByPassword(username, password);
account = authenticateService.authenticateByPassword(username, password);
break;
case VERIFY_CODE:
loginTypeEnum = LoginTypeEnum.PHONE;
account = userService.authenticateByVerifyCode(phone, phoneAreaCode, code, deviceInfo);
account = authenticateService.authenticateByVerifyCode(phone, phoneAreaCode, code, deviceInfo);
break;
case EMAIL:
loginTypeEnum = LoginTypeEnum.EMAIL;
account = userService.authenticateByEmail(email, code, deviceInfo);
account = authenticateService.authenticateByEmail(email, code, deviceInfo);
break;
default:
throw new AuthenticationException("不支持的认证类型: " + grantType);
throw new OAuthException(BusiStatus.INVALID_GRANT);
}
// 2. 检查用户状态
userService.checkUserStatus(account);
authenticateService.checkUserStatus(account);
accountLoginService.login(account, loginTypeEnum, deviceInfo, null);
@@ -77,22 +78,22 @@ public class AuthenticationService {
}
/**
* 第三方登录认证 (兼容OAuth2格式)
* 第三方认证
*
* @param openId 第三方OpenID
* @param unionId UnionID(可选)
* @param idToken ID Token(可选)
* @param type 第三方类型
* @param openId OpenID
* @param unionId UnionID
* @param idToken ID Token
* @param deviceInfo 设备信息
* @return 认证结果
* @throws AuthenticationException 认证失败
* @throws OAuthException 认证失败
*/
public AuthResult authenticateByThirdParty(Byte type, String openId, String unionId, String idToken, DeviceInfo deviceInfo) {
// 1. 第三方认证
Account account = userService.authenticateByOpenId(type, openId, unionId, idToken, deviceInfo);
Account account = authenticateService.authenticateByOpenId(type, openId, unionId, idToken, deviceInfo);
// 2. 检查用户状态
userService.checkUserStatus(account);
authenticateService.checkUserStatus(account);
// 4. 生成Token
TokenPair tokenPair = tokenManager.generateToken(account.getUid());

View File

@@ -2,6 +2,7 @@ package com.accompany.oauth.ticket;
import com.accompany.common.device.DeviceInfo;
import com.accompany.common.redis.RedisKey;
import com.accompany.common.status.BusiStatus;
import com.accompany.core.model.Account;
import com.accompany.core.model.AccountLoginRecord;
import com.accompany.core.service.account.AccountService;
@@ -9,11 +10,11 @@ import com.accompany.core.service.account.LoginRecordService;
import com.accompany.core.service.account.UserAppService;
import com.accompany.oauth.constant.LoginTypeEnum;
import com.accompany.oauth.dto.TicketResponseVO;
import com.accompany.oauth.exception.TokenException;
import com.accompany.oauth.exception.OAuthException;
import com.accompany.oauth.token.TokenManager;
import com.accompany.oauth.model.TokenValidation;
import com.accompany.oauth.service.AccountLoginService;
import com.accompany.oauth.service.UserService;
import com.accompany.oauth.service.AuthenticateService;
import com.accompany.oauth.util.JwtUtil;
import org.redisson.api.RMapCache;
import org.redisson.api.RedissonClient;
@@ -39,7 +40,7 @@ public class TicketService implements InitializingBean {
private TokenManager tokenManager;
@Autowired
private UserService userService;
private AuthenticateService authenticateService;
@Autowired
private JwtUtil jwtUtil;
@@ -67,12 +68,12 @@ public class TicketService implements InitializingBean {
// 1. 验证访问令牌
TokenValidation validation = tokenManager.validateToken(accessToken);
if (!validation.isValid()) {
throw TokenException.invalidToken();
throw new OAuthException(BusiStatus.INVALID_TOKEN);
}
// 2. 获取用户信息
Account account = userService.getUserByUid(validation.getUserId());
userService.checkUserStatus(account);
Account account = authenticateService.getUserByUid(validation.getUserId());
authenticateService.checkUserStatus(account);
Date expiration = validation.getExpirationTime();

View File

@@ -1,8 +1,9 @@
package com.accompany.oauth.token;
import com.accompany.common.redis.RedisKey;
import com.accompany.common.status.BusiStatus;
import com.accompany.core.util.StringUtils;
import com.accompany.oauth.exception.TokenException;
import com.accompany.oauth.exception.OAuthException;
import com.accompany.oauth.model.TokenPair;
import com.accompany.oauth.model.TokenValidation;
import com.accompany.oauth.util.JwtUtil;
@@ -61,27 +62,21 @@ public class TokenManager implements InitializingBean {
* @return 验证结果
*/
public TokenValidation validateToken(String token) {
try {
// 首先验证JWT格式和签名
Claims claims = jwtUtil.validateAndParseToken(token);
// 提取token信息
Long uid = Long.valueOf(claims.getSubject());
// 首先验证JWT格式和签名
Claims claims = jwtUtil.validateAndParseToken(token);
// 提取token信息
Long uid = Long.valueOf(claims.getSubject());
// 检查Redis中是否存在该token
String cacheToken = tokenCache.get(uid);
if (StringUtils.isBlank(cacheToken) || !cacheToken.equals(token)) {
return TokenValidation.invalid("Token不存在或已被撤销");
}
String clientId = claims.get("client_id", String.class);
Date expirationTime = claims.getExpiration();
return TokenValidation.valid(uid, expirationTime, clientId);
} catch (TokenException e) {
return TokenValidation.invalid(e.getErrorDescription());
} catch (Exception e) {
return TokenValidation.invalid("Token验证失败: " + e.getMessage());
// 检查Redis中是否存在该token
String cacheToken = tokenCache.get(uid);
if (StringUtils.isBlank(cacheToken) || !cacheToken.equals(token)) {
throw new OAuthException(BusiStatus.INVALID_TOKEN);
}
String clientId = claims.get("client_id", String.class);
Date expirationTime = claims.getExpiration();
return TokenValidation.valid(uid, expirationTime, clientId);
}
/**

View File

@@ -1,8 +1,9 @@
package com.accompany.oauth.util;
import com.accompany.common.status.BusiStatus;
import com.accompany.common.utils.UUIDUtil;
import com.accompany.oauth.config.OAuthConfig;
import com.accompany.oauth.exception.TokenException;
import com.accompany.oauth.exception.OAuthException;
import com.accompany.oauth.ticket.Ticket;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
@@ -55,7 +56,7 @@ public class JwtUtil implements InitializingBean {
.claim("uid", userId) // 兼容OAuth2
.claim("user_name", userId.toString()) // 兼容OAuth2
.claim("authorities", "oauth2") // 兼容OAuth2
.claim("jti", generateJti()) // 兼容OAuth2
.claim("jti", UUIDUtil.get()) // 兼容OAuth2
.claim("scope", "read write")
.signWith(secretKey, SignatureAlgorithm.HS256);
@@ -112,7 +113,7 @@ public class JwtUtil implements InitializingBean {
*
* @param token JWT令牌
* @return Claims对象
* @throws TokenException 令牌无效或过期
* @throws OAuthException 令牌无效或过期
*/
public Claims validateAndParseToken(String token) {
try {
@@ -122,21 +123,12 @@ public class JwtUtil implements InitializingBean {
.parseClaimsJws(token)
.getBody();
} catch (ExpiredJwtException e) {
throw TokenException.tokenExpired();
throw new OAuthException(BusiStatus.ACCESS_TOKEN_HAS_EXPIRED);
} catch (JwtException e) {
throw TokenException.invalidToken();
throw new OAuthException(BusiStatus.INVALID_TOKEN);
}
}
/**
* 生成JWT Token ID (兼容OAuth2)
*
* @return JTI
*/
private String generateJti() {
return UUIDUtil.get();
}
@Override
public void afterPropertiesSet() {
// 确保密钥长度足够

View File

@@ -1,5 +1,7 @@
package com.accompany.oauth;
import org.dromara.dynamictp.spring.annotation.EnableDynamicTp;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@@ -10,12 +12,15 @@ import org.springframework.context.annotation.ComponentScan;
* @author Accompany OAuth Team
* @since 1.0.0
*/
@EnableDynamicTp
@SpringBootApplication
@ComponentScan("com.accompany")
@ComponentScan(basePackages = {
"com.accompany.oauth", // OAuth模块
"com.accompany.common", // 公共模块
"com.accompany.core" // 核心模块
})
@MapperScan({"com.accompany.*.mapper","com.accompany.*.mybatismapper"})
public class OAuthApplication {
public static void main(String[] args) {

View File

@@ -1,122 +0,0 @@
package com.accompany.oauth.config;
import com.accompany.oauth.exception.AuthenticationException;
import com.accompany.oauth.exception.OAuthException;
import com.accompany.oauth.exception.TokenException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
/**
* 全局异常处理器
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 处理OAuth异常
*
* @param e OAuth异常
* @return 错误响应
*/
@ExceptionHandler(OAuthException.class)
@ResponseBody
public ResponseEntity<Map<String, Object>> handleOAuthException(OAuthException e) {
logger.warn("OAuth异常: {} - {}", e.getErrorCode(), e.getErrorDescription());
Map<String, Object> response = new HashMap<>();
response.put("error", e.getErrorCode());
response.put("error_description", e.getErrorDescription());
response.put("timestamp", System.currentTimeMillis());
return ResponseEntity.badRequest().body(response);
}
/**
* 处理认证异常
*
* @param e 认证异常
* @return 错误响应
*/
@ExceptionHandler(AuthenticationException.class)
@ResponseBody
public ResponseEntity<Map<String, Object>> handleAuthenticationException(AuthenticationException e) {
logger.warn("认证异常: {}", e.getErrorDescription());
Map<String, Object> response = new HashMap<>();
response.put("error", e.getErrorCode());
response.put("error_description", e.getErrorDescription());
response.put("timestamp", System.currentTimeMillis());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
}
/**
* 处理Token异常
*
* @param e Token异常
* @return 错误响应
*/
@ExceptionHandler(TokenException.class)
@ResponseBody
public ResponseEntity<Map<String, Object>> handleTokenException(TokenException e) {
logger.warn("Token异常: {} - {}", e.getErrorCode(), e.getErrorDescription());
Map<String, Object> response = new HashMap<>();
response.put("error", e.getErrorCode());
response.put("error_description", e.getErrorDescription());
response.put("timestamp", System.currentTimeMillis());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
}
/**
* 处理参数异常
*
* @param e 参数异常
* @return 错误响应
*/
@ExceptionHandler(IllegalArgumentException.class)
@ResponseBody
public ResponseEntity<Map<String, Object>> handleIllegalArgumentException(IllegalArgumentException e) {
logger.warn("参数异常: {}", e.getMessage());
Map<String, Object> response = new HashMap<>();
response.put("error", "invalid_request");
response.put("error_description", e.getMessage());
response.put("timestamp", System.currentTimeMillis());
return ResponseEntity.badRequest().body(response);
}
/**
* 处理其他异常
*
* @param e 异常
* @return 错误响应
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseEntity<Map<String, Object>> handleException(Exception e) {
logger.error("系统异常", e);
Map<String, Object> response = new HashMap<>();
response.put("error", "server_error");
response.put("error_description", "系统内部错误");
response.put("timestamp", System.currentTimeMillis());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}

View File

@@ -0,0 +1,99 @@
package com.accompany.oauth.config;
import cn.hutool.core.util.StrUtil;
import com.accompany.common.status.BusiStatus;
import com.accompany.core.base.SpringContextHolder;
import com.accompany.oauth.exception.OAuthException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* 全局异常处理器
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
@Slf4j
@ControllerAdvice
public class OauthGlobalExceptionHandler {
/**
* 处理OAuth异常
*
* @param e OAuth异常
* @return 错误响应
*/
@ExceptionHandler(OAuthException.class)
@ResponseBody
public ResponseEntity<Map<String, Object>> handleOAuthException(OAuthException e) {
BusiStatus status = e.getBusiStatus();
Map<String, Object> map = new HashMap<>();
map.put("code", status.getCode());
Locale locale = LocaleContextHolder.getLocale();
String i18nId = BusiStatus.class.getSimpleName() + StrUtil.DOT + status.getName();
String abc = SpringContextHolder.getBean(MessageSource.class).getMessage(i18nId, null, status.getMessage(), locale);
map.put("message", abc);
Map<String, String> additionalInformation = e.getAdditionalInformation();
if (CollectionUtils.isEmpty(additionalInformation)){
return ResponseEntity.status(status.getCode()).body(map);
}
for (String key : additionalInformation.keySet()){
map.put(key, additionalInformation.get(key));
}
return ResponseEntity.status(status.getCode()).body(map);
}
/**
* 处理参数异常
*
* @param e 参数异常
* @return 错误响应
*/
@ExceptionHandler(IllegalArgumentException.class)
@ResponseBody
public ResponseEntity<Map<String, Object>> handleIllegalArgumentException(IllegalArgumentException e) {
log.warn("参数异常: {}", e.getMessage());
Map<String, Object> response = new HashMap<>();
response.put("error", "invalid_request");
response.put("error_description", e.getMessage());
response.put("timestamp", System.currentTimeMillis());
return ResponseEntity.badRequest().body(response);
}
/**
* 处理其他异常
*
* @param e 异常
* @return 错误响应
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseEntity<Map<String, Object>> handleException(Exception e) {
log.error("系统异常", e);
Map<String, Object> response = new HashMap<>();
response.put("error", "server_error");
response.put("error_description", "系统内部错误");
response.put("timestamp", System.currentTimeMillis());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}

View File

@@ -61,14 +61,4 @@ oauth2:
server:
clientId: erban-client
clientSecret: uyzjdhds
jwtSignKey: dh293Hkdjf3G
yidunSecretId: 53ac2fc2d00e3ffc4eafbfe6305aed03
yidunSecretKey: 0b9cd0854bc6be2e5d709cc967f3fc38
registerBusinessId: af43d0f8752147c48f8281800da6049e
registerSwitch: false
registerApiUrl: https://ac.dun.163yun.com/v2/register/check
loginBusinessId: 67881c7a69764c058435ba93a51b1285
loginSwitch: false
logiApiUrl: https://ac.dun.163yun.com/v2/login/check
registerOpened: false
loginOpened: false
jwtSignKey: ead18800082c5807806d8f54914f84f4fa360dd568f4784288a4439b5fee0f25