新增user服务和gateway服务
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
// TimelineGatewayApplication.java
|
||||
package com.timeline.gateway;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableDiscoveryClient
|
||||
public class TimelineGatewayApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(TimelineGatewayApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// GatewayConfig.java
|
||||
package com.timeline.gateway.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.reactive.CorsWebFilter;
|
||||
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
|
||||
|
||||
@Configuration
|
||||
public class GatewayConfig {
|
||||
|
||||
@Bean
|
||||
public CorsWebFilter corsWebFilter() {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.addAllowedOrigin("*");
|
||||
config.addAllowedMethod("*");
|
||||
config.addAllowedHeader("*");
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
|
||||
return new CorsWebFilter(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.timeline.gateway.filter;
|
||||
import com.timeline.common.response.ResponseEntity;
|
||||
import com.timeline.common.response.ResponseEnum;
|
||||
import com.timeline.gateway.util.JwtUtils;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class AuthenticationFilter implements GlobalFilter, Ordered {
|
||||
|
||||
@Autowired
|
||||
private JwtUtils jwtUtils;
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
String path = request.getURI().getPath();
|
||||
|
||||
// 白名单路径不需要认证
|
||||
if (isWhitelisted(path)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
// 从请求头中获取token
|
||||
String token = extractToken(request);
|
||||
|
||||
if (token == null || token.isEmpty()) {
|
||||
return handleUnauthorized(exchange, ResponseEnum.UNAUTHORIZED.getMessage());
|
||||
}
|
||||
|
||||
// 验证token
|
||||
if (!jwtUtils.validateToken(token)) {
|
||||
return handleUnauthorized(exchange, "无效的Token");
|
||||
}
|
||||
|
||||
Claims claims = jwtUtils.getClaimsFromToken(token);
|
||||
if (claims == null || jwtUtils.isTokenExpired(claims)) {
|
||||
return handleUnauthorized(exchange, "Token已过期");
|
||||
}
|
||||
|
||||
// 将用户信息添加到请求头中传递给下游服务
|
||||
String userId = jwtUtils.getUserIdFromClaims(claims);
|
||||
String username = jwtUtils.getUsernameFromClaims(claims);
|
||||
|
||||
ServerHttpRequest mutatedRequest = request.mutate()
|
||||
.header("X-User-Id", userId)
|
||||
.header("X-Username", username)
|
||||
.build();
|
||||
|
||||
ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
|
||||
|
||||
return chain.filter(mutatedExchange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return -100; // 设置过滤器优先级,确保在其他过滤器之前执行
|
||||
}
|
||||
|
||||
private boolean isWhitelisted(String path) {
|
||||
// 白名单路径不需要认证
|
||||
return path.startsWith("/auth/login") ||
|
||||
path.startsWith("/auth/register") ||
|
||||
path.startsWith("/ping") ||
|
||||
path.startsWith("/actuator");
|
||||
}
|
||||
|
||||
private String extractToken(ServerHttpRequest request) {
|
||||
String bearerToken = request.getHeaders().getFirst("Authorization");
|
||||
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
|
||||
return bearerToken.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Mono<Void> handleUnauthorized(ServerWebExchange exchange, String message) {
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
response.setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
|
||||
|
||||
ResponseEntity<String> responseEntity = ResponseEntity.error(ResponseEnum.UNAUTHORIZED.getCode(), message);
|
||||
String responseBody = com.alibaba.fastjson.JSON.toJSONString(responseEntity);
|
||||
|
||||
byte[] bytes = responseBody.getBytes(StandardCharsets.UTF_8);
|
||||
DataBuffer buffer = response.bufferFactory().wrap(bytes);
|
||||
|
||||
return response.writeWith(Mono.just(buffer));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// JwtUtils.java
|
||||
package com.timeline.gateway.util;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class JwtUtils {
|
||||
|
||||
@Value("${jwt.secret:timelineSecretKey}")
|
||||
private String secret;
|
||||
|
||||
public Claims getClaimsFromToken(String token) {
|
||||
try {
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(secret)
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
Jwts.parserBuilder().setSigningKey(secret).build().parseClaimsJws(token);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isTokenExpired(Claims claims) {
|
||||
if (claims == null) {
|
||||
return true;
|
||||
}
|
||||
return claims.getExpiration().before(new java.util.Date());
|
||||
}
|
||||
|
||||
public String getUserIdFromClaims(Claims claims) {
|
||||
if (claims == null) {
|
||||
return null;
|
||||
}
|
||||
return claims.get("userId", String.class);
|
||||
}
|
||||
|
||||
public String getUsernameFromClaims(Claims claims) {
|
||||
if (claims == null) {
|
||||
return null;
|
||||
}
|
||||
return claims.getSubject();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# application.properties
|
||||
spring.application.name=timeline-gateway
|
||||
|
||||
server.port=30000
|
||||
|
||||
# ????
|
||||
spring.cloud.gateway.routes[0].id=story-service
|
||||
spring.cloud.gateway.routes[0].uri=lb://timeline.story
|
||||
spring.cloud.gateway.routes[0].predicates[0]=Path=/story/**
|
||||
spring.cloud.gateway.routes[0].filters[0]=StripPrefix=1
|
||||
|
||||
spring.cloud.gateway.routes[1].id=file-service
|
||||
spring.cloud.gateway.routes[1].uri=lb://timeline.file
|
||||
spring.cloud.gateway.routes[1].predicates[0]=Path=/file/**
|
||||
spring.cloud.gateway.routes[1].filters[0]=StripPrefix=1
|
||||
|
||||
# JWT??
|
||||
jwt.secret=timelineSecretKey
|
||||
jwt.expiration=86400
|
||||
|
||||
# Actuator??
|
||||
management.endpoints.web.exposure.include=*
|
||||
management.endpoint.health.show-details=always
|
||||
|
||||
# ????
|
||||
logging.level.org.springframework.cloud.gateway=DEBUG
|
||||
logging.level.com.timeline.gateway=DEBUG
|
||||
Reference in New Issue
Block a user