feat(customer vue, user-service): customer vue OAuth 인증

- customer vue 인증페이지 구현
- customer OAuth 로그인 구현
- user-service success handling 구현
This commit is contained in:
hoon7566
2022-03-03 20:29:19 +09:00
parent 2252a53e26
commit d40258a95b
23 changed files with 538 additions and 31 deletions

View File

@@ -31,6 +31,8 @@ dependencies {
// 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'
implementation 'javax.xml.bind:jaxb-api:2.3.1'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'

View File

@@ -1,18 +1,25 @@
package com.justpickup.customerapigatewayservice.filter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.justpickup.customerapigatewayservice.security.JwtTokenProvider;
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.core.io.buffer.DataBuffer;
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.Flux;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
@Component
@Slf4j
public class AuthorizationHeaderFilter extends AbstractGatewayFilterFactory<AuthorizationHeaderFilter.Config> {
@@ -44,9 +51,7 @@ public class AuthorizationHeaderFilter extends AbstractGatewayFilterFactory<Auth
// JWT 토큰 판별
String token = authorizationHeader.replace("Bearer", "");
if (!jwtTokenProvider.validateJwtToken(token)) {
return onError(exchange, "JWT token is not valid", HttpStatus.UNAUTHORIZED);
}
jwtTokenProvider.validateJwtToken(token);
String subject = jwtTokenProvider.getUserId(token);
if (false == jwtTokenProvider.getRoles(token).contains("Customer")) {

View File

@@ -0,0 +1,53 @@
package com.justpickup.customerapigatewayservice.filter;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
@Component
@Slf4j
public class GlobalFilter extends AbstractGatewayFilterFactory<GlobalFilter.Config> {
public GlobalFilter(){
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest(); // reactive포함된거로 import
ServerHttpResponse response = exchange.getResponse();
log.info("Global com.example.scg.filter baseMessgae: {}", config.getBaseMessage());
// Global pre Filter
if (config.isPreLogger()){
log.info("Global Filter Start: request id -> {}" , request.getId());
log.info("Global Filter Start: request path -> {}" , request.getPath());
}
// Global Post Filter
//Mono는 webflux에서 단일값 전송할때 Mono값으로 전송
return chain.filter(exchange).then(Mono.fromRunnable(()->{
if (config.isPostLogger()){
log.info("Global Filter End: response statuscode -> {}" , response.getStatusCode());
}
}));
};
}
@Data
public static class Config {
private String baseMessage;
private boolean preLogger;
private boolean postLogger;
}
}

View File

@@ -0,0 +1,64 @@
package com.justpickup.customerapigatewayservice.handler;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GlobalExceptionHandler implements ErrorWebExceptionHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
List<Class<? extends RuntimeException>> jwtExceptions =
List.of(SignatureException.class,
MalformedJwtException.class,
UnsupportedJwtException.class,
IllegalArgumentException.class);
Class<? extends Throwable> exceptionClass = ex.getClass();
Map<String, Object> responseBody = new HashMap<>();
if (exceptionClass == ExpiredJwtException.class) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
responseBody.put("code", "EXPIRED");
responseBody.put("message", "Access Token is Expired!");
} else if (jwtExceptions.contains(exceptionClass)){
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
responseBody.put("code", "INVALID");
responseBody.put("message", "Invalid Access Token");
}else{
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
responseBody.put("code", "INVALID");
}
DataBuffer wrap = null;
try {
byte[] bytes = objectMapper.writeValueAsBytes(responseBody);
wrap = exchange.getResponse().bufferFactory().wrap(bytes);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return exchange.getResponse().writeWith(Flux.just(wrap));
}
}

View File

@@ -73,25 +73,12 @@ public class JwtTokenProvider {
return (List<String>) getClaimsFromJwtToken(token).get("roles");
}
public boolean validateJwtToken(String token) {
public void validateJwtToken(String token) {
try {
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
return true;
} catch (SignatureException e) {
log.error("Invalid JWT signature: {}", e.getMessage());
return false;
} catch (MalformedJwtException e) {
log.error("Invalid JWT token: {}", e.getMessage());
return false;
} catch (ExpiredJwtException e) {
log.error("JWT token is expired: {}", e.getMessage());
return false;
} catch (UnsupportedJwtException e) {
log.error("JWT token is unsupported: {}", e.getMessage());
return false;
} catch (IllegalArgumentException e) {
log.error("JWT claims string is empty: {}", e.getMessage());
return false;
} catch (SignatureException | MalformedJwtException |
UnsupportedJwtException | IllegalArgumentException | ExpiredJwtException jwtException) {
throw jwtException;
}
}

View File

@@ -15,6 +15,25 @@ spring:
cloud:
gateway:
default-filters:
- name: GlobalFilter
args:
baseMessage: Spring Cloud Gateway Global Filter
preLogger: true
postLogger: true
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "http://just-pickup.com:8080"
allowedMethods:
- GET
- POST
- DELETE
- PUT
- OPTIONS
- DELETE
allowedHeaders: '*'
allow-credentials: true
routes:
- id: owner-frontend-service
uri: lb://CUSTOMER-FRONTEND-SERVICE
@@ -33,6 +52,7 @@ spring:
predicates:
- Path=/store-service/**
filters:
- AuthorizationHeaderFilter
- RewritePath=/store-service/(?<segment>.*),/$\{segment}
- id: user-service
uri: lb://USER-SERVICE
@@ -55,6 +75,13 @@ spring:
- Method=POST
filters:
- RewritePath=/user-service/(?<segment>.*),/$\{segment}
- id: user-service
uri: lb://USER-SERVICE
predicates:
- Path=/user-service/auth/reissue
- Method=GET
filters:
- RewritePath=/user-service/(?<segment>.*),/$\{segment}
- id: user-service
uri: lb://USER-SERVICE
predicates: