Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@

根据自己的学习和使用需求,选择合适的版本启动即可。微服务版本侧重学习设计,聚合服务版本侧重测试和部署。请根据场景需要,选择正确的版本进行学习和使用。

## JWT 密钥配置

JWT 签名密钥必须由部署环境提供,不能提交到代码仓库。启动聚合服务、网关或微服务前,请设置 `INDEX12306_JWT_SECRET` 环境变量:

```bash
export INDEX12306_JWT_SECRET="$(openssl rand -base64 64 | tr -d '\n')"
```

同一个部署中的用户服务和网关必须使用同一个密钥;不同环境应使用不同密钥。应用通过 `index12306.jwt.secret` 读取该变量,未配置密钥时会在启动阶段失败。密钥发生泄露后,应先生成新密钥并重启相关服务,使旧 Token 失效,再继续使用系统。

![](https://oss.open8gu.com/12306-base-biz-20230801.png)

## 技术架构
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
package org.opengoofy.index12306.frameworks.starter.user.config;

import org.opengoofy.index12306.frameworks.starter.user.core.UserTransmitFilter;
import org.opengoofy.index12306.frameworks.starter.user.toolkit.JWTUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
Expand All @@ -31,6 +33,14 @@
@ConditionalOnWebApplication
public class UserAutoConfiguration {

/**
* JWT utility configured with a deployment-specific signing secret.
*/
@Bean
public JWTUtil jwtUtil(@Value("${index12306.jwt.secret}") String secret) {
return new JWTUtil(secret);
}

/**
* 用户信息传递过滤器
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,28 @@ public final class JWTUtil {
private static final long EXPIRATION = 86400L;
public static final String TOKEN_PREFIX = "Bearer ";
public static final String ISS = "index12306";
public static final String SECRET = "SecretKey039245678901232039487623456783092349288901402967890140939827";
private final String secret;

public JWTUtil(String secret) {
if (!StringUtils.hasText(secret)) {
throw new IllegalArgumentException("index12306.jwt.secret must be configured");
}
this.secret = secret;
}

/**
* 生成用户 Token
*
* @param userInfo 用户信息
* @return 用户访问 Token
*/
public static String generateAccessToken(UserInfoDTO userInfo) {
public String generateAccessToken(UserInfoDTO userInfo) {
Map<String, Object> customerUserMap = new HashMap<>();
customerUserMap.put(USER_ID_KEY, userInfo.getUserId());
customerUserMap.put(USER_NAME_KEY, userInfo.getUsername());
customerUserMap.put(REAL_NAME_KEY, userInfo.getRealName());
String jwtToken = Jwts.builder()
.signWith(SignatureAlgorithm.HS512, SECRET)
.signWith(SignatureAlgorithm.HS512, secret)
.setIssuedAt(new Date())
.setIssuer(ISS)
.setSubject(JSON.toJSONString(customerUserMap))
Expand All @@ -73,11 +80,11 @@ public static String generateAccessToken(UserInfoDTO userInfo) {
* @param jwtToken 用户访问 Token
* @return 用户信息
*/
public static UserInfoDTO parseJwtToken(String jwtToken) {
public UserInfoDTO parseJwtToken(String jwtToken) {
if (StringUtils.hasText(jwtToken)) {
String actualJwtToken = jwtToken.replace(TOKEN_PREFIX, "");
try {
Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(actualJwtToken).getBody();
Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(actualJwtToken).getBody();
Date expiration = claims.getExpiration();
if (expiration.after(new Date())) {
String subject = claims.getSubject();
Expand All @@ -90,4 +97,4 @@ public static UserInfoDTO parseJwtToken(String jwtToken) {
}
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ spring:
main:
allow-bean-definition-overriding: true

index12306:
jwt:
secret: ${INDEX12306_JWT_SECRET}

rocketmq:
name-server: 127.0.0.1:9876
producer:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,11 @@
@Component
public class TokenValidateGatewayFilterFactory extends AbstractGatewayFilterFactory<Config> {

public TokenValidateGatewayFilterFactory() {
private final JWTUtil jwtUtil;

public TokenValidateGatewayFilterFactory(JWTUtil jwtUtil) {
super(Config.class);
this.jwtUtil = jwtUtil;
}

/**
Expand All @@ -58,7 +61,7 @@ public GatewayFilter apply(Config config) {
if (isPathInBlackPreList(requestPath, config.getBlackPathPre())) {
String token = request.getHeaders().getFirst("Authorization");
// TODO 需要验证 Token 是否有效,有可能用户注销了账户,但是 Token 有效期还未过
UserInfoDTO userInfo = JWTUtil.parseJwtToken(token);
UserInfoDTO userInfo = jwtUtil.parseJwtToken(token);
if (!validateToken(userInfo)) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.Date;
Expand All @@ -38,26 +40,34 @@
* 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
*/
@Slf4j
@Component
public final class JWTUtil {

private static final long EXPIRATION = 86400L;
public static final String TOKEN_PREFIX = "Bearer ";
public static final String ISS = "index12306";
public static final String SECRET = "SecretKey039245678901232039487623456783092349288901402967890140939827";
private final String secret;

public JWTUtil(@Value("${index12306.jwt.secret}") String secret) {
if (!StringUtils.hasText(secret)) {
throw new IllegalArgumentException("index12306.jwt.secret must be configured");
}
this.secret = secret;
}

/**
* 生成用户 Token
*
* @param userInfo 用户信息
* @return 用户访问 Token
*/
public static String generateAccessToken(UserInfoDTO userInfo) {
public String generateAccessToken(UserInfoDTO userInfo) {
Map<String, Object> customerUserMap = new HashMap<>();
customerUserMap.put(USER_ID_KEY, userInfo.getUserId());
customerUserMap.put(USER_NAME_KEY, userInfo.getUsername());
customerUserMap.put(REAL_NAME_KEY, userInfo.getRealName());
String jwtToken = Jwts.builder()
.signWith(SignatureAlgorithm.HS512, SECRET)
.signWith(SignatureAlgorithm.HS512, secret)
.setIssuedAt(new Date())
.setIssuer(ISS)
.setSubject(JSON.toJSONString(customerUserMap))
Expand All @@ -72,11 +82,11 @@ public static String generateAccessToken(UserInfoDTO userInfo) {
* @param jwtToken 用户访问 Token
* @return 用户信息
*/
public static UserInfoDTO parseJwtToken(String jwtToken) {
public UserInfoDTO parseJwtToken(String jwtToken) {
if (StringUtils.hasText(jwtToken)) {
String actualJwtToken = jwtToken.replace(TOKEN_PREFIX, "");
try {
Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(actualJwtToken).getBody();
Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(actualJwtToken).getBody();
Date expiration = claims.getExpiration();
if (expiration.after(new Date())) {
String subject = claims.getSubject();
Expand All @@ -89,4 +99,4 @@ public static UserInfoDTO parseJwtToken(String jwtToken) {
}
return null;
}
}
}
4 changes: 4 additions & 0 deletions services/gateway-service/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ spring:
discovery:
server-addr: 127.0.0.1:8848

index12306:
jwt:
secret: ${INDEX12306_JWT_SECRET}

management:
endpoints:
web:
Expand Down
4 changes: 4 additions & 0 deletions services/order-service/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ spring:
port: 6379
password: 123456

index12306:
jwt:
secret: ${INDEX12306_JWT_SECRET}

mybatis-plus:
global-config:
db-config:
Expand Down
4 changes: 4 additions & 0 deletions services/ticket-service/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ spring:
keep-alive-time: 9999
rejected-handler: CallerRunsPolicy

index12306:
jwt:
secret: ${INDEX12306_JWT_SECRET}

rocketmq:
name-server: 127.0.0.1:9876
producer:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public class UserLoginServiceImpl implements UserLoginService {
private final DistributedCache distributedCache;
private final AbstractChainContext<UserRegisterReqDTO> abstractChainContext;
private final RBloomFilter<String> userRegisterCachePenetrationBloomFilter;
private final JWTUtil jwtUtil;

@Override
public UserLoginRespDTO login(UserLoginReqDTO requestParam) {
Expand Down Expand Up @@ -128,7 +129,7 @@ public UserLoginRespDTO login(UserLoginReqDTO requestParam) {
.username(userDO.getUsername())
.realName(userDO.getRealName())
.build();
String accessToken = JWTUtil.generateAccessToken(userInfo);
String accessToken = jwtUtil.generateAccessToken(userInfo);
UserLoginRespDTO userLogin = UserLoginRespDTO.builder()
.userId(userInfo.getUserId())
.username(userInfo.getUsername())
Expand Down
5 changes: 5 additions & 0 deletions services/user-service/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ spring:
transport:
dashboard: localhost:8686
port: 8719

index12306:
jwt:
secret: ${INDEX12306_JWT_SECRET}

mybatis-plus:
global-config:
db-config:
Expand Down