oauth-重写-简化合并组件
This commit is contained in:
@@ -1,120 +0,0 @@
|
||||
package com.accompany.core.util;
|
||||
|
||||
|
||||
import com.accompany.common.config.WebSecurityConfig;
|
||||
import com.accompany.core.model.Account;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwtBuilder;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Created by yuanyi on 2019/2/21.
|
||||
*/
|
||||
|
||||
@Component
|
||||
public class JwtUtils {
|
||||
|
||||
/**
|
||||
* 用户登录成功后生成Jwt
|
||||
* 使用Hs256算法 私匙使用用户密码
|
||||
* @param ttlMillis jwt过期时间
|
||||
* @return
|
||||
*/
|
||||
public String createJWT(Long ttlMillis, Long uid) {
|
||||
//指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
|
||||
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
|
||||
|
||||
//生成JWT的时间
|
||||
long nowMillis = System.currentTimeMillis();
|
||||
Date now = new Date(nowMillis);
|
||||
|
||||
//创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
|
||||
Map<String, Object> claims = new HashMap<String, Object>();
|
||||
claims.put("uid", uid);
|
||||
|
||||
//生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
|
||||
// String key = PropertyUtil.getProperty("jwtH5Key");
|
||||
String key = WebSecurityConfig.jwtWebKey;
|
||||
|
||||
//生成签发人,这里以平台号为签发人
|
||||
String subject = uid.toString();
|
||||
|
||||
|
||||
|
||||
//下面就是在为payload添加各种标准声明和私有声明了
|
||||
//这里其实就是new一个JwtBuilder,设置jwt的body
|
||||
JwtBuilder builder = Jwts.builder()
|
||||
//如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
|
||||
.setClaims(claims)
|
||||
//设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
|
||||
.setId(UUID.randomUUID().toString())
|
||||
//iat: jwt的签发时间
|
||||
.setIssuedAt(now)
|
||||
//代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。
|
||||
.setSubject(subject)
|
||||
//设置签名使用的签名算法和签名使用的秘钥
|
||||
.signWith(signatureAlgorithm, key);
|
||||
if (ttlMillis >= 0) {
|
||||
long expMillis = nowMillis + ttlMillis;
|
||||
Date exp = new Date(expMillis);
|
||||
//设置过期时间
|
||||
builder.setExpiration(exp);
|
||||
}
|
||||
return builder.compact();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Token的解密
|
||||
* @param token 加密后的token
|
||||
* @return
|
||||
*/
|
||||
public Claims parseJWT(String token) {
|
||||
//签名秘钥,和生成的签名的秘钥一模一样
|
||||
String key = WebSecurityConfig.jwtWebKey;
|
||||
|
||||
//得到DefaultJwtParser
|
||||
Claims claims = Jwts.parser()
|
||||
//设置签名的秘钥
|
||||
.setSigningKey(key)
|
||||
//设置需要解析的jwt
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
return claims;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验token
|
||||
* 在这里可以使用官方的校验,我这里校验的是token中携带的密码于数据库一致的话就校验通过
|
||||
* @param token
|
||||
* @return
|
||||
*/
|
||||
public Boolean isVerify(String token, Account account) {
|
||||
//签名秘钥,和生成的签名的秘钥一模一样
|
||||
String key = WebSecurityConfig.jwtWebKey;
|
||||
|
||||
//得到DefaultJwtParser
|
||||
Claims claims = Jwts.parser()
|
||||
//设置签名的秘钥
|
||||
.setSigningKey(key)
|
||||
//设置需要解析的jwt
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
|
||||
if (claims.get("password").equals(account.getPassword())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -1,49 +0,0 @@
|
||||
package com.accompany.oauth.constant;
|
||||
|
||||
/**
|
||||
* 用户状态枚举
|
||||
*
|
||||
* @author Accompany OAuth Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public enum UserStatus {
|
||||
/**
|
||||
* 正常状态
|
||||
*/
|
||||
NORMAL(1, "正常"),
|
||||
|
||||
/**
|
||||
* 已冻结
|
||||
*/
|
||||
FROZEN(2, "已冻结"),
|
||||
|
||||
/**
|
||||
* 已删除
|
||||
*/
|
||||
DELETED(3, "已删除");
|
||||
|
||||
private final int code;
|
||||
private final String description;
|
||||
|
||||
UserStatus(int code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public static UserStatus fromCode(int code) {
|
||||
for (UserStatus status : values()) {
|
||||
if (status.code == code) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("未知的用户状态: " + code);
|
||||
}
|
||||
}
|
@@ -1,112 +0,0 @@
|
||||
package com.accompany.oauth.dto;
|
||||
|
||||
import com.accompany.oauth.constant.GrantTypeEnum;
|
||||
import com.accompany.common.device.DeviceInfo;
|
||||
|
||||
/**
|
||||
* 认证凭据DTO
|
||||
*
|
||||
* @author Accompany OAuth Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class AuthCredentials {
|
||||
|
||||
/**
|
||||
* 认证类型
|
||||
*/
|
||||
private GrantTypeEnum type;
|
||||
|
||||
/**
|
||||
* 主体标识(手机号/邮箱/OpenID等)
|
||||
*/
|
||||
private String principal;
|
||||
|
||||
/**
|
||||
* 凭据(密码/验证码等)
|
||||
*/
|
||||
private String credentials;
|
||||
|
||||
/**
|
||||
* 客户端ID
|
||||
*/
|
||||
private String clientId;
|
||||
|
||||
/**
|
||||
* 设备信息
|
||||
*/
|
||||
private DeviceInfo deviceInfo;
|
||||
|
||||
/**
|
||||
* 权限范围
|
||||
*/
|
||||
private String scope;
|
||||
|
||||
public AuthCredentials() {
|
||||
}
|
||||
|
||||
public AuthCredentials(GrantTypeEnum type, String principal, String credentials) {
|
||||
this.type = type;
|
||||
this.principal = principal;
|
||||
this.credentials = credentials;
|
||||
}
|
||||
|
||||
public GrantTypeEnum getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(GrantTypeEnum type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getPrincipal() {
|
||||
return principal;
|
||||
}
|
||||
|
||||
public void setPrincipal(String principal) {
|
||||
this.principal = principal;
|
||||
}
|
||||
|
||||
public String getCredentials() {
|
||||
return credentials;
|
||||
}
|
||||
|
||||
public void setCredentials(String credentials) {
|
||||
this.credentials = credentials;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public DeviceInfo getDeviceInfo() {
|
||||
return deviceInfo;
|
||||
}
|
||||
|
||||
public void setDeviceInfo(DeviceInfo deviceInfo) {
|
||||
this.deviceInfo = deviceInfo;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public void setScope(String scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AuthCredentials{" +
|
||||
"type=" + type +
|
||||
", principal='" + principal + '\'' +
|
||||
", credentials='[PROTECTED]'" +
|
||||
", clientId='" + clientId + '\'' +
|
||||
", deviceInfo=" + deviceInfo +
|
||||
", scope='" + scope + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
@@ -1,59 +0,0 @@
|
||||
package com.accompany.oauth.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* OAuth Token请求DTO
|
||||
*
|
||||
* @author Accompany OAuth Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Data
|
||||
public class TokenRequest {
|
||||
|
||||
/**
|
||||
* 授权类型 (password, verify_code, email, openid, refresh_token)
|
||||
*/
|
||||
private String grantType;
|
||||
|
||||
/**
|
||||
* 用户名/手机号/邮箱/OpenID
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码/验证码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 验证码 (兼容OAuth2)
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 客户端ID
|
||||
*/
|
||||
private String clientId;
|
||||
|
||||
/**
|
||||
* 客户端密钥
|
||||
*/
|
||||
private String clientSecret;
|
||||
|
||||
/**
|
||||
* 刷新令牌(当grant_type为refresh_token时使用)
|
||||
*/
|
||||
private String refreshToken;
|
||||
|
||||
/**
|
||||
* 第三方登录类型(Apple/微信等)
|
||||
*/
|
||||
private String thirdType;
|
||||
|
||||
/**
|
||||
* 设备ID
|
||||
*/
|
||||
private String deviceId;
|
||||
|
||||
}
|
@@ -1,68 +1,99 @@
|
||||
package com.accompany.oauth.ticket;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Ticket接口 - OAuth访问票据
|
||||
* 迁移自OAuth2模块的Ticket接口
|
||||
* Ticket类 - OAuth访问票据
|
||||
* 合并了接口和默认实现的功能
|
||||
*
|
||||
* @author Accompany OAuth Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface Ticket {
|
||||
public class Ticket implements Serializable {
|
||||
public static final String ONCE_TYPE = "once";
|
||||
public static final String MULTI_TYPE = "multi";
|
||||
|
||||
String ONCE_TYPE = "once";
|
||||
|
||||
String MULTI_TYPE = "multi";
|
||||
private String value;
|
||||
private Date expiration;
|
||||
private String ticketType = ONCE_TYPE.toLowerCase();
|
||||
private String accessToken;
|
||||
private Set<String> scope;
|
||||
private Map<String, Object> additionalInformation = Collections.emptyMap();
|
||||
|
||||
/**
|
||||
* 获取附加信息
|
||||
*
|
||||
* @return 附加信息Map
|
||||
* 创建票据
|
||||
*/
|
||||
Map<String, Object> getAdditionalInformation();
|
||||
public Ticket(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关联的访问令牌
|
||||
*
|
||||
* @return 访问令牌值
|
||||
* 复制构造函数
|
||||
*/
|
||||
String getAccessToken();
|
||||
public Ticket(Ticket ticket) {
|
||||
this(ticket.getValue());
|
||||
setAdditionalInformation(ticket.getAdditionalInformation());
|
||||
setAccessToken(ticket.getAccessToken());
|
||||
setExpiration(ticket.getExpiration());
|
||||
setScope(ticket.getScope());
|
||||
setTicketType(ticket.getTicketType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取票据类型
|
||||
*
|
||||
* @return 票据类型
|
||||
*/
|
||||
String getTicketType();
|
||||
public void setAdditionalInformation(Map<String, Object> additionalInformation) {
|
||||
this.additionalInformation = new LinkedHashMap<>(additionalInformation);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取过期时间
|
||||
*
|
||||
* @return 过期时间
|
||||
*/
|
||||
Date getExpiration();
|
||||
public Map<String, Object> getAdditionalInformation() {
|
||||
return additionalInformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取过期时间(秒)
|
||||
*
|
||||
* @return 过期秒数
|
||||
*/
|
||||
int getExpiresIn();
|
||||
public void setAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取票据值
|
||||
*
|
||||
* @return 票据值
|
||||
*/
|
||||
String getValue();
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限范围
|
||||
*
|
||||
* @return 权限范围Set
|
||||
*/
|
||||
Set<String> getScope();
|
||||
public void setTicketType(String ticketType) {
|
||||
this.ticketType = ticketType;
|
||||
}
|
||||
|
||||
public String getTicketType() {
|
||||
return ticketType;
|
||||
}
|
||||
|
||||
public void setExpiration(Date expiration) {
|
||||
this.expiration = expiration;
|
||||
}
|
||||
|
||||
public Date getExpiration() {
|
||||
return expiration;
|
||||
}
|
||||
|
||||
public void setExpiresIn(int delta) {
|
||||
setExpiration(new Date(System.currentTimeMillis() + delta * 1000L));
|
||||
}
|
||||
|
||||
public int getExpiresIn() {
|
||||
return expiration != null ? Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L)
|
||||
.intValue() : 0;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void setScope(Set<String> scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public Set<String> getScope() {
|
||||
return scope;
|
||||
}
|
||||
}
|
@@ -4,11 +4,9 @@ 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;
|
||||
@@ -22,8 +20,6 @@ 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;
|
||||
@@ -35,7 +31,7 @@ import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class MyUserDetailsService {
|
||||
public class AccountLoginService {
|
||||
|
||||
@Autowired
|
||||
private JedisService jedisService;
|
||||
@@ -59,8 +55,6 @@ public class MyUserDetailsService {
|
||||
private SysConfService sysConfService;
|
||||
@Autowired
|
||||
private RegionNetworkService regionService;
|
||||
@Autowired
|
||||
private PrettyNumberRecordMapper prettyNumberRecordMapper;
|
||||
|
||||
/**
|
||||
* 不允许登录的用户账号类型
|
@@ -5,7 +5,7 @@ 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.manager.TokenManager;
|
||||
import com.accompany.oauth.token.TokenManager;
|
||||
import com.accompany.oauth.model.TokenPair;
|
||||
import com.accompany.common.device.DeviceInfo;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -23,7 +23,7 @@ public class AuthenticationService {
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
@Autowired
|
||||
private MyUserDetailsService myUserDetailsService;
|
||||
private AccountLoginService accountLoginService;
|
||||
@Autowired
|
||||
private TokenManager tokenManager;
|
||||
|
||||
@@ -67,7 +67,7 @@ public class AuthenticationService {
|
||||
// 2. 检查用户状态
|
||||
userService.checkUserStatus(account);
|
||||
|
||||
myUserDetailsService.login(account, loginTypeEnum, deviceInfo, null);
|
||||
accountLoginService.login(account, loginTypeEnum, deviceInfo, null);
|
||||
|
||||
// 3. 生成Token
|
||||
TokenPair tokenPair = tokenManager.generateToken(account.getUid());
|
||||
|
@@ -10,15 +10,11 @@ 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 lombok.SneakyThrows;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* 用户服务 - 用户认证和信息查询
|
||||
*
|
||||
@@ -31,7 +27,7 @@ public class UserService {
|
||||
@Autowired
|
||||
private AccountManageService accountManageService;
|
||||
@Autowired
|
||||
private MyUserDetailsService myUserDetailsService;
|
||||
private AccountLoginService accountLoginService;
|
||||
|
||||
/**
|
||||
* 通过密码认证用户
|
||||
@@ -54,7 +50,7 @@ public class UserService {
|
||||
// BusiStatus.USER_NOT_EXISTED.getReasonPhrase());
|
||||
}
|
||||
|
||||
myUserDetailsService.validPwd(username, password, account.getPassword());
|
||||
accountLoginService.validPwd(username, password, account.getPassword());
|
||||
|
||||
return account;
|
||||
}
|
||||
@@ -67,7 +63,7 @@ public class UserService {
|
||||
|
||||
Account account = accountManageService.getOrGenAccountByPhone(phone, phoneAreaCode, code, deviceInfo);
|
||||
//校验验证码
|
||||
myUserDetailsService.checkCodeByUserType(account, code, LoginTypeEnum.PHONE);
|
||||
accountLoginService.checkCodeByUserType(account, code, LoginTypeEnum.PHONE);
|
||||
|
||||
return account;
|
||||
}
|
||||
@@ -85,7 +81,7 @@ public class UserService {
|
||||
Account account = accountManageService.getOrGenAccountByEmail(email, code, deviceInfo, deviceInfo.getClientIp());
|
||||
|
||||
//校验验证码
|
||||
myUserDetailsService.checkCodeByUserType(account, code, LoginTypeEnum.EMAIL);
|
||||
accountLoginService.checkCodeByUserType(account, code, LoginTypeEnum.EMAIL);
|
||||
|
||||
return account;
|
||||
}
|
||||
|
@@ -1,104 +0,0 @@
|
||||
package com.accompany.oauth.ticket;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 默认Ticket实现
|
||||
* 迁移自OAuth2模块的DefaultTicket
|
||||
*
|
||||
* @author Accompany OAuth Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class DefaultTicket implements Ticket, Serializable {
|
||||
private String value;
|
||||
|
||||
private Date expiration;
|
||||
|
||||
private String ticketType = ONCE_TYPE.toLowerCase();
|
||||
|
||||
private String accessToken;
|
||||
|
||||
private Set<String> scope;
|
||||
|
||||
private Map<String, Object> additionalInformation = Collections.emptyMap();
|
||||
|
||||
/**
|
||||
* 创建票据
|
||||
*/
|
||||
public DefaultTicket(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制构造函数
|
||||
*/
|
||||
public DefaultTicket(Ticket ticket) {
|
||||
this(ticket.getValue());
|
||||
setAdditionalInformation(ticket.getAdditionalInformation());
|
||||
setAccessToken(ticket.getAccessToken());
|
||||
setExpiration(ticket.getExpiration());
|
||||
setScope(ticket.getScope());
|
||||
setTicketType(ticket.getTicketType());
|
||||
}
|
||||
|
||||
public void setAdditionalInformation(Map<String, Object> additionalInformation) {
|
||||
this.additionalInformation = new LinkedHashMap<>(additionalInformation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAdditionalInformation() {
|
||||
return additionalInformation;
|
||||
}
|
||||
|
||||
public void setAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public void setTicketType(String ticketType) {
|
||||
this.ticketType = ticketType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTicketType() {
|
||||
return ticketType;
|
||||
}
|
||||
|
||||
public void setExpiration(Date expiration) {
|
||||
this.expiration = expiration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getExpiration() {
|
||||
return expiration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExpiresIn() {
|
||||
return expiration != null ? Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L)
|
||||
.intValue() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void setScope(Set<String> scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getScope() {
|
||||
return scope;
|
||||
}
|
||||
}
|
@@ -1,83 +0,0 @@
|
||||
package com.accompany.oauth.ticket;
|
||||
|
||||
import com.accompany.core.model.Account;
|
||||
import com.accompany.oauth.config.OAuthConfig;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.jsonwebtoken.JwtBuilder;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JWT票据增强器
|
||||
* 迁移自OAuth2模块的JwtTicketConverter
|
||||
*
|
||||
* @author Accompany OAuth Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Component
|
||||
public class TicketEnhancer {
|
||||
|
||||
@Autowired
|
||||
private OAuthConfig oAuthConfig;
|
||||
|
||||
public Ticket enhance(Ticket ticket, Account account) {
|
||||
DefaultTicket result = new DefaultTicket(ticket);
|
||||
result.setValue(encode(ticket, account));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码票据为JWT
|
||||
*
|
||||
* @param ticket 票据
|
||||
* @param account
|
||||
* @return JWT字符串
|
||||
*/
|
||||
protected String encode(Ticket ticket, Account account) {
|
||||
try {
|
||||
Map<String, Object> claims = convertTicket(ticket, account);
|
||||
|
||||
SecretKeySpec secretKey = new SecretKeySpec(oAuthConfig.getJwtSignKey().getBytes(StandardCharsets.UTF_8), SignatureAlgorithm.HS256.getJcaName());
|
||||
|
||||
Date now = new Date();
|
||||
Date expiration = ticket.getExpiration();
|
||||
|
||||
JwtBuilder builder = Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(expiration)
|
||||
.signWith(secretKey, SignatureAlgorithm.HS256);
|
||||
|
||||
return builder.compact();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Cannot convert ticket to JWT", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换票据为Claims
|
||||
*
|
||||
* @param ticket 票据
|
||||
* @param account
|
||||
* @return Claims Map
|
||||
*/
|
||||
protected Map<String, Object> convertTicket(Ticket ticket, Account account) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("ticket_id", ticket.getValue());
|
||||
response.put("client_id", oAuthConfig.getClientId());
|
||||
response.put("exp", ticket.getExpiresIn());
|
||||
response.put("uid", account.getUid());
|
||||
response.put("ticket_type", ticket.getTicketType());
|
||||
response.put("scope", "read write");
|
||||
return response;
|
||||
}
|
||||
}
|
@@ -10,10 +10,11 @@ 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.manager.TokenManager;
|
||||
import com.accompany.oauth.token.TokenManager;
|
||||
import com.accompany.oauth.model.TokenValidation;
|
||||
import com.accompany.oauth.service.MyUserDetailsService;
|
||||
import com.accompany.oauth.service.AccountLoginService;
|
||||
import com.accompany.oauth.service.UserService;
|
||||
import com.accompany.oauth.util.JwtUtil;
|
||||
import org.redisson.api.RMapCache;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.redisson.client.codec.StringCodec;
|
||||
@@ -39,14 +40,14 @@ public class TicketService implements InitializingBean {
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
|
||||
@Autowired
|
||||
private TicketEnhancer ticketEnhancer;
|
||||
private JwtUtil jwtUtil;
|
||||
|
||||
@Autowired
|
||||
private UserAppService userAppService;
|
||||
@Autowired
|
||||
private MyUserDetailsService myUserDetailsService;
|
||||
private AccountLoginService accountLoginService;
|
||||
@Autowired
|
||||
private LoginRecordService loginRecordService;
|
||||
@Autowired
|
||||
@@ -76,24 +77,25 @@ public class TicketService implements InitializingBean {
|
||||
Date expiration = validation.getExpirationTime();
|
||||
|
||||
// 4. 创建票据
|
||||
DefaultTicket defaultTicket = new DefaultTicket(UUID.randomUUID().toString());
|
||||
defaultTicket.setAccessToken(accessToken);
|
||||
defaultTicket.setExpiration(expiration);
|
||||
defaultTicket.setTicketType(Ticket.MULTI_TYPE);
|
||||
defaultTicket.setScope(validation.getScopes());
|
||||
|
||||
Ticket ticket = new Ticket(UUID.randomUUID().toString());
|
||||
ticket.setAccessToken(accessToken);
|
||||
ticket.setExpiration(expiration);
|
||||
ticket.setTicketType(Ticket.MULTI_TYPE);
|
||||
ticket.setScope(validation.getScopes());
|
||||
|
||||
// 5. 增强票据(JWT签名)
|
||||
Ticket enhancedTicket = ticketEnhancer.enhance(defaultTicket, account);
|
||||
|
||||
String ticketValue = jwtUtil.generateTicket(ticket, account.getUid());
|
||||
ticket.setValue(ticketValue);
|
||||
|
||||
// 6. 存储票据
|
||||
ticketCache.fastPut(account.getUid(), enhancedTicket.getValue(), defaultTicket.getExpiresIn(), TimeUnit.SECONDS);
|
||||
ticketCache.fastPut(account.getUid(), ticketValue, ticket.getExpiresIn(), TimeUnit.SECONDS);
|
||||
|
||||
// 7. 构建响应
|
||||
TicketResponseVO response = new TicketResponseVO();
|
||||
response.setUid(account.getUid());
|
||||
|
||||
List<TicketResponseVO.TicketVO> tickets = new ArrayList<>();
|
||||
tickets.add(createTicketVo(enhancedTicket));
|
||||
tickets.add(createTicketVo(ticket));
|
||||
response.setTickets(tickets);
|
||||
|
||||
return response;
|
||||
@@ -111,10 +113,10 @@ public class TicketService implements InitializingBean {
|
||||
ticketVo.setExpiresIn(ticket.getExpiresIn());
|
||||
return ticketVo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 保存登录记录(异步)
|
||||
*
|
||||
*
|
||||
* @param uid 用户ID
|
||||
* @param ipAddress IP地址
|
||||
* @param deviceInfo 设备信息
|
||||
@@ -127,7 +129,7 @@ public class TicketService implements InitializingBean {
|
||||
if (count <= 0L) {
|
||||
Account account = accountService.getAccountByUid(id);
|
||||
Optional.ofNullable(account).ifPresent(acc -> {
|
||||
AccountLoginRecord record = myUserDetailsService.buildAccountLoginRecord(ipAddress, acc, LoginTypeEnum.TICKET.getValue(), deviceInfo, null);
|
||||
AccountLoginRecord record = accountLoginService.buildAccountLoginRecord(ipAddress, acc, LoginTypeEnum.TICKET.getValue(), deviceInfo, null);
|
||||
loginRecordService.addAccountLoginRecord(record);
|
||||
});
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package com.accompany.oauth.manager;
|
||||
package com.accompany.oauth.token;
|
||||
|
||||
import com.accompany.common.redis.RedisKey;
|
||||
import com.accompany.core.util.StringUtils;
|
@@ -3,6 +3,7 @@ package com.accompany.oauth.util;
|
||||
import com.accompany.common.utils.UUIDUtil;
|
||||
import com.accompany.oauth.config.OAuthConfig;
|
||||
import com.accompany.oauth.exception.TokenException;
|
||||
import com.accompany.oauth.ticket.Ticket;
|
||||
import io.jsonwebtoken.*;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import lombok.Getter;
|
||||
@@ -13,6 +14,8 @@ import org.springframework.stereotype.Component;
|
||||
import javax.crypto.SecretKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JWT工具类 - 使用更安全的实现
|
||||
@@ -82,7 +85,28 @@ public class JwtUtil implements InitializingBean {
|
||||
.signWith(secretKey, SignatureAlgorithm.HS256)
|
||||
.compact();
|
||||
}
|
||||
|
||||
|
||||
public String generateTicket(Ticket ticket, Long uid){
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("ticket_id", ticket.getValue());
|
||||
claims.put("client_id", oAuthConfig.getClientId());
|
||||
claims.put("exp", ticket.getExpiresIn());
|
||||
claims.put("uid", uid);
|
||||
claims.put("ticket_type", ticket.getTicketType());
|
||||
claims.put("scope", "read write");
|
||||
|
||||
Date now = new Date();
|
||||
Date expiration = ticket.getExpiration();
|
||||
|
||||
JwtBuilder builder = Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(expiration)
|
||||
.signWith(secretKey, SignatureAlgorithm.HS256);
|
||||
|
||||
return builder.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证并解析JWT令牌
|
||||
*
|
||||
@@ -103,7 +127,7 @@ public class JwtUtil implements InitializingBean {
|
||||
throw TokenException.invalidToken();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成JWT Token ID (兼容OAuth2)
|
||||
*
|
||||
|
@@ -1,28 +0,0 @@
|
||||
spring:
|
||||
# Redis配置 - 开发环境
|
||||
redis:
|
||||
redisson:
|
||||
config: |
|
||||
singleServerConfig:
|
||||
address: "redis://127.0.0.1:6379"
|
||||
password: null
|
||||
database: 1
|
||||
connectionPoolSize: 5
|
||||
connectionMinimumIdleSize: 1
|
||||
connectTimeout: 3000
|
||||
timeout: 3000
|
||||
|
||||
# OAuth配置 - 开发环境
|
||||
oauth:
|
||||
jwt:
|
||||
secret: accompany-oauth-dev-secret-key-2024
|
||||
access-token-expiration: 3600 # 开发环境1小时
|
||||
refresh-token-expiration: 604800 # 开发环境7天
|
||||
|
||||
# 日志配置 - 开发环境
|
||||
logging:
|
||||
level:
|
||||
com.accompany.oauth: DEBUG
|
||||
org.springframework.web: DEBUG
|
||||
org.redisson: DEBUG
|
||||
io.jsonwebtoken: DEBUG
|
@@ -0,0 +1,74 @@
|
||||
#MySQL数据库配置
|
||||
spring:
|
||||
dynamic-datasource:
|
||||
master:
|
||||
poolName: master
|
||||
jdbcUrl: jdbc:mysql://124.156.164.187:3306/peko?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&tinyInt1isBit=false&useSSL=false&useCursorFetch=true
|
||||
username: root
|
||||
password: anan@dev##
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
hikari:
|
||||
minimum-idle: 10
|
||||
maximum-pool-size: 20
|
||||
connection-test-query: select 1
|
||||
max-lifetime: 7000
|
||||
slave:
|
||||
poolName: slave
|
||||
jdbcUrl: jdbc:mysql://124.156.164.187:3306/peko?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&tinyInt1isBit=false&useSSL=false&useCursorFetch=true
|
||||
username: root
|
||||
password: anan@dev##
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
hikari:
|
||||
minimum-idle: 10
|
||||
maximum-pool-size: 20
|
||||
connection-test-query: select 1
|
||||
max-lifetime: 7000
|
||||
redis:
|
||||
host: 124.156.164.187
|
||||
port: 6200
|
||||
maxTotal: 100
|
||||
maxIdle: 50
|
||||
maxWait: 2500
|
||||
testOnBorrow: true
|
||||
testOnReturn: true
|
||||
password: anan@dev@redis@#!
|
||||
redisson:
|
||||
# file: classpath:redisson.yaml
|
||||
config: |
|
||||
singleServerConfig:
|
||||
address: redis://124.156.164.187:6200
|
||||
password: anan@dev@redis@#!
|
||||
connectionMinimumIdleSize: 4
|
||||
timeout: 10000
|
||||
threads: 8
|
||||
nettyThreads: 16
|
||||
codec: !<org.redisson.codec.JsonJacksonCodec> {}
|
||||
transportMode: "NIO"
|
||||
|
||||
## rocketmq 配置
|
||||
rocketmq:
|
||||
name-server: 124.156.164.187:9876
|
||||
producer:
|
||||
group: peko-group
|
||||
sendMessageTimeout: 300000
|
||||
|
||||
server:
|
||||
port: 8081
|
||||
|
||||
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
|
@@ -1,30 +0,0 @@
|
||||
spring:
|
||||
# Redis配置 - 生产环境
|
||||
redis:
|
||||
redisson:
|
||||
config: |
|
||||
singleServerConfig:
|
||||
address: "redis://${REDIS_HOST:127.0.0.1}:${REDIS_PORT:6379}"
|
||||
password: ${REDIS_PASSWORD:null}
|
||||
database: ${REDIS_DATABASE:0}
|
||||
connectionPoolSize: 20
|
||||
connectionMinimumIdleSize: 5
|
||||
connectTimeout: 5000
|
||||
timeout: 5000
|
||||
retryAttempts: 3
|
||||
retryInterval: 2000
|
||||
|
||||
# OAuth配置 - 生产环境
|
||||
oauth:
|
||||
jwt:
|
||||
secret: ${JWT_SECRET:accompany-oauth-prod-secret-key-2024-very-secure}
|
||||
access-token-expiration: ${ACCESS_TOKEN_EXPIRATION:7200}
|
||||
refresh-token-expiration: ${REFRESH_TOKEN_EXPIRATION:2592000}
|
||||
|
||||
# 日志配置 - 生产环境
|
||||
logging:
|
||||
level:
|
||||
com.accompany.oauth: INFO
|
||||
org.springframework.web: WARN
|
||||
org.redisson: WARN
|
||||
root: WARN
|
@@ -1,75 +0,0 @@
|
||||
server:
|
||||
port: 8081
|
||||
servlet:
|
||||
context-path: /
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: accompany-oauth
|
||||
profiles:
|
||||
active: dev
|
||||
|
||||
# Redis配置 (Redisson)
|
||||
redis:
|
||||
redisson:
|
||||
config: |
|
||||
singleServerConfig:
|
||||
address: "redis://127.0.0.1:6379"
|
||||
password: null
|
||||
database: 0
|
||||
connectionPoolSize: 10
|
||||
connectionMinimumIdleSize: 2
|
||||
connectTimeout: 3000
|
||||
timeout: 3000
|
||||
retryAttempts: 3
|
||||
retryInterval: 1500
|
||||
|
||||
# OAuth配置
|
||||
oauth:
|
||||
jwt:
|
||||
secret: accompany-oauth-secret-key-for-jwt-token-generation-2024
|
||||
access-token-expiration: 7200 # 访问令牌有效期(秒) - 2小时
|
||||
refresh-token-expiration: 2592000 # 刷新令牌有效期(秒) - 30天
|
||||
|
||||
client:
|
||||
default:
|
||||
client-id: default
|
||||
client-secret: default-secret
|
||||
grant-types: password,verify_code,email,openid,refresh_token
|
||||
scopes: read,write
|
||||
access-token-validity: 7200
|
||||
refresh-token-validity: 2592000
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
com.accompany.oauth: DEBUG
|
||||
org.springframework.web: INFO
|
||||
org.redisson: INFO
|
||||
pattern:
|
||||
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
|
||||
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
|
||||
file:
|
||||
name: logs/accompany-oauth.log
|
||||
max-size: 100MB
|
||||
max-history: 30
|
||||
|
||||
# 管理端点配置
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics,prometheus
|
||||
endpoint:
|
||||
health:
|
||||
show-details: when_authorized
|
||||
server:
|
||||
port: 8082
|
||||
|
||||
# Swagger文档配置
|
||||
springdoc:
|
||||
api-docs:
|
||||
path: /v3/api-docs
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
operationsSorter: method
|
@@ -0,0 +1,83 @@
|
||||
spring:
|
||||
application:
|
||||
name: oauth
|
||||
profiles:
|
||||
active: native
|
||||
main:
|
||||
allow-bean-definition-overriding : true
|
||||
allow-circular-references: true
|
||||
mvc:
|
||||
pathmatch:
|
||||
matching-strategy: ant_path_matcher
|
||||
|
||||
---
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: native
|
||||
cloud:
|
||||
nacos:
|
||||
config:
|
||||
server-addr: 124.156.164.187:8848
|
||||
namespace: 5edc3246-3e69-4be5-a32a-273f0a09ddf6
|
||||
file-extension: yml
|
||||
shared-configs:
|
||||
- data-id: application.yml
|
||||
refresh: true
|
||||
- data-id: thirdpart.yml
|
||||
refresh: true
|
||||
- data-id: pay.yml
|
||||
refresh: true
|
||||
- data-id: sysconf.yml
|
||||
refresh: true
|
||||
- data-id: dtp.yml
|
||||
refresh: true
|
||||
|
||||
---
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: dev
|
||||
cloud:
|
||||
nacos:
|
||||
config:
|
||||
server-addr: 124.156.164.187:8848
|
||||
namespace: 5edc3246-3e69-4be5-a32a-273f0a09ddf6
|
||||
file-extension: yml
|
||||
shared-configs:
|
||||
- data-id: application.yml
|
||||
refresh: true
|
||||
- data-id: thirdpart.yml
|
||||
refresh: true
|
||||
- data-id: pay.yml
|
||||
refresh: true
|
||||
- data-id: sysconf.yml
|
||||
refresh: true
|
||||
- data-id: dtp.yml
|
||||
refresh: true
|
||||
- data-id: database.yml
|
||||
refresh: true
|
||||
---
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: prod
|
||||
cloud:
|
||||
nacos:
|
||||
config:
|
||||
server-addr: 172.19.16.15:8848
|
||||
namespace: 0c9bf047-19ca-4969-bf5d-bbc1f86b501a
|
||||
file-extension: yml
|
||||
shared-configs:
|
||||
- data-id: application.yml
|
||||
refresh: true
|
||||
- data-id: thirdpart.yml
|
||||
refresh: true
|
||||
- data-id: pay.yml
|
||||
refresh: true
|
||||
- data-id: sysconf.yml
|
||||
refresh: true
|
||||
- data-id: threadpool.yml
|
||||
refresh: true
|
||||
- data-id: database.yml
|
||||
refresh: true
|
Reference in New Issue
Block a user