diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index c702abb..5cbb19a 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -11,7 +11,7 @@ on: push: branches: [ "main" ] pull_request: - branches: [ "main" ] + branches: [ "dev" ] permissions: contents: read diff --git a/build.gradle b/build.gradle index 0773134..e2cf03d 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,14 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.projectreactor:reactor-test' + + implementation 'org.springframework.boot:spring-boot-starter-security' + + implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2' + runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2' + runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2' + + implementation 'org.springframework.boot:spring-boot-starter-web' } dependencyManagement { diff --git a/src/main/java/com/example/farmusgateway/config/FilterConfig.java b/src/main/java/com/example/farmusgateway/config/FilterConfig.java new file mode 100644 index 0000000..94cdcbe --- /dev/null +++ b/src/main/java/com/example/farmusgateway/config/FilterConfig.java @@ -0,0 +1,30 @@ +package com.example.farmusgateway.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class FilterConfig { + + @Value("${baseurl.user}") + private String userUrl; + + @Bean + public RouteLocator gatewayRoutes(RouteLocatorBuilder builder) { + // application.yml 에 적용한 라우팅 대신에 필터를 적용해서 처리 + return builder.routes() + .route(r -> r.path("/api/user/auth/logout") + .filters(f -> f.addRequestHeader("user", "first-request-header")) + .uri(userUrl)) + + .route(r -> r.path("/api/user/auth/reissue-token") + .filters(f -> f.addRequestHeader("user", "second-request-header") + .addRequestHeader("Authorization","sd") + ) + .uri(userUrl)) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/farmusgateway/filter/AuthorizationHeaderFilter.java b/src/main/java/com/example/farmusgateway/filter/AuthorizationHeaderFilter.java new file mode 100644 index 0000000..5229b3f --- /dev/null +++ b/src/main/java/com/example/farmusgateway/filter/AuthorizationHeaderFilter.java @@ -0,0 +1,84 @@ +package com.example.farmusgateway.filter; + + +import io.jsonwebtoken.Jwts; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.core.env.Environment; +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 { + + @Value("${jwt.secret}") + private String secret; + + public AuthorizationHeaderFilter(Environment env) { + super(Config.class); + + } + + // login -> token -> users (with token) -> header(include token) + @Override + public GatewayFilter apply(Config config) { + return (exchange, chain) -> { + ServerHttpRequest request = exchange.getRequest(); + if(!request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) { + return onError(exchange, "no authorization header", HttpStatus.UNAUTHORIZED); + } + + String authorizationHeader = request.getHeaders().get(HttpHeaders.AUTHORIZATION).get(0); + String jwt = authorizationHeader.replace("Bearer", ""); + if(!isJwtValid(jwt)) { + return onError(exchange, "JWT token is not valid", HttpStatus.UNAUTHORIZED); + } + + // Custom Post Filter + return chain.filter(exchange); + }; + } + + private boolean isJwtValid(String jwt) { + boolean returnValue = true; + + String subject = null; + try { + subject = Jwts.parser().setSigningKey(secret) + .parseClaimsJws(jwt).getBody() + .getSubject(); + } catch(Exception ex) { + returnValue = false; + } + + if(subject == null || subject.isEmpty()) { + returnValue = false; + } + + return returnValue; + } + + // Mono, Flux -> Spring WebFlux + private Mono onError(ServerWebExchange exchange, String error, HttpStatus httpStatus) { + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(httpStatus); + + log.error(error); + + return response.setComplete(); + } + + @Data + public static class Config { + + } +}