- 인증서버의 Custom 필터, 인터셉터 수정
- 회원서비스에서 JWT 토큰 사용하도록 수정 - 주울 서비스에서 Custom 필터, 인터셉터 삭제
This commit is contained in:
@@ -9,11 +9,14 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
public class CustomContext {
|
||||
public static final String CORRELATION_ID = "assu-correlation-id";
|
||||
public static final String AUTH_TOKEN = "Authorization";
|
||||
|
||||
private static final ThreadLocal<String> correlationId = new ThreadLocal<>();
|
||||
private static final ThreadLocal<String> authToken = new ThreadLocal<>();
|
||||
|
||||
// 그 외 필요한 항목 넣을 수 있음 (인증 토큰 등...)
|
||||
|
||||
|
||||
public static String getCorrelationId() {
|
||||
return correlationId.get();
|
||||
}
|
||||
@@ -21,4 +24,12 @@ public class CustomContext {
|
||||
public static void setCorrelationId(String cid) {
|
||||
correlationId.set(cid);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getAuthToken() {
|
||||
return authToken.get();
|
||||
}
|
||||
|
||||
public static void setAuthToken(String aToken) {
|
||||
authToken.set(aToken);
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,10 @@ import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 유입되는 HTTP 요청을 가로채서 필요한 헤더값을 CustomContext 에 매핑
|
||||
*
|
||||
*
|
||||
* REST 서비스에 대한 모든 HTTP 요청을 가로채서 컨텍스트 정보(상관관계 ID 등)를 추출해 CustomContext 클래스에 매핑하는 HTTP 서블릿 필터
|
||||
* (즉, HTTP 헤더에서 인증 토큰과 상관관계 ID 파싱)
|
||||
*
|
||||
* REST 서비스 호출 시 코드에서 CustomContext 액세스가 필요할 때마다 ThreadLocal 변수에서 검색해 읽어올 수 있음
|
||||
*/
|
||||
@Component
|
||||
@@ -25,7 +27,9 @@ public class CustomContextFilter implements Filter {
|
||||
|
||||
// HTTP 호출 헤더에서 상관관계 ID 를 검색하여 CustomContextHolder 의 CustomContext 클래스에 설정
|
||||
CustomContextHolder.getContext().setCorrelationId(httpServletRequest.getHeader(CustomContext.CORRELATION_ID));
|
||||
|
||||
// 그 외 필요한 항목 넣을 수 있음 (인증 토큰 등...)
|
||||
CustomContextHolder.getContext().setAuthToken(httpServletRequest.getHeader(CustomContext.AUTH_TOKEN));
|
||||
|
||||
logger.debug("상관관계 ID {} 로 실행된 동적 라우팅", CustomContextHolder.getContext().getCorrelationId());
|
||||
|
||||
@@ -37,4 +41,4 @@ public class CustomContextFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void destroy() {}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ public class CustomContextHolder {
|
||||
}
|
||||
|
||||
public static final void setContext(CustomContext ctx) {
|
||||
Assert.notNull(ctx, "customcontxt is null.");
|
||||
Assert.notNull(ctx, "CustomContext is null.");
|
||||
customContext.set(ctx);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.springframework.http.client.ClientHttpResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* RestTemplate 인스턴스에서 실행되는 모든 HTTP 기반 서비스 발신 요청에 상관관계 ID 삽입
|
||||
* RestTemplate 인스턴스에서 실행되는 모든 HTTP 기반 서비스 발신 요청에 상관관계 ID 삽입 + 토큰
|
||||
*/
|
||||
public class CustomContextInterceptor implements ClientHttpRequestInterceptor {
|
||||
/**
|
||||
@@ -20,7 +20,9 @@ public class CustomContextInterceptor implements ClientHttpRequestInterceptor {
|
||||
HttpHeaders headers = httpRequest.getHeaders();
|
||||
|
||||
headers.add(CustomContext.CORRELATION_ID, CustomContextHolder.getContext().getCorrelationId());
|
||||
|
||||
// 그 외 필요한 항목 넣을 수 있음 (인증 토큰 등...)
|
||||
headers.add(CustomContext.AUTH_TOKEN, CustomContextHolder.getContext().getAuthToken()); // HTTP 헤더에 인증 토큰 추가
|
||||
|
||||
return clientHttpRequestExecution.execute(httpRequest, bytes);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ import org.springframework.security.oauth2.provider.token.TokenStore;
|
||||
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
|
||||
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
|
||||
|
||||
/**
|
||||
* 인증 서버가 JWT 토큰을 생성, 서명, 해석하는 방법 지정
|
||||
*/
|
||||
@Configuration
|
||||
public class JWTTokenStoreConfig {
|
||||
|
||||
@@ -23,19 +26,28 @@ public class JWTTokenStoreConfig {
|
||||
return new JwtTokenStore(jwtAccessTokenConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* 서비스에 전달된 토큰에서 데이터를 읽는데 사용
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public JwtAccessTokenConverter jwtAccessTokenConverter() {
|
||||
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
|
||||
converter.setSigningKey(customConfig.getJwtSigningKey());
|
||||
return converter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@Primary // 특정 타입의 빈이 둘 이상인 경우 (여기선 DefaultTokenServices) @Primary 로 지정된 타입을 자동 주입
|
||||
public DefaultTokenServices tokenServices() {
|
||||
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
|
||||
defaultTokenServices.setTokenStore(tokenStore());
|
||||
defaultTokenServices.setSupportRefreshToken(true);
|
||||
return defaultTokenServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT 와 OAuth2 인증 서버 사이의 변환기
|
||||
* 토큰 서명에 사용되는 서명키 사용 (여기선 대칭 키)
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public JwtAccessTokenConverter jwtAccessTokenConverter() {
|
||||
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
|
||||
converter.setSigningKey(customConfig.getJwtSigningKey()); // 토큰 서명에 사용되는 서명키 정의
|
||||
return converter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,13 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-oauth2-client</artifactId>
|
||||
</dependency>-->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-jwt</artifactId>
|
||||
<version>1.1.1.RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@@ -20,6 +21,29 @@ public class MemberServiceApplication {
|
||||
SpringApplication.run(MemberServiceApplication.class, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 정의 RestTemplate 빈을 생성하여 토큰 삽입
|
||||
* RestTemplate 기반 호출이 수행되기 전 후킹되는 메서드
|
||||
*/
|
||||
@Primary
|
||||
@LoadBalanced
|
||||
@Bean
|
||||
public RestTemplate getCustomRestTemplate() {
|
||||
RestTemplate template = new RestTemplate();
|
||||
List interceptors = template.getInterceptors();
|
||||
|
||||
// CustomContextInterceptor 는 Authorization 헤더를 모든 REST 호출에 삽입함
|
||||
if (interceptors == null) {
|
||||
template.setInterceptors(Collections.singletonList(new CustomContextInterceptor()));
|
||||
} else {
|
||||
interceptors.add(new CustomContextInterceptor());
|
||||
template.setInterceptors(interceptors);
|
||||
}
|
||||
return template;
|
||||
}
|
||||
|
||||
/*
|
||||
// 기본 RestTemplate
|
||||
@LoadBalanced // 스프링 클라우드가 리본이 지원하는 RestTemplate 클래스 생성하도록 지시
|
||||
@Bean
|
||||
public RestTemplate getRestTemplate() {
|
||||
@@ -34,5 +58,5 @@ public class MemberServiceApplication {
|
||||
template.setInterceptors(interceptors);
|
||||
}
|
||||
return template;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ public class CustomConfig {
|
||||
@Value("${service.id.zuul}")
|
||||
private String serviceIdZuul;
|
||||
|
||||
@Value("${signing.key}")
|
||||
private String jwtSigningKey = "";
|
||||
|
||||
public String getYourName() {
|
||||
return yourName;
|
||||
}
|
||||
@@ -20,4 +23,8 @@ public class CustomConfig {
|
||||
public String getServiceIdZuul() {
|
||||
return serviceIdZuul;
|
||||
}
|
||||
|
||||
public String getJwtSigningKey() {
|
||||
return jwtSigningKey;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.assu.cloud.memberservice.security;
|
||||
|
||||
import com.assu.cloud.memberservice.config.CustomConfig;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
|
||||
import org.springframework.security.oauth2.provider.token.TokenStore;
|
||||
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
|
||||
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
|
||||
|
||||
/**
|
||||
* 인증 서버가 JWT 토큰을 생성, 서명, 해석하는 방법 지정
|
||||
*/
|
||||
@Configuration
|
||||
public class JWTTokenStoreConfig {
|
||||
|
||||
private final CustomConfig customConfig;
|
||||
|
||||
public JWTTokenStoreConfig(CustomConfig customConfig) {
|
||||
this.customConfig = customConfig;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TokenStore tokenStore() {
|
||||
return new JwtTokenStore(jwtAccessTokenConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* 서비스에 전달된 토큰에서 데이터를 읽는데 사용
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
@Primary // 특정 타입의 빈이 둘 이상인 경우 (여기선 DefaultTokenServices) @Primary 로 지정된 타입을 자동 주입
|
||||
public DefaultTokenServices tokenServices() {
|
||||
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
|
||||
defaultTokenServices.setTokenStore(tokenStore());
|
||||
defaultTokenServices.setSupportRefreshToken(true);
|
||||
return defaultTokenServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT 와 OAuth2 인증 서버 사이의 변환기
|
||||
* 토큰 서명에 사용되는 서명키 사용 (여기선 대칭 키)
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public JwtAccessTokenConverter jwtAccessTokenConverter() {
|
||||
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
|
||||
converter.setSigningKey(customConfig.getJwtSigningKey()); // 토큰 서명에 사용되는 서명키 정의
|
||||
return converter;
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,14 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
public class CustomContext {
|
||||
public static final String CORRELATION_ID = "assu-correlation-id";
|
||||
public static final String AUTH_TOKEN = "Authorization";
|
||||
|
||||
private static final ThreadLocal<String> correlationId = new ThreadLocal<>();
|
||||
private static final ThreadLocal<String> authToken = new ThreadLocal<>();
|
||||
|
||||
// 그 외 필요한 항목 넣을 수 있음 (인증 토큰 등...)
|
||||
|
||||
|
||||
public static String getCorrelationId() {
|
||||
return correlationId.get();
|
||||
}
|
||||
@@ -21,4 +24,12 @@ public class CustomContext {
|
||||
public static void setCorrelationId(String cid) {
|
||||
correlationId.set(cid);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getAuthToken() {
|
||||
return authToken.get();
|
||||
}
|
||||
|
||||
public static void setAuthToken(String aToken) {
|
||||
authToken.set(aToken);
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,10 @@ import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 유입되는 HTTP 요청을 가로채서 필요한 헤더값을 CustomContext 에 매핑
|
||||
*
|
||||
*
|
||||
* REST 서비스에 대한 모든 HTTP 요청을 가로채서 컨텍스트 정보(상관관계 ID 등)를 추출해 CustomContext 클래스에 매핑하는 HTTP 서블릿 필터
|
||||
* (즉, HTTP 헤더에서 인증 토큰과 상관관계 ID 파싱)
|
||||
*
|
||||
* REST 서비스 호출 시 코드에서 CustomContext 액세스가 필요할 때마다 ThreadLocal 변수에서 검색해 읽어올 수 있음
|
||||
*/
|
||||
@Component
|
||||
@@ -25,7 +27,9 @@ public class CustomContextFilter implements Filter {
|
||||
|
||||
// HTTP 호출 헤더에서 상관관계 ID 를 검색하여 CustomContextHolder 의 CustomContext 클래스에 설정
|
||||
CustomContextHolder.getContext().setCorrelationId(httpServletRequest.getHeader(CustomContext.CORRELATION_ID));
|
||||
|
||||
// 그 외 필요한 항목 넣을 수 있음 (인증 토큰 등...)
|
||||
CustomContextHolder.getContext().setAuthToken(httpServletRequest.getHeader(CustomContext.AUTH_TOKEN));
|
||||
|
||||
logger.debug("상관관계 ID {} 로 실행된 동적 라우팅", CustomContextHolder.getContext().getCorrelationId());
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ public class CustomContextHolder {
|
||||
}
|
||||
|
||||
public static final void setContext(CustomContext ctx) {
|
||||
Assert.notNull(ctx, "customcontxt is null.");
|
||||
Assert.notNull(ctx, "CustomContext is null.");
|
||||
customContext.set(ctx);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.springframework.http.client.ClientHttpResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* RestTemplate 인스턴스에서 실행되는 모든 HTTP 기반 서비스 발신 요청에 상관관계 ID 삽입
|
||||
* RestTemplate 인스턴스에서 실행되는 모든 HTTP 기반 서비스 발신 요청에 상관관계 ID 삽입 + 토큰
|
||||
*/
|
||||
public class CustomContextInterceptor implements ClientHttpRequestInterceptor {
|
||||
/**
|
||||
@@ -20,7 +20,9 @@ public class CustomContextInterceptor implements ClientHttpRequestInterceptor {
|
||||
HttpHeaders headers = httpRequest.getHeaders();
|
||||
|
||||
headers.add(CustomContext.CORRELATION_ID, CustomContextHolder.getContext().getCorrelationId());
|
||||
|
||||
// 그 외 필요한 항목 넣을 수 있음 (인증 토큰 등...)
|
||||
headers.add(CustomContext.AUTH_TOKEN, CustomContextHolder.getContext().getAuthToken()); // HTTP 헤더에 인증 토큰 추가
|
||||
|
||||
return clientHttpRequestExecution.execute(httpRequest, bytes);
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.assu.cloud.zuulserver.utils;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 서비스가 쉽게 액세스할 수 있는 HTTP 헤더를 만들어 저장하는 클래스
|
||||
* HTTP 요청에서 추출한 값을 보관하는 POJO
|
||||
*/
|
||||
@Component
|
||||
public class CustomContext {
|
||||
public static final String CORRELATION_ID = "assu-correlation-id";
|
||||
|
||||
private static final ThreadLocal<String> correlationId = new ThreadLocal<>();
|
||||
|
||||
// 그 외 필요한 항목 넣을 수 있음 (인증 토큰 등...)
|
||||
|
||||
public static String getCorrelationId() {
|
||||
return correlationId.get();
|
||||
}
|
||||
|
||||
public static void setCorrelationId(String cid) {
|
||||
correlationId.set(cid);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.assu.cloud.zuulserver.utils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 유입되는 HTTP 요청을 가로채서 필요한 헤더값을 CustomContext 에 매핑
|
||||
*
|
||||
* REST 서비스에 대한 모든 HTTP 요청을 가로채서 컨텍스트 정보(상관관계 ID 등)를 추출해 CustomContext 클래스에 매핑하는 HTTP 서블릿 필터
|
||||
* REST 서비스 호출 시 코드에서 CustomContext 액세스가 필요할 때마다 ThreadLocal 변수에서 검색해 읽어올 수 있음
|
||||
*/
|
||||
@Component
|
||||
public class CustomContextFilter implements Filter {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CustomContextFilter.class);
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
|
||||
|
||||
// HTTP 호출 헤더에서 상관관계 ID 를 검색하여 CustomContextHolder 의 CustomContext 클래스에 설정
|
||||
CustomContextHolder.getContext().setCorrelationId(httpServletRequest.getHeader(CustomContext.CORRELATION_ID));
|
||||
// 그 외 필요한 항목 넣을 수 있음 (인증 토큰 등...)
|
||||
|
||||
logger.debug("상관관계 ID {} 로 실행된 동적 라우팅", CustomContextHolder.getContext().getCorrelationId());
|
||||
|
||||
filterChain.doFilter(httpServletRequest, servletResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) {}
|
||||
|
||||
@Override
|
||||
public void destroy() {}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.assu.cloud.zuulserver.utils;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* ThreadLocal 저장소에 CustomContext 를 저장하는 클래스
|
||||
* * ThreadLocal 변수: 사용자 요청을 처리하는 해당 스레드에서 호출되는 모든 메서드에서 액세스 가능한 변수
|
||||
*
|
||||
* CustomContext 가 ThreadLocal 저장소에 저장되면 요청으로 실행된 모든 코드에서 CustomContextHolder 의 CustomContext 객체 사용 가능
|
||||
*/
|
||||
public class CustomContextHolder {
|
||||
|
||||
/** 정적 ThreadLocal 변수에 저장되는 CustomContext */
|
||||
private static final ThreadLocal<CustomContext> customContext = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* CustomContext 객체를 사용하기 위해 조회해오는 메서드
|
||||
*/
|
||||
public static final CustomContext getContext() {
|
||||
CustomContext ctx = customContext.get();
|
||||
|
||||
if (ctx == null) {
|
||||
ctx = createEmptyContext();
|
||||
customContext.set(ctx);
|
||||
}
|
||||
return customContext.get();
|
||||
}
|
||||
|
||||
public static final void setContext(CustomContext ctx) {
|
||||
Assert.notNull(ctx, "customcontxt is null.");
|
||||
customContext.set(ctx);
|
||||
}
|
||||
|
||||
public static final CustomContext createEmptyContext() {
|
||||
return new CustomContext();
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.assu.cloud.zuulserver.utils;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* RestTemplate 인스턴스에서 실행되는 모든 HTTP 기반 서비스 발신 요청에 상관관계 ID 삽입
|
||||
*/
|
||||
public class CustomContextInterceptor implements ClientHttpRequestInterceptor {
|
||||
/**
|
||||
* RestTemplate 로 실제 HTTP 서비스 호출 전 intercept 메서드 호출
|
||||
*/
|
||||
@Override
|
||||
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
|
||||
HttpHeaders headers = httpRequest.getHeaders();
|
||||
|
||||
headers.add(CustomContext.CORRELATION_ID, CustomContextHolder.getContext().getCorrelationId());
|
||||
// 그 외 필요한 항목 넣을 수 있음 (인증 토큰 등...)
|
||||
|
||||
return clientHttpRequestExecution.execute(httpRequest, bytes);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user