oauth-重写-/oauth/token和第三方登录
This commit is contained in:
@@ -57,16 +57,6 @@ public class AccountService extends ServiceImpl<AccountMapper, Account> {
|
||||
return accounts.get(0);
|
||||
}
|
||||
|
||||
public Account getAccount(String phone) {
|
||||
Account account = getAccountByPhone(phone);
|
||||
if (account == null) {
|
||||
if (!CommonUtil.checkValidPhone(phone)) {
|
||||
account = getAccountByErBanNo(Long.valueOf(phone));
|
||||
}
|
||||
}
|
||||
return account;
|
||||
}
|
||||
|
||||
public Account getAccountByPhone(String phone) {
|
||||
QueryWrapper<Account> wrapper = new QueryWrapper<>();
|
||||
wrapper.lambda().eq(Account::getPhone, phone);
|
||||
@@ -145,7 +135,7 @@ public class AccountService extends ServiceImpl<AccountMapper, Account> {
|
||||
return accountList;
|
||||
}
|
||||
|
||||
public String refreshAndGetNetEaseToken(Account account) throws Exception {
|
||||
public String refreshAndGetNetEaseToken(Account account) {
|
||||
TokenRet tokenRet = netEaseService.refreshToken(account.getUid().toString());
|
||||
//未注册自动注册云信
|
||||
if (tokenRet != null && StrUtil.isNotEmpty(tokenRet.getDesc()) &&tokenRet.getDesc().contains("not register")) {
|
||||
@@ -154,20 +144,6 @@ public class AccountService extends ServiceImpl<AccountMapper, Account> {
|
||||
return (String) tokenRet.getInfo().get("token");
|
||||
}
|
||||
|
||||
public Account refreshNetEaseTokne(Long uid) throws Exception {
|
||||
Account account = getById(uid);
|
||||
String uidStr = String.valueOf(account.getUid());
|
||||
TokenRet tokenRet = netEaseService.refreshToken(uidStr);
|
||||
String token = (String) tokenRet.getInfo().get("token");
|
||||
Account accountUpdate = new Account();
|
||||
accountUpdate.setUid(account.getUid());
|
||||
accountUpdate.setNeteaseToken(token);
|
||||
accountUpdate.setErbanNo(account.getErbanNo());
|
||||
accountUpdate.setDeviceId(account.getDeviceId());
|
||||
save(accountUpdate);
|
||||
return accountUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初次设置
|
||||
*
|
||||
|
@@ -0,0 +1,82 @@
|
||||
package com.accompany.oauth.constant;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 登录类型
|
||||
*/
|
||||
public enum LoginTypeEnum {
|
||||
|
||||
/**
|
||||
* 微信
|
||||
*/
|
||||
WECHAT((byte) 1),
|
||||
/**
|
||||
* QQ
|
||||
*/
|
||||
QQ((byte) 2),
|
||||
/**
|
||||
* ID,萌声号或手机号
|
||||
*/
|
||||
ID((byte) 3),
|
||||
/**
|
||||
* 手机号一键登录
|
||||
*/
|
||||
PHONE((byte) 4),
|
||||
/**
|
||||
* 苹果账户
|
||||
*/
|
||||
APPLE((byte) 5),
|
||||
/**
|
||||
* 冷启动,ticket登录
|
||||
*/
|
||||
TICKET((byte) 6),
|
||||
|
||||
/**
|
||||
* 账号密码登录
|
||||
*/
|
||||
PASSWORD((byte) 7),
|
||||
|
||||
/**
|
||||
* 谷歌登录
|
||||
*/
|
||||
GOOGLE((byte) 8),
|
||||
|
||||
/**
|
||||
* line登录
|
||||
*/
|
||||
LINE((byte) 9),
|
||||
|
||||
/**
|
||||
* facebook登录
|
||||
*/
|
||||
FACEBOOK((byte) 10),
|
||||
|
||||
/**
|
||||
* 邮箱登录
|
||||
* */
|
||||
EMAIL((byte) 11),
|
||||
|
||||
;
|
||||
|
||||
private final byte value;
|
||||
|
||||
LoginTypeEnum(byte value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public byte getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static LoginTypeEnum get(int value) {
|
||||
Optional<LoginTypeEnum> result = Arrays.stream(LoginTypeEnum.values()).filter(loginTypeEnum ->
|
||||
loginTypeEnum.value == value).findAny();
|
||||
if (result.isPresent()) {
|
||||
return result.get();
|
||||
}
|
||||
throw new IllegalArgumentException("value not match");
|
||||
}
|
||||
|
||||
}
|
@@ -1,11 +1,14 @@
|
||||
package com.accompany.oauth.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 认证结果DTO
|
||||
*
|
||||
* @author Accompany OAuth Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Data
|
||||
public class AuthResult {
|
||||
|
||||
/**
|
||||
@@ -43,152 +46,9 @@ public class AuthResult {
|
||||
*/
|
||||
private String netEaseToken = "";
|
||||
|
||||
/**
|
||||
* 网易云信账号ID (兼容oauth2)
|
||||
*/
|
||||
private String accid = "";
|
||||
|
||||
/**
|
||||
* 用户Token (兼容oauth2)
|
||||
*/
|
||||
private String userToken = "";
|
||||
|
||||
/**
|
||||
* 登录Key (兼容oauth2)
|
||||
*/
|
||||
private String loginKey = "";
|
||||
|
||||
/**
|
||||
* JWT ID (兼容oauth2)
|
||||
*/
|
||||
private String jti = "";
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
private UserInfo userInfo;
|
||||
|
||||
public AuthResult() {
|
||||
}
|
||||
|
||||
public AuthResult(String accessToken, String refreshToken, Long expiresIn, UserInfo userInfo) {
|
||||
this.accessToken = accessToken;
|
||||
this.refreshToken = refreshToken;
|
||||
this.expiresIn = expiresIn;
|
||||
this.userInfo = userInfo;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public void setAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
public void setRefreshToken(String refreshToken) {
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
|
||||
public Long getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public void setExpiresIn(Long expiresIn) {
|
||||
this.expiresIn = expiresIn;
|
||||
}
|
||||
|
||||
public String getTokenType() {
|
||||
return tokenType;
|
||||
}
|
||||
|
||||
public void setTokenType(String tokenType) {
|
||||
this.tokenType = tokenType;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public void setScope(String scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public Long getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
public void setUid(Long uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
public String getNetEaseToken() {
|
||||
return netEaseToken;
|
||||
}
|
||||
|
||||
public void setNetEaseToken(String netEaseToken) {
|
||||
this.netEaseToken = netEaseToken;
|
||||
}
|
||||
|
||||
public String getAccid() {
|
||||
return accid;
|
||||
}
|
||||
|
||||
public void setAccid(String accid) {
|
||||
this.accid = accid;
|
||||
}
|
||||
|
||||
public String getUserToken() {
|
||||
return userToken;
|
||||
}
|
||||
|
||||
public void setUserToken(String userToken) {
|
||||
this.userToken = userToken;
|
||||
}
|
||||
|
||||
public String getLoginKey() {
|
||||
return loginKey;
|
||||
}
|
||||
|
||||
public void setLoginKey(String loginKey) {
|
||||
this.loginKey = loginKey;
|
||||
}
|
||||
|
||||
public String getJti() {
|
||||
return jti;
|
||||
}
|
||||
|
||||
public void setJti(String jti) {
|
||||
this.jti = jti;
|
||||
}
|
||||
|
||||
public UserInfo getUserInfo() {
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
public void setUserInfo(UserInfo userInfo) {
|
||||
this.userInfo = userInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AuthResult{" +
|
||||
"accessToken='" + accessToken + '\'' +
|
||||
", refreshToken='" + refreshToken + '\'' +
|
||||
", expiresIn=" + expiresIn +
|
||||
", tokenType='" + tokenType + '\'' +
|
||||
", scope='" + scope + '\'' +
|
||||
", uid=" + uid +
|
||||
", netEaseToken='" + netEaseToken + '\'' +
|
||||
", accid='" + accid + '\'' +
|
||||
", userToken='" + userToken + '\'' +
|
||||
", loginKey='" + loginKey + '\'' +
|
||||
", jti='" + jti + '\'' +
|
||||
", userInfo=" + userInfo +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
package com.accompany.oauth.dto;
|
||||
|
||||
public class DayIpMaxRegisterLimitConfig {
|
||||
private boolean open;
|
||||
private long max;
|
||||
|
||||
public boolean getOpen() {
|
||||
return open;
|
||||
}
|
||||
|
||||
public void setOpen(boolean open) {
|
||||
this.open = open;
|
||||
}
|
||||
|
||||
public long getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
public void setMax(long max) {
|
||||
this.max = max;
|
||||
}
|
||||
}
|
@@ -1,120 +0,0 @@
|
||||
package com.accompany.oauth.dto;
|
||||
|
||||
/**
|
||||
* H5访问令牌DTO (兼容OAuth2格式)
|
||||
*
|
||||
* @author Accompany OAuth Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class H5AccessToken {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long uid;
|
||||
|
||||
/**
|
||||
* 访问令牌
|
||||
*/
|
||||
private String access_token;
|
||||
|
||||
/**
|
||||
* 过期时间(秒)
|
||||
*/
|
||||
private Long expires_in;
|
||||
|
||||
/**
|
||||
* 令牌类型
|
||||
*/
|
||||
private String token_type = "Bearer";
|
||||
|
||||
/**
|
||||
* 刷新令牌
|
||||
*/
|
||||
private String refresh_token;
|
||||
|
||||
/**
|
||||
* 权限范围
|
||||
*/
|
||||
private String scope;
|
||||
|
||||
public H5AccessToken() {
|
||||
}
|
||||
|
||||
public H5AccessToken(Long uid, String accessToken, Long expiresIn) {
|
||||
this.uid = uid;
|
||||
this.access_token = accessToken;
|
||||
this.expires_in = expiresIn;
|
||||
}
|
||||
|
||||
public static H5AccessToken fromAuthResult(AuthResult authResult) {
|
||||
H5AccessToken h5Token = new H5AccessToken();
|
||||
h5Token.setUid(authResult.getUid());
|
||||
h5Token.setAccess_token(authResult.getAccessToken());
|
||||
h5Token.setRefresh_token(authResult.getRefreshToken());
|
||||
h5Token.setExpires_in(authResult.getExpiresIn());
|
||||
h5Token.setToken_type(authResult.getTokenType());
|
||||
h5Token.setScope(authResult.getScope());
|
||||
return h5Token;
|
||||
}
|
||||
|
||||
public Long getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
public void setUid(Long uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
public String getAccess_token() {
|
||||
return access_token;
|
||||
}
|
||||
|
||||
public void setAccess_token(String access_token) {
|
||||
this.access_token = access_token;
|
||||
}
|
||||
|
||||
public Long getExpires_in() {
|
||||
return expires_in;
|
||||
}
|
||||
|
||||
public void setExpires_in(Long expires_in) {
|
||||
this.expires_in = expires_in;
|
||||
}
|
||||
|
||||
public String getToken_type() {
|
||||
return token_type;
|
||||
}
|
||||
|
||||
public void setToken_type(String token_type) {
|
||||
this.token_type = token_type;
|
||||
}
|
||||
|
||||
public String getRefresh_token() {
|
||||
return refresh_token;
|
||||
}
|
||||
|
||||
public void setRefresh_token(String refresh_token) {
|
||||
this.refresh_token = refresh_token;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public void setScope(String scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "H5AccessToken{" +
|
||||
"uid=" + uid +
|
||||
", access_token='" + access_token + '\'' +
|
||||
", expires_in=" + expires_in +
|
||||
", token_type='" + token_type + '\'' +
|
||||
", refresh_token='" + refresh_token + '\'' +
|
||||
", scope='" + scope + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
package com.accompany.oauth.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RepeatedDeviceIpRegisterLimitConfig {
|
||||
private boolean open;
|
||||
private int repeatedDeviceNumLimit;
|
||||
private int repeatedIpNumLimit;
|
||||
}
|
@@ -1,136 +0,0 @@
|
||||
package com.accompany.oauth.dto;
|
||||
|
||||
/**
|
||||
* 用户信息DTO
|
||||
*
|
||||
* @author Accompany OAuth Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class UserInfo {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 手机号(脱敏)
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 邮箱(脱敏)
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 头像URL
|
||||
*/
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
private String nickname;
|
||||
|
||||
/**
|
||||
* 性别(1-男,2-女,0-未知)
|
||||
*/
|
||||
private Integer gender;
|
||||
|
||||
/**
|
||||
* 用户状态
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
public UserInfo() {
|
||||
}
|
||||
|
||||
public UserInfo(Long userId, String username) {
|
||||
this.userId = userId;
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public Long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(Long userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public void setAvatar(String avatar) {
|
||||
this.avatar = avatar;
|
||||
}
|
||||
|
||||
public String getNickname() {
|
||||
return nickname;
|
||||
}
|
||||
|
||||
public void setNickname(String nickname) {
|
||||
this.nickname = nickname;
|
||||
}
|
||||
|
||||
public Integer getGender() {
|
||||
return gender;
|
||||
}
|
||||
|
||||
public void setGender(Integer gender) {
|
||||
this.gender = gender;
|
||||
}
|
||||
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UserInfo{" +
|
||||
"userId=" + userId +
|
||||
", username='" + username + '\'' +
|
||||
", phone='" + phone + '\'' +
|
||||
", email='" + email + '\'' +
|
||||
", avatar='" + avatar + '\'' +
|
||||
", nickname='" + nickname + '\'' +
|
||||
", gender=" + gender +
|
||||
", status=" + status +
|
||||
'}';
|
||||
}
|
||||
}
|
@@ -1,11 +1,14 @@
|
||||
package com.accompany.oauth.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Token对模型
|
||||
*
|
||||
* @author Accompany OAuth Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Data
|
||||
public class TokenPair {
|
||||
|
||||
/**
|
||||
@@ -32,64 +35,5 @@ public class TokenPair {
|
||||
* 权限范围
|
||||
*/
|
||||
private String scope;
|
||||
|
||||
public TokenPair() {
|
||||
}
|
||||
|
||||
public TokenPair(String accessToken, String refreshToken, Long expiresIn) {
|
||||
this.accessToken = accessToken;
|
||||
this.refreshToken = refreshToken;
|
||||
this.expiresIn = expiresIn;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public void setAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
public void setRefreshToken(String refreshToken) {
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
|
||||
public Long getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public void setExpiresIn(Long expiresIn) {
|
||||
this.expiresIn = expiresIn;
|
||||
}
|
||||
|
||||
public String getTokenType() {
|
||||
return tokenType;
|
||||
}
|
||||
|
||||
public void setTokenType(String tokenType) {
|
||||
this.tokenType = tokenType;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public void setScope(String scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TokenPair{" +
|
||||
"accessToken='" + accessToken + '\'' +
|
||||
", refreshToken='" + refreshToken + '\'' +
|
||||
", expiresIn=" + expiresIn +
|
||||
", tokenType='" + tokenType + '\'' +
|
||||
", scope='" + scope + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
@@ -1,135 +0,0 @@
|
||||
package com.accompany.oauth.model;
|
||||
|
||||
import com.accompany.oauth.constant.UserStatus;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 用户详情模型
|
||||
*
|
||||
* @author Accompany OAuth Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class UserDetails {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 用户状态
|
||||
*/
|
||||
private UserStatus status;
|
||||
|
||||
/**
|
||||
* 权限集合
|
||||
*/
|
||||
private Set<String> authorities;
|
||||
|
||||
/**
|
||||
* 客户端ID
|
||||
*/
|
||||
private String clientId;
|
||||
|
||||
public UserDetails() {
|
||||
}
|
||||
|
||||
public UserDetails(Long userId, String phone, String email, UserStatus status) {
|
||||
this.userId = userId;
|
||||
this.phone = phone;
|
||||
this.email = email;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(Long userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public UserStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(UserStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Set<String> getAuthorities() {
|
||||
return authorities;
|
||||
}
|
||||
|
||||
public void setAuthorities(Set<String> authorities) {
|
||||
this.authorities = authorities;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否可用
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return status == UserStatus.NORMAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UserDetails{" +
|
||||
"userId=" + userId +
|
||||
", phone='" + phone + '\'' +
|
||||
", email='" + email + '\'' +
|
||||
", username='" + username + '\'' +
|
||||
", status=" + status +
|
||||
", authorities=" + authorities +
|
||||
", clientId='" + clientId + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
@@ -1,15 +1,14 @@
|
||||
package com.accompany.oauth.manager;
|
||||
|
||||
import com.accompany.common.redis.RedisKey;
|
||||
import com.accompany.core.service.common.JedisService;
|
||||
import com.accompany.oauth.constant.OAuthConstants;
|
||||
import com.accompany.oauth.dto.UserInfo;
|
||||
import com.accompany.oauth.exception.TokenException;
|
||||
import com.accompany.oauth.model.TokenPair;
|
||||
import com.accompany.oauth.model.TokenValidation;
|
||||
import com.accompany.oauth.model.UserDetails;
|
||||
import com.accompany.oauth.util.JwtUtil;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import org.redisson.api.RBucket;
|
||||
import com.accompany.oauth.ticket.RedisTicketStore;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -18,7 +17,6 @@ import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Token管理器 - 使用Redisson进行Token存储和管理
|
||||
@@ -31,48 +29,38 @@ public class TokenManager {
|
||||
|
||||
@Autowired
|
||||
public JwtUtil jwtUtil;
|
||||
|
||||
@Autowired
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
@Autowired
|
||||
private RedisTicketStore redisTicketStore;
|
||||
|
||||
/**
|
||||
* 生成Token对
|
||||
*
|
||||
* @param userDetails 用户详情
|
||||
* @return Token对
|
||||
*/
|
||||
public TokenPair generateToken(UserDetails userDetails) {
|
||||
private JedisService jedisService;
|
||||
|
||||
public TokenPair generateToken(Long uid) {
|
||||
try {
|
||||
String clientId = userDetails.getClientId() != null ? userDetails.getClientId() : "default";
|
||||
Set<String> scopes = userDetails.getAuthorities() != null ?
|
||||
userDetails.getAuthorities() : new HashSet<>(Arrays.asList("read", "write"));
|
||||
|
||||
// 生成JWT token
|
||||
String accessToken = jwtUtil.generateAccessToken(userDetails.getUserId(), clientId, scopes);
|
||||
String refreshToken = jwtUtil.generateRefreshToken(userDetails.getUserId(), clientId);
|
||||
|
||||
// 将token存储到Redis中
|
||||
storeTokenInRedis(accessToken, refreshToken, userDetails);
|
||||
|
||||
String accessToken = jwtUtil.generateAccessToken(uid);
|
||||
String refreshToken = jwtUtil.generateRefreshToken(uid);
|
||||
|
||||
// 存储access token
|
||||
jedisService.hwrite(RedisKey.uid_access_token.getKey(), uid.toString(), accessToken);
|
||||
|
||||
// 存储用户的access token到ticket store
|
||||
redisTicketStore.storeAccessToken(userDetails.getUserId(), accessToken, jwtUtil.getAccessTokenExpiration());
|
||||
|
||||
jedisService.hwrite(RedisKey.uid_ticket.getKey(), uid.toString(), accessToken);
|
||||
|
||||
TokenPair tokenPair = new TokenPair();
|
||||
tokenPair.setAccessToken(accessToken);
|
||||
tokenPair.setRefreshToken(refreshToken);
|
||||
//todo
|
||||
tokenPair.setExpiresIn(jwtUtil.getAccessTokenExpiration());
|
||||
tokenPair.setTokenType("Bearer");
|
||||
tokenPair.setScope(String.join(" ", scopes));
|
||||
|
||||
tokenPair.setScope(String.join(" ", "read", "write"));
|
||||
|
||||
return tokenPair;
|
||||
|
||||
} catch (Exception e) {
|
||||
throw TokenException.tokenGenerationFailed();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 验证Token
|
||||
*
|
||||
@@ -108,43 +96,6 @@ public class TokenManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新Token
|
||||
*
|
||||
* @param refreshToken 刷新令牌
|
||||
* @param userDetails 用户详情
|
||||
* @return 新的Token对
|
||||
*/
|
||||
public TokenPair refreshToken(String refreshToken, UserDetails userDetails) {
|
||||
try {
|
||||
// 验证refresh token
|
||||
Claims claims = jwtUtil.validateAndParseToken(refreshToken);
|
||||
String tokenType = claims.get("token_type", String.class);
|
||||
|
||||
if (!"refresh_token".equals(tokenType)) {
|
||||
throw TokenException.invalidRefreshToken();
|
||||
}
|
||||
|
||||
// 检查Redis中是否存在该refresh token
|
||||
String refreshTokenKey = OAuthConstants.Token.REFRESH_TOKEN_PREFIX + refreshToken;
|
||||
RBucket<String> bucket = redissonClient.getBucket(refreshTokenKey);
|
||||
|
||||
if (!bucket.isExists()) {
|
||||
throw TokenException.invalidRefreshToken();
|
||||
}
|
||||
|
||||
// 撤销旧的tokens
|
||||
revokeTokensByUserId(userDetails.getUserId());
|
||||
|
||||
// 生成新的token对
|
||||
return generateToken(userDetails);
|
||||
} catch (TokenException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw TokenException.invalidRefreshToken();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销Token
|
||||
*
|
||||
@@ -153,62 +104,16 @@ public class TokenManager {
|
||||
public void revokeToken(String token) {
|
||||
try {
|
||||
Claims claims = jwtUtil.validateAndParseToken(token);
|
||||
Long userId = Long.valueOf(claims.getSubject());
|
||||
Long uid = Long.valueOf(claims.getSubject());
|
||||
|
||||
// 删除access token
|
||||
String accessTokenKey = OAuthConstants.Token.ACCESS_TOKEN_PREFIX + token;
|
||||
redissonClient.getBucket(accessTokenKey).delete();
|
||||
|
||||
// 查找并删除对应的refresh token
|
||||
revokeTokensByUserId(userId);
|
||||
jedisService.hdel(RedisKey.uid_access_token.getKey(), uid.toString());
|
||||
// ticket
|
||||
jedisService.hdel(RedisKey.uid_ticket.getKey(), uid.toString());
|
||||
|
||||
} catch (Exception e) {
|
||||
// 忽略撤销失败的情况
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销用户所有Token
|
||||
*
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
public void revokeTokensByUserId(Long userId) {
|
||||
try {
|
||||
String userTokenKey = OAuthConstants.Token.USER_TOKEN_PREFIX + userId;
|
||||
RBucket<String> userTokenBucket = redissonClient.getBucket(userTokenKey);
|
||||
|
||||
if (userTokenBucket.isExists()) {
|
||||
// 可以在这里实现更复杂的用户token管理逻辑
|
||||
// 暂时简单删除用户关联的token记录
|
||||
userTokenBucket.delete();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 忽略撤销失败的情况
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Token存储到Redis
|
||||
*
|
||||
* @param accessToken 访问令牌
|
||||
* @param refreshToken 刷新令牌
|
||||
* @param userDetails 用户详情
|
||||
*/
|
||||
private void storeTokenInRedis(String accessToken, String refreshToken, UserDetails userDetails) {
|
||||
// 存储access token
|
||||
String accessTokenKey = OAuthConstants.Token.ACCESS_TOKEN_PREFIX + accessToken;
|
||||
RBucket<String> accessTokenBucket = redissonClient.getBucket(accessTokenKey);
|
||||
accessTokenBucket.set(userDetails.getUserId().toString(),
|
||||
jwtUtil.getAccessTokenExpiration(), TimeUnit.SECONDS);
|
||||
|
||||
// 存储refresh token
|
||||
String refreshTokenKey = OAuthConstants.Token.REFRESH_TOKEN_PREFIX + refreshToken;
|
||||
RBucket<String> refreshTokenBucket = redissonClient.getBucket(refreshTokenKey);
|
||||
refreshTokenBucket.set(userDetails.getUserId().toString(),
|
||||
jwtUtil.getRefreshTokenExpiration(), TimeUnit.SECONDS);
|
||||
|
||||
// 存储用户token关联(可用于实现单点登录控制)
|
||||
String userTokenKey = OAuthConstants.Token.USER_TOKEN_PREFIX + userDetails.getUserId();
|
||||
RBucket<String> userTokenBucket = redissonClient.getBucket(userTokenKey);
|
||||
userTokenBucket.set(accessToken, jwtUtil.getAccessTokenExpiration(), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
}
|
@@ -2,24 +2,37 @@ package com.accompany.oauth.service;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.accompany.common.constant.Constant;
|
||||
import com.accompany.common.device.DeviceInfo;
|
||||
import com.accompany.common.netease.neteaseacc.result.TokenRet;
|
||||
import com.accompany.common.redis.RedisKey;
|
||||
import com.accompany.common.status.BusiStatus;
|
||||
import com.accompany.common.utils.BlankUtil;
|
||||
import com.accompany.common.utils.CommonUtil;
|
||||
import com.accompany.common.utils.DateTimeUtil;
|
||||
import com.accompany.common.utils.UUIDUtil;
|
||||
import com.accompany.core.exception.ServiceException;
|
||||
import com.accompany.core.model.Account;
|
||||
import com.accompany.core.model.GoogleOpenidRef;
|
||||
import com.accompany.core.model.UserCancelRecord;
|
||||
import com.accompany.core.model.Users;
|
||||
import com.accompany.core.mybatismapper.AccountMapper;
|
||||
import com.accompany.core.service.SysConfService;
|
||||
import com.accompany.core.service.account.AccountService;
|
||||
import com.accompany.core.service.account.ErBanNoService;
|
||||
import com.accompany.core.service.account.NetEaseService;
|
||||
import com.accompany.core.service.common.JedisService;
|
||||
import com.accompany.core.service.user.GoogleOpenidRefService;
|
||||
import com.accompany.core.service.user.UserCancelRecordService;
|
||||
import com.accompany.core.service.user.UsersBaseService;
|
||||
import com.accompany.core.util.MD5;
|
||||
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.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;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -40,6 +53,10 @@ public class AccountManageService {
|
||||
@Autowired
|
||||
private AccountMapper accountMapper;
|
||||
@Autowired
|
||||
private NetEaseService netEaseService;
|
||||
@Autowired
|
||||
private ErBanNoService erBanNoService;
|
||||
@Autowired
|
||||
private AccountService accountService;
|
||||
@Autowired
|
||||
private UsersBaseService usersBaseService;
|
||||
@@ -51,9 +68,273 @@ public class AccountManageService {
|
||||
private SmsService smsService;
|
||||
@Autowired
|
||||
private EmailService emailService;
|
||||
@Autowired
|
||||
private GoogleOpenidRefService googleOpenidRefService;
|
||||
|
||||
protected Gson gson = new Gson();
|
||||
|
||||
public Account getAccountPyUsername(String username, String password) {
|
||||
log.info("getAccountByUserName username:{} password:{}", username, password);
|
||||
Long erbanNo = Long.parseLong(username);
|
||||
return accountService.getAccountByErBanNo(erbanNo);
|
||||
}
|
||||
|
||||
public Account getOrGenAccountByPhone(String phone, String phoneAreaCode, String smsCode, DeviceInfo deviceInfo) {
|
||||
log.info("getOrGenAccountByPhone phone:{},smsCode:{},phoneAreaCode:{}", phone, smsCode, phoneAreaCode);
|
||||
final String lockVal = jedisService.lock(RedisKey.lock_register_by_phone.getKey(phone));
|
||||
try {
|
||||
if (BlankUtil.isBlank(lockVal)) {
|
||||
throw new ServiceException(BusiStatus.REQUEST_FAST);
|
||||
}
|
||||
Account account = accountService.getAccountByPhone(phone);
|
||||
if (account == null) {
|
||||
account = saveSignUpByPhone(phone, phoneAreaCode, deviceInfo);
|
||||
}
|
||||
return account;
|
||||
} finally {
|
||||
jedisService.unlock(RedisKey.lock_register_by_phone.getKey(phone), lockVal);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Account getOrGenAccountByEmail(String email, String code, DeviceInfo deviceInfo, String ipAddress) {
|
||||
log.info("getOrGenAccountByPhone email:{},code:{}", email, code);
|
||||
String lockVal = jedisService.lock(RedisKey.lock_register_by_email.getKey(email));
|
||||
try {
|
||||
if (BlankUtil.isBlank(lockVal)) {
|
||||
throw new ServiceException(BusiStatus.REQUEST_FAST);
|
||||
}
|
||||
Account account = accountService.getAccountByEmail(email);
|
||||
if (account == null) {
|
||||
account = saveSignUpByEmail(email, null, deviceInfo, ipAddress);
|
||||
}
|
||||
return account;
|
||||
} finally {
|
||||
jedisService.unlock(RedisKey.lock_register_by_email.getKey(email), lockVal);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public Account getOrGenAccountByOpenid(Byte type, String openid, String unionId, String idToken, DeviceInfo deviceInfo) {
|
||||
log.info("getOrGenAccountByOpenid openId:{},type:{},unionId:{}", openid, type, unionId);
|
||||
final String locKey = RedisKey.lock_register_by_openid.getKey(openid, unionId, String.valueOf(type));
|
||||
final String lockVal = jedisService.lock(locKey, 10 * 1000);
|
||||
try {
|
||||
if (BlankUtil.isBlank(lockVal)) {
|
||||
throw new ServiceException(BusiStatus.REQUEST_FAST);
|
||||
}
|
||||
|
||||
Account account = null;
|
||||
String thirdAccountEmail = null;
|
||||
|
||||
// openid是邮箱,则是新版本
|
||||
if (LoginTypeEnum.GOOGLE.getValue() == type && emailService.isValidEmail(openid)) {
|
||||
GoogleOpenidRef ref = googleOpenidRefService.getRefByEmail(openid, idToken);
|
||||
if (null != ref) {
|
||||
openid = ref.getOpenId();
|
||||
unionId = ref.getOpenId();
|
||||
thirdAccountEmail = ref.getEmail();
|
||||
|
||||
if (ref.isRegister()){
|
||||
Account emailAccount = accountService.getAccountByEmail(thirdAccountEmail);
|
||||
if (null != emailAccount && null == emailAccount.getThirdLoginType()){
|
||||
emailAccount.setThirdLoginType(type);
|
||||
emailAccount.setUnionId(unionId);
|
||||
emailAccount.setOpenId(openid);
|
||||
accountMapper.updateById(emailAccount);
|
||||
|
||||
account = emailAccount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null == account){
|
||||
account = accountService.getAccountByThird(type, unionId);
|
||||
}
|
||||
|
||||
if (null != account){
|
||||
|
||||
if (BlankUtil.isBlank(account.getUnionId()) || !account.getUnionId().equals(unionId)) {
|
||||
account.setUnionId(unionId);
|
||||
}
|
||||
account.setLastLoginTime(new Date());
|
||||
account.setLastLoginIp(deviceInfo.getClientIp());
|
||||
account.setUpdateTime(new Date());
|
||||
accountMapper.updateById(account);
|
||||
return account;
|
||||
}
|
||||
|
||||
checkRegisterLimit(deviceInfo.getDeviceId(), deviceInfo.getClientIp());
|
||||
|
||||
Date date = new Date();
|
||||
account = new Account();
|
||||
account.setLastLoginTime(date);
|
||||
account.setLastLoginIp(deviceInfo.getClientIp());
|
||||
account.setUpdateTime(date);
|
||||
account.setRegisterIp(deviceInfo.getClientIp());
|
||||
account.setSignTime(date);
|
||||
account.setState(Constant.AccountState.normal);
|
||||
account.setErbanNo(erBanNoService.getErBanNo());
|
||||
account.setPhone(CommonUtil.genSpecialPhoneForInitAccount(account.getErbanNo().toString()));
|
||||
// 三方登录信息
|
||||
account.setThirdLoginType(type);
|
||||
account.setUnionId(unionId);
|
||||
account.setOpenId(openid);
|
||||
account.setEmail(thirdAccountEmail);
|
||||
|
||||
account.setNeteaseToken(UUIDUtil.get());
|
||||
|
||||
account = fillDeviceInfo(account, deviceInfo);
|
||||
|
||||
accountMapper.insert(account);
|
||||
//写缓存
|
||||
accountService.writeAche(account);
|
||||
|
||||
String uidStr = String.valueOf(account.getUid());
|
||||
TokenRet tokenRet = netEaseService.createNetEaseAcc(uidStr, account.getNeteaseToken(), "", "", null);
|
||||
if (tokenRet.getCode() != 200) {
|
||||
log.info("注册云信账号失败,openid=" + openid + "&uid=" + uidStr + ",异常原因code=" + tokenRet.getCode());
|
||||
log.error("注册云信账号失败,openid=" + openid + "&uid=" + uidStr + ",异常原因code=" + tokenRet.getCode());
|
||||
throw new ServiceException("第三方登录失败,openid=" + openid + ",异常原因code=" + tokenRet.getCode());
|
||||
}
|
||||
|
||||
return account;
|
||||
|
||||
} finally {
|
||||
jedisService.unlock(locKey, lockVal);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkRegisterLimit(String deviceId, String ipAddress) {
|
||||
if (!StringUtils.hasText(deviceId)) {
|
||||
//todo
|
||||
//throw new CustomOAuth2Exception(CustomOAuth2Exception.SIGN_IP_TO_OFTEN, BusiStatus.DEVICE_ERROR.getReasonPhrase());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
//当日单个ip注册数
|
||||
DayIpMaxRegisterLimitConfig config = getIpMaxLimitConfig();
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String encryptPassword(String password) {
|
||||
return MD5.getMD5(password);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过手机号码注册,独立账号系统,不掺杂业务
|
||||
*
|
||||
* @param phone
|
||||
* @return
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Account saveSignUpByPhone(String phone, String phoneAreaCode, DeviceInfo deviceInfo) {
|
||||
checkRegisterLimit(deviceInfo.getDeviceId(), deviceInfo.getClientIp());
|
||||
|
||||
Date date = new Date();
|
||||
Account account = new Account();
|
||||
account.setPhone(phone);
|
||||
account.setPhoneAreaCode(phoneAreaCode);
|
||||
// if (!StringUtils.isEmpty(password)) {
|
||||
// account.setPassword(encryptPassword(password));
|
||||
// }
|
||||
account.setNeteaseToken(UUIDUtil.get());
|
||||
account.setLastLoginTime(date);
|
||||
account.setLastLoginIp(deviceInfo.getClientIp());
|
||||
account.setUpdateTime(date);
|
||||
account.setRegisterIp(deviceInfo.getClientIp());
|
||||
account.setSignTime(date);
|
||||
account.setState(Constant.AccountState.normal);
|
||||
account.setErbanNo(erBanNoService.getErBanNo());
|
||||
account = fillDeviceInfo(account, deviceInfo);
|
||||
account.setSignupApp(deviceInfo.getApp());
|
||||
|
||||
accountMapper.insert(account);
|
||||
accountService.writeAche(account);
|
||||
|
||||
String uidStr = String.valueOf(account.getUid());
|
||||
TokenRet tokenRet = netEaseService.createNetEaseAcc(uidStr, account.getNeteaseToken(), "", "", null);
|
||||
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());
|
||||
}
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Account saveSignUpByEmail(String email, String password, DeviceInfo deviceInfo, String ipAddress) {
|
||||
checkRegisterLimit(deviceInfo.getDeviceId(), ipAddress);
|
||||
|
||||
Date date = new Date();
|
||||
Account account = new Account();
|
||||
account.setEmail(email);
|
||||
if (StringUtils.hasText(password)) {
|
||||
account.setPassword(encryptPassword(password));
|
||||
}
|
||||
account.setNeteaseToken(UUIDUtil.get());
|
||||
account.setLastLoginTime(date);
|
||||
account.setLastLoginIp(ipAddress);
|
||||
account.setUpdateTime(date);
|
||||
account.setRegisterIp(ipAddress);
|
||||
account.setSignTime(date);
|
||||
account.setState(Constant.AccountState.normal);
|
||||
account.setErbanNo(erBanNoService.getErBanNo());
|
||||
account = fillDeviceInfo(account, deviceInfo);
|
||||
|
||||
accountMapper.insert(account);
|
||||
accountService.writeAche(account);
|
||||
String uidStr = String.valueOf(account.getUid());
|
||||
TokenRet tokenRet = netEaseService.createNetEaseAcc(uidStr, account.getNeteaseToken(), "", "", null);
|
||||
if (tokenRet.getCode() != 200) {
|
||||
log.error("邮件email {} 注册异常,异常原因code {}", email, tokenRet.getCode());
|
||||
throw new ServiceException(BusiStatus.SERVERBUSY);
|
||||
}
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
private Account fillDeviceInfo(Account account, DeviceInfo deviceInfo) {
|
||||
if (deviceInfo != null) {
|
||||
account.setSignupApp(deviceInfo.getApp());
|
||||
account.setApp(deviceInfo.getApp());
|
||||
account.setAppVersion(deviceInfo.getAppVersion());
|
||||
account.setChannel(deviceInfo.getChannel());
|
||||
account.setLinkedmeChannel(deviceInfo.getLinkedmeChannel());
|
||||
account.setDeviceId(deviceInfo.getDeviceId());
|
||||
account.setImei(deviceInfo.getImei());
|
||||
account.setIspType(deviceInfo.getIspType());
|
||||
account.setModel(deviceInfo.getModel());
|
||||
account.setNetType(deviceInfo.getNetType());
|
||||
account.setOs(deviceInfo.getOs());
|
||||
account.setOsversion(deviceInfo.getOsVersion());
|
||||
account.setDeviceInfo(JSON.toJSONString(deviceInfo));
|
||||
}
|
||||
return account;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置密码
|
||||
* 两个场景调用 => 客户端未登录 忘记密码, 此时uid 为 null 登录状态下忘记密码 uid有值
|
||||
@@ -159,10 +440,6 @@ public class AccountManageService {
|
||||
30 * 24 * 60 * 60, String.valueOf(new Date().getTime()));
|
||||
}
|
||||
|
||||
private String encryptPassword(String password) {
|
||||
return MD5.getMD5(password);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置登录密码
|
||||
* @param uid
|
||||
@@ -183,10 +460,57 @@ public class AccountManageService {
|
||||
// 更新用户缓存
|
||||
this.jedisService.hdel(RedisKey.user.getKey(), account.getUid().toString());
|
||||
this.jedisService.hdel(RedisKey.user_summary.getKey(), account.getUid().toString());
|
||||
|
||||
accountService.delNickPasswordCache(account.getErbanNo());
|
||||
}
|
||||
|
||||
public void checkAccountCancel(Long uid) {
|
||||
log.info("检查账号{}是否已注销", uid);
|
||||
Users users = usersBaseService.getUsersByUid(uid);
|
||||
if (ObjectUtil.isNull(users)) {
|
||||
log.info("获取不到用户{}users账号信息", uid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Constant.UserUseStatus.cancel.equals(users.getUseStatus())) return;
|
||||
|
||||
UserCancelRecord userCancelRecord = userCancelRecordService.getById(uid);
|
||||
|
||||
if (ObjectUtil.isNull(userCancelRecord)) {
|
||||
//获取不到注销账号信息
|
||||
log.info("获取不到用户{}注销信息", uid);
|
||||
throw new CustomOAuth2Exception(CustomOAuth2Exception.ACCOUNT_CANCEL_INFO_NOT_EXIST, BusiStatus.ACCOUNT_CANCEL_INFO_NOT_EXIST.getReasonPhrase());
|
||||
}
|
||||
|
||||
|
||||
log.info("检测到注销账号{}昵称{}于{}尝试登录", users.getErbanNo(), userCancelRecord.getNick(), DateTimeUtil.convertDate(userCancelRecord.getUpdateTime()));
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return gson.fromJson(config, DayIpMaxRegisterLimitConfig.class);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return gson.fromJson(config, RepeatedDeviceIpRegisterLimitConfig.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@@ -1,13 +1,14 @@
|
||||
package com.accompany.oauth.service;
|
||||
|
||||
import com.accompany.oauth.dto.AuthCredentials;
|
||||
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.dto.UserInfo;
|
||||
import com.accompany.oauth.exception.AuthenticationException;
|
||||
import com.accompany.oauth.manager.TokenManager;
|
||||
import com.accompany.oauth.model.TokenPair;
|
||||
import com.accompany.oauth.model.TokenValidation;
|
||||
import com.accompany.oauth.model.UserDetails;
|
||||
import com.accompany.common.device.DeviceInfo;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -22,34 +23,85 @@ public class AuthenticationService {
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private MyUserDetailsService myUserDetailsService;
|
||||
@Autowired
|
||||
private TokenManager tokenManager;
|
||||
|
||||
/**
|
||||
* 用户认证
|
||||
*
|
||||
* @param credentials 认证凭据
|
||||
* 用户认证 - 简化接口(直接传参)
|
||||
*
|
||||
* @param grantType 授权类型/手机号/邮箱
|
||||
* @param password 密码
|
||||
* @param code 验证码
|
||||
* @param deviceInfo 设备信息
|
||||
* @return 认证结果
|
||||
* @throws AuthenticationException 认证失败
|
||||
*/
|
||||
public AuthResult authenticate(AuthCredentials credentials) {
|
||||
// 1. 根据认证类型进行用户认证
|
||||
UserDetails userDetails = authenticateUser(credentials);
|
||||
|
||||
public AuthResult authenticate(String grantType,
|
||||
String username, String password,
|
||||
String phone, String phoneAreaCode, String email,
|
||||
String code, DeviceInfo deviceInfo) {
|
||||
GrantTypeEnum grantTypeEnum = GrantTypeEnum.fromCode(grantType);
|
||||
|
||||
// 1. 根据授权类型进行用户认证
|
||||
LoginTypeEnum loginTypeEnum = null;
|
||||
Account account = null;
|
||||
|
||||
switch (grantTypeEnum) {
|
||||
case PASSWORD:
|
||||
loginTypeEnum = LoginTypeEnum.ID;
|
||||
account = userService.authenticateByPassword(username, password, deviceInfo);
|
||||
break;
|
||||
case VERIFY_CODE:
|
||||
loginTypeEnum = LoginTypeEnum.PHONE;
|
||||
account = userService.authenticateByVerifyCode(phone, phoneAreaCode, code, deviceInfo);
|
||||
break;
|
||||
case EMAIL:
|
||||
loginTypeEnum = LoginTypeEnum.EMAIL;
|
||||
account = userService.authenticateByEmail(email, code, deviceInfo);
|
||||
break;
|
||||
default:
|
||||
throw new AuthenticationException("不支持的认证类型: " + grantType);
|
||||
}
|
||||
|
||||
// 2. 检查用户状态
|
||||
userService.checkUserStatus(userDetails);
|
||||
|
||||
// 3. 设置客户端ID和权限范围
|
||||
userDetails.setClientId(credentials.getClientId());
|
||||
|
||||
// 4. 生成Token
|
||||
TokenPair tokenPair = tokenManager.generateToken(userDetails);
|
||||
|
||||
// 5. 构建认证结果
|
||||
return buildAuthResult(tokenPair, userDetails);
|
||||
userService.checkUserStatus(account);
|
||||
|
||||
myUserDetailsService.login(account, loginTypeEnum, deviceInfo, null);
|
||||
|
||||
// 3. 生成Token
|
||||
TokenPair tokenPair = tokenManager.generateToken(account.getUid());
|
||||
|
||||
// 4. 构建认证结果
|
||||
return buildAuthResult(tokenPair, account);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 第三方登录认证 (兼容OAuth2格式)
|
||||
*
|
||||
* @param openId 第三方OpenID
|
||||
* @param unionId UnionID(可选)
|
||||
* @param idToken ID Token(可选)
|
||||
* @param deviceInfo 设备信息
|
||||
* @return 认证结果
|
||||
* @throws AuthenticationException 认证失败
|
||||
*/
|
||||
|
||||
public AuthResult authenticateByThirdParty(Byte type, String openId, String unionId, String idToken, DeviceInfo deviceInfo) {
|
||||
// 1. 第三方认证
|
||||
Account account = userService.authenticateByOpenId(type, openId, unionId, idToken, deviceInfo);
|
||||
|
||||
// 2. 检查用户状态
|
||||
userService.checkUserStatus(account);
|
||||
|
||||
// 4. 生成Token
|
||||
TokenPair tokenPair = tokenManager.generateToken(account.getUid());
|
||||
|
||||
// 5. 构建认证结果
|
||||
return buildAuthResult(tokenPair, account);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证Token
|
||||
*
|
||||
@@ -60,32 +112,6 @@ public class AuthenticationService {
|
||||
return tokenManager.validateToken(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新Token
|
||||
*
|
||||
* @param refreshToken 刷新令牌
|
||||
* @return 认证结果
|
||||
* @throws AuthenticationException 刷新失败
|
||||
*/
|
||||
public AuthResult refreshToken(String refreshToken) {
|
||||
try {
|
||||
// 1. 从refresh token中获取用户信息
|
||||
Long userId = tokenManager.jwtUtil.getUserIdFromToken(refreshToken);
|
||||
UserDetails userDetails = userService.getUserById(userId);
|
||||
|
||||
// 2. 检查用户状态
|
||||
userService.checkUserStatus(userDetails);
|
||||
|
||||
// 3. 刷新Token
|
||||
TokenPair tokenPair = tokenManager.refreshToken(refreshToken, userDetails);
|
||||
|
||||
// 4. 构建认证结果
|
||||
return buildAuthResult(tokenPair, userDetails);
|
||||
} catch (Exception e) {
|
||||
throw new AuthenticationException("刷新Token失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销Token
|
||||
*
|
||||
@@ -95,99 +121,13 @@ public class AuthenticationService {
|
||||
tokenManager.revokeToken(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销用户所有Token
|
||||
*
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
public void revokeAllTokens(Long userId) {
|
||||
tokenManager.revokeTokensByUserId(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 第三方登录认证 (兼容OAuth2格式)
|
||||
*
|
||||
* @param openId 第三方OpenID
|
||||
* @param thirdPartyType 第三方类型
|
||||
* @param unionId UnionID(可选)
|
||||
* @param idToken ID Token(可选)
|
||||
* @param deviceInfo 设备信息
|
||||
* @param ipAddress IP地址
|
||||
* @param clientId 客户端标识
|
||||
* @return 认证结果
|
||||
* @throws AuthenticationException 认证失败
|
||||
*/
|
||||
public AuthResult authenticateByThirdParty(String openId, Integer thirdPartyType,
|
||||
String unionId, String idToken,
|
||||
Object deviceInfo, String ipAddress,
|
||||
String clientId) {
|
||||
try {
|
||||
// 1. 第三方认证
|
||||
UserDetails userDetails = userService.authenticateByOpenId(openId, thirdPartyType.byteValue());
|
||||
|
||||
// 2. 检查用户状态
|
||||
userService.checkUserStatus(userDetails);
|
||||
|
||||
// 3. 设置客户端信息
|
||||
userDetails.setClientId(clientId);
|
||||
|
||||
// 4. 生成Token
|
||||
TokenPair tokenPair = tokenManager.generateToken(userDetails);
|
||||
|
||||
// 5. 构建认证结果
|
||||
return buildAuthResult(tokenPair, userDetails);
|
||||
} catch (Exception e) {
|
||||
throw new AuthenticationException("第三方登录失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据认证类型进行用户认证
|
||||
*
|
||||
* @param credentials 认证凭据
|
||||
* @return 用户详情
|
||||
*/
|
||||
private UserDetails authenticateUser(AuthCredentials credentials) {
|
||||
switch (credentials.getType()) {
|
||||
case PASSWORD:
|
||||
return userService.authenticateByPassword(
|
||||
credentials.getPrincipal(), credentials.getCredentials());
|
||||
|
||||
case VERIFY_CODE:
|
||||
return userService.authenticateByVerifyCode(
|
||||
credentials.getPrincipal(), credentials.getCredentials());
|
||||
|
||||
case EMAIL:
|
||||
return userService.authenticateByEmail(
|
||||
credentials.getPrincipal(), credentials.getCredentials());
|
||||
|
||||
case OPENID:
|
||||
case APPLE:
|
||||
// 对于第三方登录,credentials中包含第三方类型信息
|
||||
return userService.authenticateByOpenId(
|
||||
credentials.getPrincipal(), (byte) 1);
|
||||
|
||||
default:
|
||||
throw new AuthenticationException("不支持的认证类型: " + credentials.getType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建认证结果
|
||||
*
|
||||
* @param tokenPair Token对
|
||||
* @param userDetails 用户详情
|
||||
* @return 认证结果
|
||||
*/
|
||||
private AuthResult buildAuthResult(TokenPair tokenPair, UserDetails userDetails) {
|
||||
// 构建用户信息
|
||||
UserInfo userInfo = new UserInfo();
|
||||
userInfo.setUserId(userDetails.getUserId());
|
||||
userInfo.setUsername(userDetails.getUsername());
|
||||
userInfo.setPhone(maskPhone(userDetails.getPhone()));
|
||||
userInfo.setEmail(maskEmail(userDetails.getEmail()));
|
||||
userInfo.setStatus(userDetails.getStatus().getCode());
|
||||
|
||||
private AuthResult buildAuthResult(TokenPair tokenPair, Account account) {
|
||||
// 构建认证结果
|
||||
AuthResult authResult = new AuthResult();
|
||||
authResult.setAccessToken(tokenPair.getAccessToken());
|
||||
@@ -195,16 +135,10 @@ public class AuthenticationService {
|
||||
authResult.setExpiresIn(tokenPair.getExpiresIn());
|
||||
authResult.setTokenType(tokenPair.getTokenType());
|
||||
authResult.setScope(tokenPair.getScope());
|
||||
authResult.setUserInfo(userInfo);
|
||||
|
||||
// 填充兼容字段
|
||||
authResult.setUid(userDetails.getUserId());
|
||||
authResult.setNetEaseToken(""); // TODO: 需要根据实际业务填充
|
||||
authResult.setAccid(""); // TODO: 需要根据实际业务填充
|
||||
authResult.setUserToken(""); // TODO: 需要根据实际业务填充
|
||||
authResult.setLoginKey(""); // TODO: 需要根据实际业务填充
|
||||
authResult.setUserToken(""); // TODO: 需要根据实际业务填充
|
||||
authResult.setLoginKey(""); // TODO: 需要根据实际业务填充
|
||||
authResult.setUid(account.getUid());
|
||||
authResult.setNetEaseToken(account.getNeteaseToken());
|
||||
|
||||
return authResult;
|
||||
}
|
||||
@@ -239,4 +173,5 @@ public class AuthenticationService {
|
||||
}
|
||||
return localPart.substring(0, 2) + "***@" + parts[1];
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,219 @@
|
||||
package com.accompany.oauth.service;
|
||||
|
||||
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.common.utils.CommonUtil;
|
||||
import com.accompany.core.exception.ServiceException;
|
||||
import com.accompany.core.model.Account;
|
||||
import com.accompany.core.model.AccountLoginRecord;
|
||||
import com.accompany.core.model.PrettyNumberRecord;
|
||||
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;
|
||||
import com.accompany.core.service.account.LoginRecordService;
|
||||
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.email.service.EmailService;
|
||||
import com.accompany.oauth.constant.LoginTypeEnum;
|
||||
import com.accompany.sms.service.SmsService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class MyUserDetailsService {
|
||||
|
||||
@Autowired
|
||||
private JedisService jedisService;
|
||||
@Autowired
|
||||
private AccountService accountService;
|
||||
@Autowired
|
||||
private AccountManageService accountManageService;
|
||||
@Autowired
|
||||
private AccountBlockCheckService accountBlockCheckService;
|
||||
@Autowired
|
||||
private UserAppService userAppService;
|
||||
@Autowired
|
||||
private LoginRecordService loginRecordService;
|
||||
@Autowired
|
||||
private UsersBaseService usersBaseService;
|
||||
@Autowired
|
||||
private SmsService smsService;
|
||||
@Autowired
|
||||
private EmailService emailService;
|
||||
@Autowired
|
||||
private SysConfService sysConfService;
|
||||
@Autowired
|
||||
private RegionNetworkService regionService;
|
||||
@Autowired
|
||||
private PrettyNumberRecordMapper prettyNumberRecordMapper;
|
||||
|
||||
/**
|
||||
* 不允许登录的用户账号类型
|
||||
*/
|
||||
private final static List<Byte> NEED_INTERCEPT_USER_TYPE = Arrays.asList(Constant.DefUser.OFFICIAL,
|
||||
Constant.DefUser.ROBOT, Constant.DefUser.GAME_MANAGE_ROBOT);
|
||||
|
||||
public void login(Account account, LoginTypeEnum loginType, DeviceInfo deviceInfo, String openId) {
|
||||
|
||||
Long uid = account.getUid();
|
||||
|
||||
String deviceId = deviceInfo.getDeviceId();
|
||||
String ip = deviceInfo.getClientIp();
|
||||
String app = deviceInfo.getApp();
|
||||
String appVersion = deviceInfo.getAppVersion();
|
||||
|
||||
Date date = new Date();
|
||||
|
||||
// 拦截指定账号登录
|
||||
Users users = usersBaseService.getUsersByUid(account.getUid());
|
||||
if (users != null && NEED_INTERCEPT_USER_TYPE.contains(users.getDefUser())) {
|
||||
throw new ServiceException(BusiStatus.ILLEGAL_OPERATE);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
accountManageService.checkAccountCancel(uid);
|
||||
|
||||
//更新account信息
|
||||
String newToken = accountService.refreshAndGetNetEaseToken(account);
|
||||
|
||||
account.setNeteaseToken(newToken);
|
||||
account.setApp(deviceInfo.getApp());
|
||||
account.setAppVersion(deviceInfo.getAppVersion());
|
||||
account.setChannel(deviceInfo.getChannel());
|
||||
account.setLinkedmeChannel(deviceInfo.getLinkedmeChannel());
|
||||
account.setDeviceId(deviceInfo.getDeviceId());
|
||||
account.setImei(deviceInfo.getImei());
|
||||
account.setIspType(deviceInfo.getIspType());
|
||||
account.setModel(deviceInfo.getModel());
|
||||
account.setNetType(deviceInfo.getNetType());
|
||||
account.setOs(deviceInfo.getOs());
|
||||
account.setOsversion(deviceInfo.getOsVersion());
|
||||
account.setUpdateTime(date);
|
||||
account.setLastLoginTime(date);
|
||||
account.setLastLoginIp(ip);
|
||||
accountService.updateById(account);
|
||||
|
||||
//更新用户正在使用的app字段
|
||||
userAppService.updateCurrentApp(uid, app, date, ip, appVersion);
|
||||
|
||||
//将用户信息登记
|
||||
AccountLoginRecord accountLoginRecord = buildAccountLoginRecord(ip, account, loginType.getValue(), deviceInfo, openId);
|
||||
loginRecordService.addAccountLoginRecordAsync(accountLoginRecord);
|
||||
}
|
||||
|
||||
public AccountLoginRecord buildAccountLoginRecord(String ipAddress, Account account, byte loginType, DeviceInfo deviceInfo, String openId) {
|
||||
AccountLoginRecord accountLoginRecord = new AccountLoginRecord();
|
||||
accountLoginRecord.setLoginIp(ipAddress);
|
||||
accountLoginRecord.setLoginIpRegion(regionService.getRegion(ipAddress));
|
||||
accountLoginRecord.setUid(account.getUid());
|
||||
accountLoginRecord.setErbanNo(account.getErbanNo());
|
||||
accountLoginRecord.setLoginType(loginType);
|
||||
accountLoginRecord.setDeviceId(deviceInfo.getDeviceId());
|
||||
accountLoginRecord.setPhone(account.getPhone());
|
||||
accountLoginRecord.setEmail(account.getEmail());
|
||||
accountLoginRecord.setApp(account.getApp());
|
||||
accountLoginRecord.setAppVersion(deviceInfo.getAppVersion());
|
||||
accountLoginRecord.setIspType(deviceInfo.getIspType());
|
||||
accountLoginRecord.setModel(deviceInfo.getModel());
|
||||
accountLoginRecord.setOs(deviceInfo.getOs());
|
||||
accountLoginRecord.setOsversion(deviceInfo.getOsVersion());
|
||||
accountLoginRecord.setCreateTime(new Date());
|
||||
|
||||
if (loginType == LoginTypeEnum.APPLE.getValue()) {
|
||||
accountLoginRecord.setAppleUid(openId);
|
||||
}
|
||||
return accountLoginRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
* 普通用户需要用手机验证码登录,官方账号和公会账号不校验验证码
|
||||
*
|
||||
* @param account
|
||||
* @param code
|
||||
*/
|
||||
public void checkCodeByUserType(Account account, String code, LoginTypeEnum loginType) {
|
||||
//是否手机号登录
|
||||
boolean needVerifyCode = LoginTypeEnum.PHONE.getValue() == loginType.getValue()
|
||||
|| LoginTypeEnum.EMAIL.getValue() == loginType.getValue();
|
||||
if (!needVerifyCode) {
|
||||
return;
|
||||
}
|
||||
if (!StringUtils.hasText(code)) {
|
||||
//todo
|
||||
//throw new CustomOAuth2Exception(CustomOAuth2Exception.VERIFY_CODE_ERROR,
|
||||
// BusiStatus.VERIFY_CODE_ERROR.getReasonPhrase());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理密码登录
|
||||
*
|
||||
* @param username 用户登录账号
|
||||
* @param accountPassword
|
||||
* @return 错误提示
|
||||
*/
|
||||
public void validPwd(String username, String password, String accountPassword) {
|
||||
String value = sysConfService.getDefaultSysConfValueById(Constant.SysConfId.PWD_LOGIN_DAY_WRONG_COUNT, "5");
|
||||
Long maxCount = Long.valueOf(value);
|
||||
|
||||
String cacheKey = RedisKey.user_login_pwd_wrong_day_count.getKey();
|
||||
Boolean exits = jedisService.exits(cacheKey);
|
||||
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());
|
||||
}
|
||||
|
||||
if (!password.equals(accountPassword)) {
|
||||
currCount = jedisService.hincrBy(cacheKey, username, 1L);
|
||||
if (!exits) {
|
||||
jedisService.expire(cacheKey, 10 * 60);//10分钟后解锁
|
||||
}
|
||||
|
||||
if (currCount >= maxCount) {
|
||||
//todo
|
||||
//throw new CustomOAuth2Exception(CustomOAuth2Exception.PWD_WRONG_OVER_LIMIT, BusiStatus.PWD_WRONG_OVER_LIMIT.getReasonPhrase());
|
||||
} else {
|
||||
Long remainCount = maxCount - currCount;
|
||||
//todo
|
||||
//throw new CustomOAuth2Exception(CustomOAuth2Exception.PASSWORD_ERROR, String.format(BusiStatus.PASSWORD_ERROR_COUNT.getReasonPhrase(), remainCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,8 +1,19 @@
|
||||
package com.accompany.oauth.service;
|
||||
|
||||
import com.accompany.common.constant.Constant;
|
||||
import com.accompany.common.device.DeviceInfo;
|
||||
import com.accompany.common.status.BusiStatus;
|
||||
import com.accompany.common.utils.CommonUtil;
|
||||
import com.accompany.common.utils.DESUtils;
|
||||
import com.accompany.core.exception.ServiceException;
|
||||
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.constant.UserStatus;
|
||||
import com.accompany.oauth.exception.AuthenticationException;
|
||||
import com.accompany.oauth.model.UserDetails;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -16,110 +27,80 @@ import java.util.HashSet;
|
||||
*/
|
||||
@Service
|
||||
public class UserService {
|
||||
|
||||
|
||||
@Autowired
|
||||
private AccountManageService accountManageService;
|
||||
@Autowired
|
||||
private MyUserDetailsService myUserDetailsService;
|
||||
|
||||
/**
|
||||
* 通过密码认证用户
|
||||
*
|
||||
* @param phone 手机号
|
||||
* @param password 密码
|
||||
*
|
||||
* @param username 手机号
|
||||
* @param password 密码
|
||||
* @param deviceInfo
|
||||
* @return 用户详情
|
||||
* @throws AuthenticationException 认证失败
|
||||
*/
|
||||
public UserDetails authenticateByPassword(String phone, String password) {
|
||||
// TODO: 实现密码认证逻辑,需要连接用户数据库
|
||||
// 这里先提供一个模拟实现
|
||||
|
||||
if ("13800138000".equals(phone) && "123456".equals(password)) {
|
||||
UserDetails userDetails = new UserDetails();
|
||||
userDetails.setUserId(1L);
|
||||
userDetails.setPhone(phone);
|
||||
userDetails.setUsername("test_user");
|
||||
userDetails.setStatus(UserStatus.NORMAL);
|
||||
userDetails.setAuthorities(new HashSet<>(Arrays.asList("read", "write")));
|
||||
return userDetails;
|
||||
@SneakyThrows
|
||||
public Account authenticateByPassword(String username, String password, DeviceInfo deviceInfo) {
|
||||
username = DESUtils.DESAndBase64Decrypt(username, KeyStore.DES_ENCRYPT_KEY);
|
||||
password = DESUtils.DESAndBase64Decrypt(password, KeyStore.DES_ENCRYPT_KEY);
|
||||
password = MD5.getMD5(password);
|
||||
|
||||
Account account = accountManageService.getAccountPyUsername(username, password);
|
||||
if (account == null) {
|
||||
// todo
|
||||
//throw new CustomOAuth2Exception(CustomOAuth2Exception.USER_NOT_EXISTED,
|
||||
// BusiStatus.USER_NOT_EXISTED.getReasonPhrase());
|
||||
}
|
||||
|
||||
throw AuthenticationException.invalidCredentials();
|
||||
|
||||
myUserDetailsService.validPwd(username, password, account.getPassword());
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过验证码认证用户
|
||||
*
|
||||
* @param phone 手机号
|
||||
* @param code 验证码
|
||||
* @return 用户详情
|
||||
* @throws AuthenticationException 认证失败
|
||||
*/
|
||||
public UserDetails authenticateByVerifyCode(String phone, String code) {
|
||||
// TODO: 实现验证码认证逻辑
|
||||
// 1. 验证验证码是否正确且未过期
|
||||
// 2. 查询用户信息
|
||||
// 3. 检查用户状态
|
||||
|
||||
if ("13800138000".equals(phone) && "888888".equals(code)) {
|
||||
UserDetails userDetails = new UserDetails();
|
||||
userDetails.setUserId(1L);
|
||||
userDetails.setPhone(phone);
|
||||
userDetails.setUsername("test_user");
|
||||
userDetails.setStatus(UserStatus.NORMAL);
|
||||
userDetails.setAuthorities(new HashSet<>(Arrays.asList("read", "write")));
|
||||
return userDetails;
|
||||
|
||||
|
||||
public Account authenticateByVerifyCode(String phone, String phoneAreaCode, String code, DeviceInfo deviceInfo) {
|
||||
if (!CommonUtil.checkPhoneFormat(phoneAreaCode, phone)){
|
||||
throw new ServiceException(BusiStatus.PARAMERROR);
|
||||
}
|
||||
|
||||
throw AuthenticationException.invalidVerifyCode();
|
||||
|
||||
Account account = accountManageService.getOrGenAccountByPhone(phone, phoneAreaCode, code, deviceInfo);
|
||||
//校验验证码
|
||||
myUserDetailsService.checkCodeByUserType(account, code, LoginTypeEnum.PHONE);
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过邮箱认证用户
|
||||
*
|
||||
* @param email 邮箱
|
||||
* @param code 验证码
|
||||
*
|
||||
* @param email 邮箱
|
||||
* @param code 验证码
|
||||
* @param deviceInfo
|
||||
* @return 用户详情
|
||||
* @throws AuthenticationException 认证失败
|
||||
*/
|
||||
public UserDetails authenticateByEmail(String email, String code) {
|
||||
// TODO: 实现邮箱认证逻辑
|
||||
// 1. 验证邮箱验证码是否正确且未过期
|
||||
// 2. 查询用户信息
|
||||
// 3. 检查用户状态
|
||||
|
||||
if ("test@example.com".equals(email) && "666666".equals(code)) {
|
||||
UserDetails userDetails = new UserDetails();
|
||||
userDetails.setUserId(2L);
|
||||
userDetails.setEmail(email);
|
||||
userDetails.setUsername("email_user");
|
||||
userDetails.setStatus(UserStatus.NORMAL);
|
||||
userDetails.setAuthorities(new HashSet<>(Arrays.asList("read", "write")));
|
||||
return userDetails;
|
||||
}
|
||||
|
||||
throw AuthenticationException.invalidVerifyCode();
|
||||
public Account authenticateByEmail(String email, String code, DeviceInfo deviceInfo) {
|
||||
Account account = accountManageService.getOrGenAccountByEmail(email, code, deviceInfo, deviceInfo.getClientIp());
|
||||
|
||||
//校验验证码
|
||||
myUserDetailsService.checkCodeByUserType(account, code, LoginTypeEnum.EMAIL);
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过OpenID认证用户
|
||||
*
|
||||
*
|
||||
* @param openId OpenID
|
||||
* @param type 第三方类型 (1-微信, 2-Apple等)
|
||||
* @return 用户详情
|
||||
* @throws AuthenticationException 认证失败
|
||||
*/
|
||||
public UserDetails authenticateByOpenId(String openId, Byte type) {
|
||||
// TODO: 实现OpenID认证逻辑
|
||||
// 1. 验证OpenID的有效性
|
||||
// 2. 查询绑定的用户信息
|
||||
// 3. 检查用户状态
|
||||
|
||||
if ("openid_test_123".equals(openId)) {
|
||||
UserDetails userDetails = new UserDetails();
|
||||
userDetails.setUserId(3L);
|
||||
userDetails.setUsername("openid_user");
|
||||
userDetails.setStatus(UserStatus.NORMAL);
|
||||
userDetails.setAuthorities(new HashSet<>(Arrays.asList("read", "write")));
|
||||
return userDetails;
|
||||
}
|
||||
|
||||
throw AuthenticationException.userNotFound();
|
||||
public Account authenticateByOpenId(Byte type, String openId, String unionId, String idToken, DeviceInfo deviceInfo) {
|
||||
return accountManageService.getOrGenAccountByOpenid(type, openId, unionId, idToken, deviceInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,21 +128,17 @@ public class UserService {
|
||||
|
||||
/**
|
||||
* 检查用户状态是否可用
|
||||
*
|
||||
* @param userDetails 用户详情
|
||||
*
|
||||
* @throws AuthenticationException 用户不可用
|
||||
*/
|
||||
public void checkUserStatus(UserDetails userDetails) {
|
||||
if (userDetails == null) {
|
||||
throw AuthenticationException.userNotFound();
|
||||
}
|
||||
|
||||
if (userDetails.getStatus() == UserStatus.FROZEN) {
|
||||
public void checkUserStatus(Account account) {
|
||||
if (Constant.AccountState.block.equals(account.getState())) {
|
||||
throw AuthenticationException.userFrozen();
|
||||
}
|
||||
|
||||
if (userDetails.getStatus() == UserStatus.DELETED) {
|
||||
if (Constant.AccountState.cancel.equals(account.getState())) {
|
||||
throw AuthenticationException.userNotFound();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,7 +1,5 @@
|
||||
package com.accompany.oauth.ticket;
|
||||
|
||||
import com.accompany.oauth.model.UserDetails;
|
||||
import com.accompany.oauth.util.JwtUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.jsonwebtoken.JwtBuilder;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
|
@@ -1,63 +0,0 @@
|
||||
package com.accompany.oauth.ticket;
|
||||
|
||||
import com.accompany.oauth.model.UserDetails;
|
||||
import org.redisson.api.RBucket;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Redis票据存储实现
|
||||
* 迁移自OAuth2模块的RedisTicketStore
|
||||
*
|
||||
* @author Accompany OAuth Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Component
|
||||
public class RedisTicketStore implements TicketStore {
|
||||
|
||||
private static final String TICKET_PREFIX = "oauth:ticket:";
|
||||
private static final String ACCESS_TOKEN_PREFIX = "oauth:uid_access_token:";
|
||||
|
||||
@Autowired
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
@Override
|
||||
public void storeTicket(Ticket ticket, UserDetails userDetails) {
|
||||
Long uid = userDetails.getUserId();
|
||||
|
||||
// 存储用户的ticket
|
||||
String ticketKey = TICKET_PREFIX + uid;
|
||||
RBucket<String> ticketBucket = redissonClient.getBucket(ticketKey);
|
||||
ticketBucket.set(ticket.getValue(), ticket.getExpiresIn(), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readTicket(String key) {
|
||||
String ticketKey = TICKET_PREFIX + key;
|
||||
RBucket<String> bucket = redissonClient.getBucket(ticketKey);
|
||||
return bucket.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readAccessToken(String key) {
|
||||
String accessTokenKey = ACCESS_TOKEN_PREFIX + key;
|
||||
RBucket<String> bucket = redissonClient.getBucket(accessTokenKey);
|
||||
return bucket.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储用户的access token
|
||||
*
|
||||
* @param uid 用户ID
|
||||
* @param accessToken 访问令牌
|
||||
* @param expiresIn 过期时间(秒)
|
||||
*/
|
||||
public void storeAccessToken(Long uid, String accessToken, long expiresIn) {
|
||||
String accessTokenKey = ACCESS_TOKEN_PREFIX + uid;
|
||||
RBucket<String> bucket = redissonClient.getBucket(accessTokenKey);
|
||||
bucket.set(accessToken, expiresIn, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
@@ -1,7 +1,5 @@
|
||||
package com.accompany.oauth.ticket;
|
||||
|
||||
import com.accompany.oauth.model.UserDetails;
|
||||
|
||||
/**
|
||||
* Ticket增强器接口
|
||||
* 迁移自OAuth2模块的TicketEnhancer
|
||||
|
@@ -2,7 +2,6 @@ package com.accompany.oauth.ticket;
|
||||
|
||||
import com.accompany.oauth.manager.TokenManager;
|
||||
import com.accompany.oauth.model.TokenValidation;
|
||||
import com.accompany.oauth.model.UserDetails;
|
||||
import com.accompany.oauth.service.UserService;
|
||||
import com.accompany.oauth.exception.TokenException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@@ -1,7 +1,5 @@
|
||||
package com.accompany.oauth.ticket;
|
||||
|
||||
import com.accompany.oauth.model.UserDetails;
|
||||
|
||||
/**
|
||||
* Ticket存储接口
|
||||
* 迁移自OAuth2模块的TicketStore
|
||||
|
@@ -1,8 +1,11 @@
|
||||
package com.accompany.oauth.util;
|
||||
|
||||
import com.accompany.common.utils.UUIDUtil;
|
||||
import com.accompany.oauth.config.OAuthConfig;
|
||||
import com.accompany.oauth.exception.TokenException;
|
||||
import io.jsonwebtoken.*;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -11,6 +14,7 @@ import javax.crypto.SecretKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* JWT工具类 - 使用更安全的实现
|
||||
@@ -20,36 +24,39 @@ import java.util.Set;
|
||||
*/
|
||||
@Component
|
||||
public class JwtUtil {
|
||||
|
||||
@Value("${oauth.jwt.secret:accompany-oauth-secret-key-for-jwt-token-generation}")
|
||||
private String secret;
|
||||
|
||||
@Autowired
|
||||
private OAuthConfig oAuthConfig;
|
||||
|
||||
@Value("${oauth.jwt.access-token-expiration:7200}")
|
||||
private long accessTokenExpiration;
|
||||
|
||||
@Value("${oauth.jwt.refresh-token-expiration:2592000}")
|
||||
private long refreshTokenExpiration;
|
||||
|
||||
|
||||
public long getAccessTokenExpiration() {
|
||||
return accessTokenExpiration;
|
||||
}
|
||||
|
||||
public long getRefreshTokenExpiration() {
|
||||
return refreshTokenExpiration;
|
||||
}
|
||||
|
||||
private SecretKey secretKey;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// 确保密钥长度足够
|
||||
if (secret.getBytes(StandardCharsets.UTF_8).length < 32) {
|
||||
secret = secret + "0123456789abcdef0123456789abcdef";
|
||||
}
|
||||
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
|
||||
this.secretKey = Keys.hmacShaKeyFor(oAuthConfig.getJwtSignKey().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成访问令牌 (兼容OAuth2格式)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param clientId 客户端ID
|
||||
* @param scopes 权限范围
|
||||
* @return JWT令牌
|
||||
*/
|
||||
public String generateAccessToken(Long userId, String clientId, Set<String> scopes) {
|
||||
public String generateAccessToken(Long userId) {
|
||||
Date now = new Date();
|
||||
Date expiration = new Date(now.getTime() + accessTokenExpiration * 1000);
|
||||
|
||||
@@ -57,20 +64,15 @@ public class JwtUtil {
|
||||
.setSubject(userId.toString())
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(expiration)
|
||||
.claim("client_id", clientId)
|
||||
.claim("client_id", oAuthConfig.getClientId())
|
||||
.claim("token_type", "access_token")
|
||||
.claim("uid", userId) // 兼容OAuth2
|
||||
.claim("user_name", userId.toString()) // 兼容OAuth2
|
||||
.claim("authorities", "oauth2") // 兼容OAuth2
|
||||
.claim("jti", generateJti()) // 兼容OAuth2
|
||||
.claim("scope", "read write")
|
||||
.signWith(secretKey, SignatureAlgorithm.HS256);
|
||||
|
||||
if (scopes != null && !scopes.isEmpty()) {
|
||||
builder.claim("scope", String.join(" ", scopes));
|
||||
} else {
|
||||
builder.claim("scope", "read write"); // 默认权限
|
||||
}
|
||||
|
||||
return builder.compact();
|
||||
}
|
||||
|
||||
@@ -78,10 +80,9 @@ public class JwtUtil {
|
||||
* 生成刷新令牌
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param clientId 客户端ID
|
||||
* @return JWT令牌
|
||||
*/
|
||||
public String generateRefreshToken(Long userId, String clientId) {
|
||||
public String generateRefreshToken(Long userId) {
|
||||
Date now = new Date();
|
||||
Date expiration = new Date(now.getTime() + refreshTokenExpiration * 1000);
|
||||
|
||||
@@ -89,7 +90,7 @@ public class JwtUtil {
|
||||
.setSubject(userId.toString())
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(expiration)
|
||||
.claim("client_id", clientId)
|
||||
.claim("client_id", oAuthConfig.getClientId())
|
||||
.claim("token_type", "refresh_token")
|
||||
.signWith(secretKey, SignatureAlgorithm.HS256)
|
||||
.compact();
|
||||
@@ -116,39 +117,6 @@ public class JwtUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取用户ID
|
||||
*
|
||||
* @param token JWT令牌
|
||||
* @return 用户ID
|
||||
*/
|
||||
public Long getUserIdFromToken(String token) {
|
||||
Claims claims = validateAndParseToken(token);
|
||||
return Long.valueOf(claims.getSubject());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取客户端ID
|
||||
*
|
||||
* @param token JWT令牌
|
||||
* @return 客户端ID
|
||||
*/
|
||||
public String getClientIdFromToken(String token) {
|
||||
Claims claims = validateAndParseToken(token);
|
||||
return claims.get("client_id", String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取权限范围
|
||||
*
|
||||
* @param token JWT令牌
|
||||
* @return 权限范围
|
||||
*/
|
||||
public String getScopeFromToken(String token) {
|
||||
Claims claims = validateAndParseToken(token);
|
||||
return claims.get("scope", String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查令牌是否过期
|
||||
*
|
||||
@@ -175,30 +143,12 @@ public class JwtUtil {
|
||||
return claims.getExpiration();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取访问令牌有效期(秒)
|
||||
*
|
||||
* @return 有效期秒数
|
||||
*/
|
||||
public long getAccessTokenExpiration() {
|
||||
return accessTokenExpiration;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取刷新令牌有效期(秒)
|
||||
*
|
||||
* @return 有效期秒数
|
||||
*/
|
||||
public long getRefreshTokenExpiration() {
|
||||
return refreshTokenExpiration;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成JWT Token ID (兼容OAuth2)
|
||||
*
|
||||
* @return JTI
|
||||
*/
|
||||
private String generateJti() {
|
||||
return java.util.UUID.randomUUID().toString().replace("-", "");
|
||||
return UUIDUtil.get();
|
||||
}
|
||||
}
|
@@ -5,6 +5,8 @@ import com.accompany.common.device.DeviceInfo;
|
||||
import com.accompany.common.result.BusiResult;
|
||||
import com.accompany.common.status.BusiStatus;
|
||||
import com.accompany.common.utils.DESUtils;
|
||||
import com.accompany.common.utils.IPUtils;
|
||||
import com.accompany.core.base.DeviceInfoContextHolder;
|
||||
import com.accompany.core.base.UidContextHolder;
|
||||
import com.accompany.core.util.KeyStore;
|
||||
import com.accompany.oauth.dto.AuthResult;
|
||||
@@ -12,6 +14,7 @@ import com.accompany.oauth.model.TokenValidation;
|
||||
import com.accompany.oauth.service.AccountManageService;
|
||||
import com.accompany.oauth.service.AuthenticationService;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -26,6 +29,7 @@ import java.util.Map;
|
||||
* @author Accompany OAuth Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/acc")
|
||||
public class AccountController {
|
||||
@@ -38,46 +42,27 @@ public class AccountController {
|
||||
@Autowired
|
||||
private AccountManageService accountManageService;
|
||||
|
||||
/**
|
||||
* 用户注销 (兼容OAuth2格式)
|
||||
*
|
||||
* @param accessToken 访问令牌
|
||||
* @return BusiResult响应结果
|
||||
*/
|
||||
@PostMapping("/logout")
|
||||
public BusiResult<Void> logout(@RequestParam("access_token") String accessToken) {
|
||||
if (StringUtils.hasText(accessToken)) {
|
||||
authenticationService.revokeToken(accessToken);
|
||||
}
|
||||
return BusiResult.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 第三方登录 (兼容OAuth2格式)
|
||||
*
|
||||
* @param openid OpenID
|
||||
* @param openId OpenID
|
||||
* @param type 第三方类型
|
||||
* @param unionid UnionID(可选)
|
||||
* @param deviceInfo 设备信息
|
||||
* @param app 应用类型
|
||||
* @param unionId UnionID(可选)
|
||||
* @param idToken ID Token(可选)
|
||||
* @param request HTTP请求
|
||||
* @return 直接返回AuthResult结构
|
||||
*/
|
||||
@RequestMapping("/third/login")
|
||||
public AuthResult thirdLogin(@RequestParam String openid,
|
||||
@RequestParam Integer type,
|
||||
@RequestParam(required = false) String unionid,
|
||||
DeviceInfo deviceInfo,
|
||||
@RequestParam(required = false) AppEnum app,
|
||||
@RequestParam(required = false) String idToken,
|
||||
HttpServletRequest request) {
|
||||
// TODO: 实现第三方登录逻辑
|
||||
// 1. 验证第三方登录信息
|
||||
// 2. 查询或创建用户
|
||||
// 3. 生成Token
|
||||
|
||||
throw new UnsupportedOperationException("第三方登录功能暂未实现");
|
||||
public BusiResult<AuthResult> thirdLogin(
|
||||
Byte type,
|
||||
@RequestParam("openid") String openId,
|
||||
@RequestParam("openid")String unionId,
|
||||
String idToken) {
|
||||
|
||||
log.info("/acc/third/login? app {} , type {}, unionId {}", type, unionId);
|
||||
DeviceInfo deviceInfo = DeviceInfoContextHolder.get();
|
||||
AuthResult authResult = authenticationService.authenticateByThirdParty(type, openId, unionId, idToken, deviceInfo);
|
||||
return BusiResult.success(authResult);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,7 +121,21 @@ public class AccountController {
|
||||
|
||||
throw new UnsupportedOperationException("密码修改功能暂未实现");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用户注销 (兼容OAuth2格式)
|
||||
*
|
||||
* @param accessToken 访问令牌
|
||||
* @return BusiResult响应结果
|
||||
*/
|
||||
@PostMapping("/logout")
|
||||
public BusiResult<Void> logout(@RequestParam("access_token") String accessToken) {
|
||||
if (StringUtils.hasText(accessToken)) {
|
||||
authenticationService.revokeToken(accessToken);
|
||||
}
|
||||
return BusiResult.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求中提取Token
|
||||
*
|
||||
|
@@ -4,11 +4,8 @@ import com.accompany.common.result.BusiResult;
|
||||
import com.accompany.common.status.BusiStatus;
|
||||
import com.accompany.common.utils.IPUtils;
|
||||
import com.accompany.core.base.DeviceInfoContextHolder;
|
||||
import com.accompany.oauth.constant.GrantTypeEnum;
|
||||
import com.accompany.oauth.constant.OAuthConstants;
|
||||
import com.accompany.oauth.dto.AuthCredentials;
|
||||
import com.accompany.core.exception.ServiceException;
|
||||
import com.accompany.oauth.dto.AuthResult;
|
||||
import com.accompany.oauth.dto.TokenRequest;
|
||||
import com.accompany.common.device.DeviceInfo;
|
||||
import com.accompany.oauth.service.AuthenticationService;
|
||||
import com.accompany.oauth.ticket.TicketService;
|
||||
@@ -36,76 +33,40 @@ public class OAuthController {
|
||||
private TicketService ticketService;
|
||||
|
||||
/**
|
||||
* 统一认证端点 - 获取Token (兼容OAuth2格式)
|
||||
* Token获取接口 - 简化实现 (兼容OAuth2格式)
|
||||
* 支持表单提交和JSON提交两种方式
|
||||
*
|
||||
* @param grantType 授权类型
|
||||
* @param phone 手机号/用户名
|
||||
* @param email 邮箱
|
||||
* @param grantType 授权类型 (password, verify_code, email等)
|
||||
* @param password 密码
|
||||
* @param code 验证码
|
||||
* @param clientId 客户端ID
|
||||
* @param clientSecret 客户端密钥
|
||||
* @param phoneAreaCode 电话区号
|
||||
* @return 直接返回AuthResult结构,兼容OAuth2的CustomOAuth2AccessToken
|
||||
*/
|
||||
@PostMapping("/token")
|
||||
public AuthResult token(@RequestParam(value = "grant_type", required = false) String grantType,
|
||||
@RequestParam(value = "client_id", required = false) String clientId,
|
||||
@RequestParam(value = "client_secret", required = false) String clientSecret,
|
||||
@RequestParam(value = "phone", required = false) String phone,
|
||||
@RequestParam(value = "phoneAreaCode", required = false) String phoneAreaCode,
|
||||
@RequestParam(value = "password", required = false) String password,
|
||||
@RequestParam(value = "email", required = false) String email,
|
||||
@RequestParam(value = "code", required = false) String code) {
|
||||
|
||||
DeviceInfo deviceInfo = DeviceInfoContextHolder.get();
|
||||
|
||||
// 支持两种格式:表单提交和JSON提交
|
||||
TokenRequest request = new TokenRequest();
|
||||
request.setGrantType(grantType);
|
||||
request.setUsername(StringUtils.hasText(phone) ? phone : email);
|
||||
request.setPassword(password);
|
||||
request.setCode(code);
|
||||
request.setClientId(clientId);
|
||||
request.setClientSecret(clientSecret);
|
||||
request.setDeviceId(deviceInfo.getDeviceId());
|
||||
|
||||
// 1. 验证请求参数
|
||||
validateTokenRequest(request);
|
||||
public AuthResult token(@RequestParam(value = "grant_type") String grantType,
|
||||
@RequestParam(value = "username") String username,
|
||||
@RequestParam(value = "password") String password,
|
||||
@RequestParam(value = "phone") String phone,
|
||||
@RequestParam(value = "phoneAreaCode") String phoneAreaCode,
|
||||
@RequestParam(value = "email") String email,
|
||||
@RequestParam(value = "code") String code) {
|
||||
|
||||
// 2. 构建认证凭据
|
||||
AuthCredentials credentials = buildAuthCredentials(request, phoneAreaCode, deviceInfo);
|
||||
|
||||
// 3. 执行认证
|
||||
AuthResult authResult = authenticationService.authenticate(credentials);
|
||||
|
||||
return authResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新Token端点 (兼容OAuth2格式)
|
||||
*
|
||||
* @param request Token刷新请求
|
||||
* @return 直接返回AuthResult结构
|
||||
*/
|
||||
@PostMapping("/refresh")
|
||||
public AuthResult refresh(@RequestBody TokenRequest request) {
|
||||
if (!OAuthConstants.GrantType.REFRESH_TOKEN.equals(request.getGrantType()) ||
|
||||
!StringUtils.hasText(request.getRefreshToken())) {
|
||||
throw new IllegalArgumentException("缺少刷新令牌");
|
||||
// 1. 简单验证
|
||||
if (!StringUtils.hasText(grantType)) {
|
||||
throw new ServiceException(BusiStatus.PARAMERROR);
|
||||
}
|
||||
|
||||
// 执行Token刷新
|
||||
AuthResult authResult = authenticationService.refreshToken(request.getRefreshToken());
|
||||
// 2. 获取设备信息
|
||||
DeviceInfo deviceInfo = DeviceInfoContextHolder.get();
|
||||
|
||||
return authResult;
|
||||
// 3. 直接调用服务,不做中间转换
|
||||
return authenticationService.authenticate(grantType,
|
||||
username, password, phone, phoneAreaCode, email, code, deviceInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ticket签发端点 (兼容OAuth2格式)
|
||||
* Ticket签发接口 - 简化实现 (兼容OAuth2格式)
|
||||
*
|
||||
* @param issueType 签发类型
|
||||
* @param issueType 签发类型 (once/multi)
|
||||
* @param accessToken 访问令牌
|
||||
* @param httpRequest HTTP请求
|
||||
* @return BusiResult包装的Ticket响应
|
||||
@@ -115,16 +76,17 @@ public class OAuthController {
|
||||
@RequestParam("access_token") String accessToken,
|
||||
HttpServletRequest httpRequest) {
|
||||
try {
|
||||
// 验证签发类型
|
||||
if (!"once".equals(issueType) && !"multi".equals(issueType)) {
|
||||
throw new IllegalArgumentException("unsupported ticket issue type");
|
||||
throw new IllegalArgumentException("不支持的票据签发类型");
|
||||
}
|
||||
|
||||
// 直接传递accessToken给TicketService
|
||||
Map<String, Object> result = ticketService.issueTicket(accessToken);
|
||||
|
||||
// 获取IP地址并保存登录记录
|
||||
// 获取IP地址并异步记录用户登录信息
|
||||
String ipAddress = IPUtils.getRealIpAddress(httpRequest);
|
||||
Long uid = (Long) result.get("uid");
|
||||
|
||||
ticketService.saveLoginRecord(uid, ipAddress, null);
|
||||
|
||||
return BusiResult.success(result);
|
||||
@@ -133,62 +95,6 @@ public class OAuthController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证Token请求参数
|
||||
*
|
||||
* @param request Token请求
|
||||
*/
|
||||
private void validateTokenRequest(TokenRequest request) {
|
||||
if (!StringUtils.hasText(request.getGrantType())) {
|
||||
throw new IllegalArgumentException("缺少grant_type参数");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(request.getUsername()) &&
|
||||
!OAuthConstants.GrantType.REFRESH_TOKEN.equals(request.getGrantType()) &&
|
||||
!OAuthConstants.GrantType.EMAIL.equals(request.getGrantType())) {
|
||||
throw new IllegalArgumentException("缺少username参数");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(request.getPassword()) &&
|
||||
!OAuthConstants.GrantType.REFRESH_TOKEN.equals(request.getGrantType()) &&
|
||||
!OAuthConstants.GrantType.EMAIL.equals(request.getGrantType())) {
|
||||
throw new IllegalArgumentException("缺少password参数");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(request.getCode()) &&
|
||||
OAuthConstants.GrantType.EMAIL.equals(request.getGrantType())) {
|
||||
throw new IllegalArgumentException("缺少验证码参数");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建认证凭据
|
||||
*
|
||||
* @param request Token请求
|
||||
* @param phoneAreaCode 电话区号
|
||||
* @return 认证凭据
|
||||
*/
|
||||
private AuthCredentials buildAuthCredentials(TokenRequest request,
|
||||
String phoneAreaCode, DeviceInfo deviceInfo) {
|
||||
AuthCredentials credentials = new AuthCredentials();
|
||||
|
||||
// 设置认证类型
|
||||
GrantTypeEnum grantTypeEnum = GrantTypeEnum.fromCode(request.getGrantType());
|
||||
credentials.setType(grantTypeEnum);
|
||||
|
||||
// 设置主体和凭据
|
||||
credentials.setPrincipal(request.getUsername());
|
||||
credentials.setCredentials(request.getPassword());
|
||||
|
||||
// 设置客户端信息
|
||||
credentials.setClientId(request.getClientId());
|
||||
|
||||
// 设置权限范围
|
||||
credentials.setScope(OAuthConstants.Scope.ALL);
|
||||
|
||||
credentials.setDeviceInfo(deviceInfo);
|
||||
|
||||
return credentials;
|
||||
}
|
||||
|
||||
}
|
@@ -36,47 +36,6 @@ public class OAuth2CompatibilityIntegrationTest {
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 测试OAuth2 Token端点兼容性 - 表单格式
|
||||
*/
|
||||
@Test
|
||||
public void testOAuth2TokenEndpoint_FormFormat() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setRemoteAddr("127.0.0.1");
|
||||
|
||||
try {
|
||||
AuthResult result = oauthController.token(
|
||||
"password", // grant_type
|
||||
"testuser", // phone
|
||||
null, // email
|
||||
"password123", // password
|
||||
null, // code
|
||||
"test-client", // client_id
|
||||
"read write", // scope
|
||||
"device-001"
|
||||
);
|
||||
|
||||
// 验证OAuth2兼容的响应结构
|
||||
assertNotNull(result);
|
||||
assertNotNull(result.getAccessToken());
|
||||
assertNotNull(result.getRefreshToken());
|
||||
assertNotNull(result.getTokenType());
|
||||
assertEquals("bearer", result.getTokenType().toLowerCase());
|
||||
assertTrue(result.getExpiresIn() > 0);
|
||||
|
||||
// 验证OAuth2特有字段
|
||||
assertNotNull(result.getUserToken());
|
||||
assertNotNull(result.getLoginKey());
|
||||
|
||||
System.out.println("OAuth2 Token Response: " + objectMapper.writeValueAsString(result));
|
||||
|
||||
} catch (Exception e) {
|
||||
// 在测试环境中,由于没有真实的用户服务实现,预期会抛出异常
|
||||
assertTrue(e.getMessage().contains("用户") || e.getMessage().contains("User") ||
|
||||
e.getMessage().contains("认证") || e.getMessage().contains("authentication"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试账户管理端点兼容性
|
||||
*/
|
||||
@@ -153,30 +112,6 @@ public class OAuth2CompatibilityIntegrationTest {
|
||||
assertEquals(authResult.getLoginKey(), deserializedResult.getLoginKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试Token请求参数验证
|
||||
*/
|
||||
@Test
|
||||
public void testTokenRequestValidation() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setRemoteAddr("127.0.0.1");
|
||||
|
||||
// 测试缺少grant_type
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
oauthController.token(null, "user", null, "pass", null, null, null, null);
|
||||
});
|
||||
|
||||
// 测试缺少username
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
oauthController.token("password", null, null, null, null, null, null, null);
|
||||
});
|
||||
|
||||
// 测试缺少password
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
oauthController.token("password", "user", null, null, null, null, null, null);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证支持的grant_type类型
|
||||
*/
|
||||
|
Reference in New Issue
Block a user