diff --git a/owner-apigateway-service/build.gradle b/owner-apigateway-service/build.gradle index 5094502..6986aa8 100644 --- a/owner-apigateway-service/build.gradle +++ b/owner-apigateway-service/build.gradle @@ -30,6 +30,8 @@ dependencies { implementation 'io.jsonwebtoken:jjwt:0.9.1' // https://mvnrepository.com/artifact/io.netty/netty-resolver-dns-native-macos implementation 'io.netty:netty-resolver-dns-native-macos:4.1.68.Final:osx-aarch_64' + // https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api + implementation 'javax.xml.bind:jaxb-api:2.3.1' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' diff --git a/owner-apigateway-service/src/main/java/com/justpickup/ownerapigatewayservice/filter/AuthorizationHeaderFilter.java b/owner-apigateway-service/src/main/java/com/justpickup/ownerapigatewayservice/filter/AuthorizationHeaderFilter.java new file mode 100644 index 0000000..3eb834f --- /dev/null +++ b/owner-apigateway-service/src/main/java/com/justpickup/ownerapigatewayservice/filter/AuthorizationHeaderFilter.java @@ -0,0 +1,74 @@ +package com.justpickup.ownerapigatewayservice.filter; + +import com.justpickup.ownerapigatewayservice.security.JwtTokenProvider; +import io.jsonwebtoken.Jwts; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.http.HttpHeaders; +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; + +@Component +@Slf4j +public class AuthorizationHeaderFilter extends AbstractGatewayFilterFactory { + + private final JwtTokenProvider jwtTokenProvider; + + @Autowired + public AuthorizationHeaderFilter(JwtTokenProvider jwtTokenProvider) { + super(Config.class); + this.jwtTokenProvider = jwtTokenProvider; + } + + static class Config { + + } + + @Override + public GatewayFilter apply(Config config) { + return (exchange, chain) -> { + ServerHttpRequest request = exchange.getRequest(); + + HttpHeaders headers = request.getHeaders(); + if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) { + return onError(exchange, "No authorization header", HttpStatus.UNAUTHORIZED); + } + + String authorizationHeader = headers.get(HttpHeaders.AUTHORIZATION).get(0); + + // JWT 토큰 판별 + String token = authorizationHeader.replace("Bearer", ""); + + if (jwtTokenProvider.isExpired(token)) { + return onError(exchange, "Access Token is Expired", HttpStatus.UNAUTHORIZED); + } + + String subject = jwtTokenProvider.getUserId(token); + if (subject == null) { + return onError(exchange, "JWT token is not valid", HttpStatus.UNAUTHORIZED); + } + + ServerHttpRequest request1 = request.mutate() + .header("jwt-sub", subject) + .build(); + + return chain.filter(exchange.mutate().request(request1).build()); + }; + } + + // Mono(단일 값), Flux(다중 값) -> Spring WebFlux + private Mono onError(ServerWebExchange exchange, String errorMsg, HttpStatus httpStatus) { + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(httpStatus); + + log.error(errorMsg); + return response.setComplete(); + } +} diff --git a/owner-apigateway-service/src/main/java/com/justpickup/ownerapigatewayservice/security/JwtTokenProvider.java b/owner-apigateway-service/src/main/java/com/justpickup/ownerapigatewayservice/security/JwtTokenProvider.java new file mode 100644 index 0000000..d4fed6b --- /dev/null +++ b/owner-apigateway-service/src/main/java/com/justpickup/ownerapigatewayservice/security/JwtTokenProvider.java @@ -0,0 +1,42 @@ +package com.justpickup.ownerapigatewayservice.security; + +import io.jsonwebtoken.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Date; + +@Component +@RequiredArgsConstructor +@Slf4j +public class JwtTokenProvider { + + @Value("${token.access-expired-time}") + private long ACCESS_EXPIRED_TIME; + + @Value("${token.refresh-expired-time}") + private long REFRESH_EXPIRED_TIME; + + @Value("${token.secret}") + private String SECRET; + + public String getUserId(String token) { + return getClaimsFromJwtToken(token).getBody().getSubject(); + } + + public boolean isExpired(String token) { + try { + return getClaimsFromJwtToken(token).getBody().getExpiration().before(new Date()); + } catch (ExpiredJwtException e) { + return true; + } catch (Exception e) { + return true; + } + } + + public Jws getClaimsFromJwtToken(String token) { + return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token); + } +} diff --git a/owner-apigateway-service/src/main/resources/application.yml b/owner-apigateway-service/src/main/resources/application.yml index 68321c9..7395f0e 100644 --- a/owner-apigateway-service/src/main/resources/application.yml +++ b/owner-apigateway-service/src/main/resources/application.yml @@ -33,9 +33,36 @@ spring: - Path=/store-service/** filters: - RewritePath=/store-service/(?.*),/$\{segment} + - id: user-service + uri: lb://USER-SERVICE + predicates: + - Path=/user-service/login + - Method=POST + filters: + - RewritePath=/user-service/(?.*),/$\{segment} + - id: user-service + uri: lb://USER-SERVICE + predicates: + - Path=/user-service/refreshToken + - Method=GET + filters: + - RewritePath=/user-service/(?.*),/$\{segment} + - id: user-service + uri: lb://USER-SERVICE + predicates: + - Path=/user-service/logout + - Method=POST + filters: + - RewritePath=/user-service/(?.*),/$\{segment} - id: user-service uri: lb://USER-SERVICE predicates: - Path=/user-service/** filters: - - RewritePath=/user-service/(?.*),/$\{segment} \ No newline at end of file + - AuthorizationHeaderFilter + - RewritePath=/user-service/(?.*),/$\{segment} + +token: + access-expired-time: 3600000 + refresh-expired-time: 604800000 + secret: my-secret \ No newline at end of file