마이크로서비스(이벤트 서비스)에서 JWT 사용
This commit is contained in:
@@ -49,12 +49,13 @@ public class JWTOAuth2Config extends AuthorizationServerConfigurerAdapter {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
|
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
|
||||||
|
// 스프링 OAuth 의 TokenEnhancerChain 를 등록하면 여러 TokenEnhancer 후킹 가능
|
||||||
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
|
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
|
||||||
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer, jwtAccessTokenConverter));
|
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer, jwtAccessTokenConverter));
|
||||||
|
|
||||||
endpoints.tokenStore(tokenStore) // JWT, JWTTokenStoreConfig 에서 정의한 토큰 저장소
|
endpoints.tokenStore(tokenStore) // JWT, JWTTokenStoreConfig 에서 정의한 토큰 저장소
|
||||||
.accessTokenConverter(jwtAccessTokenConverter) // JWT, 스프링 시큐리티 OAuth2 가 JWT 사용하도록 연결
|
.accessTokenConverter(jwtAccessTokenConverter) // JWT, 스프링 시큐리티 OAuth2 가 JWT 사용하도록 연결
|
||||||
.tokenEnhancer(tokenEnhancerChain) // JWT
|
.tokenEnhancer(tokenEnhancerChain) // JWT, endpoints 에 tokenEnhancerChain 연결
|
||||||
.authenticationManager(authenticationManager)
|
.authenticationManager(authenticationManager)
|
||||||
.userDetailsService(userDetailsService);
|
.userDetailsService(userDetailsService);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.assu.cloud.authservice.security;
|
package com.assu.cloud.authservice.security;
|
||||||
|
|
||||||
import com.assu.cloud.authservice.config.CustomConfig;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
|
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
|
||||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||||
@@ -28,6 +27,7 @@ public class JWTTokenEnhancer implements TokenEnhancer {
|
|||||||
|
|
||||||
additionalInfo.put("userId", userId);
|
additionalInfo.put("userId", userId);
|
||||||
|
|
||||||
|
// 모든 추가 속성은 HashMap 에 추가하고, 메서드에 전달된 accessToken 변수에 추가
|
||||||
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
|
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
|
||||||
return accessToken;
|
return accessToken;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ public class JWTTokenStoreConfig {
|
|||||||
return converter;
|
return converter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAuth2 에 JWT 토큰 확장 클래스인 JWTTokenEnhancer 클래스를 사용한다고 알리기 위해 빈으로 노출
|
||||||
|
* 여기서 노출하면 JWTOAuth2Config 에서 사용 가능
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public TokenEnhancer jwtTokenEnhancer() {
|
public TokenEnhancer jwtTokenEnhancer() {
|
||||||
return new JWTTokenEnhancer();
|
return new JWTTokenEnhancer();
|
||||||
|
|||||||
@@ -61,6 +61,12 @@
|
|||||||
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
|
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-jwt</artifactId>
|
||||||
|
<version>1.1.1.RELEASE</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ import com.assu.cloud.eventservice.utils.CustomContextInterceptor;
|
|||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoRestTemplateFactory;
|
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoRestTemplateFactory;
|
||||||
|
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||||
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
|
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
|
||||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
|
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
|
||||||
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
|
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -16,9 +19,32 @@ import java.util.List;
|
|||||||
@EnableEurekaClient
|
@EnableEurekaClient
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableFeignClients
|
@EnableFeignClients
|
||||||
@EnableResourceServer
|
@EnableResourceServer // 보호 자원으로 설정
|
||||||
public class EventServiceApplication {
|
public class EventServiceApplication {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자 정의 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// OAuth2 RestTemplate -> JWT 기반 토큰을 전파하지 앟음
|
||||||
//@LoadBalanced
|
//@LoadBalanced
|
||||||
@Bean
|
@Bean
|
||||||
public OAuth2RestTemplate restTemplate(UserInfoRestTemplateFactory factory) {
|
public OAuth2RestTemplate restTemplate(UserInfoRestTemplateFactory factory) {
|
||||||
@@ -31,9 +57,11 @@ public class EventServiceApplication {
|
|||||||
factory.getUserInfoRestTemplate().setInterceptors(interceptors);
|
factory.getUserInfoRestTemplate().setInterceptors(interceptors);
|
||||||
}
|
}
|
||||||
return factory.getUserInfoRestTemplate();
|
return factory.getUserInfoRestTemplate();
|
||||||
}
|
}*/
|
||||||
|
|
||||||
/*@LoadBalanced // 스프링 클라우드가 리본이 지원하는 RestTemplate 클래스 생성하도록 지시
|
/*
|
||||||
|
기본 RestTemplate
|
||||||
|
@LoadBalanced // 스프링 클라우드가 리본이 지원하는 RestTemplate 클래스 생성하도록 지시
|
||||||
@Bean
|
@Bean
|
||||||
public RestTemplate getRestTemplate() {
|
public RestTemplate getRestTemplate() {
|
||||||
// return new RestTemplate();
|
// return new RestTemplate();
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import org.springframework.stereotype.Component;
|
|||||||
@Component
|
@Component
|
||||||
public class MemberRestTemplateClient {
|
public class MemberRestTemplateClient {
|
||||||
|
|
||||||
private final OAuth2RestTemplate restTemplate;
|
/*private final OAuth2RestTemplate restTemplate;
|
||||||
private final CustomConfig customConfig;
|
private final CustomConfig customConfig;
|
||||||
|
|
||||||
public MemberRestTemplateClient(OAuth2RestTemplate restTemplate, CustomConfig customConfig) {
|
public MemberRestTemplateClient(OAuth2RestTemplate restTemplate, CustomConfig customConfig) {
|
||||||
@@ -36,5 +36,5 @@ public class MemberRestTemplateClient {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return restExchange.getBody();
|
return restExchange.getBody();
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ public class CustomConfig {
|
|||||||
@Value("${service.id.zuul}")
|
@Value("${service.id.zuul}")
|
||||||
private String serviceIdZuul;
|
private String serviceIdZuul;
|
||||||
|
|
||||||
|
@Value("${signing.key}")
|
||||||
|
private String jwtSigningKey = "";
|
||||||
|
|
||||||
public String getYourName() {
|
public String getYourName() {
|
||||||
return yourName;
|
return yourName;
|
||||||
}
|
}
|
||||||
@@ -20,4 +23,8 @@ public class CustomConfig {
|
|||||||
public String getServiceIdZuul() {
|
public String getServiceIdZuul() {
|
||||||
return serviceIdZuul;
|
return serviceIdZuul;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getJwtSigningKey() {
|
||||||
|
return jwtSigningKey;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ public class EventController {
|
|||||||
return "[EVENT] Gift is " + gift;
|
return "[EVENT] Gift is " + gift;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("userInfo/{name}")
|
/*@GetMapping("userInfo/{name}")
|
||||||
public String userInfo(@PathVariable("name") String name) {
|
public String userInfo(@PathVariable("name") String name) {
|
||||||
return "[EVENT-MEMBER] " + memberRestTemplateClient.userInfo(name);
|
return "[EVENT-MEMBER] " + memberRestTemplateClient.userInfo(name);
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.assu.cloud.eventservice.security;
|
||||||
|
|
||||||
|
import com.assu.cloud.eventservice.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;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class JWTTokenStoreConfig {
|
||||||
|
|
||||||
|
private final CustomConfig customConfig;
|
||||||
|
|
||||||
|
public JWTTokenStoreConfig(CustomConfig customConfig) {
|
||||||
|
this.customConfig = customConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TokenStore tokenStore() {
|
||||||
|
return new JwtTokenStore(jwtAccessTokenConverter());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JwtAccessTokenConverter jwtAccessTokenConverter() {
|
||||||
|
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
|
||||||
|
converter.setSigningKey(customConfig.getJwtSigningKey());
|
||||||
|
return converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Primary
|
||||||
|
public DefaultTokenServices tokenServices() {
|
||||||
|
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
|
||||||
|
defaultTokenServices.setTokenStore(tokenStore());
|
||||||
|
defaultTokenServices.setSupportRefreshToken(true);
|
||||||
|
return defaultTokenServices;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,11 +9,14 @@ import org.springframework.stereotype.Component;
|
|||||||
@Component
|
@Component
|
||||||
public class CustomContext {
|
public class CustomContext {
|
||||||
public static final String CORRELATION_ID = "assu-correlation-id";
|
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> correlationId = new ThreadLocal<>();
|
||||||
|
private static final ThreadLocal<String> authToken = new ThreadLocal<>();
|
||||||
|
|
||||||
// 그 외 필요한 항목 넣을 수 있음 (인증 토큰 등...)
|
// 그 외 필요한 항목 넣을 수 있음 (인증 토큰 등...)
|
||||||
|
|
||||||
|
|
||||||
public static String getCorrelationId() {
|
public static String getCorrelationId() {
|
||||||
return correlationId.get();
|
return correlationId.get();
|
||||||
}
|
}
|
||||||
@@ -21,4 +24,12 @@ public class CustomContext {
|
|||||||
public static void setCorrelationId(String cid) {
|
public static void setCorrelationId(String cid) {
|
||||||
correlationId.set(cid);
|
correlationId.set(cid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getAuthToken() {
|
||||||
|
return authToken.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setAuthToken(String aToken) {
|
||||||
|
authToken.set(aToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import java.io.IOException;
|
|||||||
* 유입되는 HTTP 요청을 가로채서 필요한 헤더값을 CustomContext 에 매핑
|
* 유입되는 HTTP 요청을 가로채서 필요한 헤더값을 CustomContext 에 매핑
|
||||||
*
|
*
|
||||||
* REST 서비스에 대한 모든 HTTP 요청을 가로채서 컨텍스트 정보(상관관계 ID 등)를 추출해 CustomContext 클래스에 매핑하는 HTTP 서블릿 필터
|
* REST 서비스에 대한 모든 HTTP 요청을 가로채서 컨텍스트 정보(상관관계 ID 등)를 추출해 CustomContext 클래스에 매핑하는 HTTP 서블릿 필터
|
||||||
|
* (즉, HTTP 헤더에서 인증 토큰과 상관관계 ID 파싱)
|
||||||
|
*
|
||||||
* REST 서비스 호출 시 코드에서 CustomContext 액세스가 필요할 때마다 ThreadLocal 변수에서 검색해 읽어올 수 있음
|
* REST 서비스 호출 시 코드에서 CustomContext 액세스가 필요할 때마다 ThreadLocal 변수에서 검색해 읽어올 수 있음
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@@ -25,7 +27,9 @@ public class CustomContextFilter implements Filter {
|
|||||||
|
|
||||||
// HTTP 호출 헤더에서 상관관계 ID 를 검색하여 CustomContextHolder 의 CustomContext 클래스에 설정
|
// HTTP 호출 헤더에서 상관관계 ID 를 검색하여 CustomContextHolder 의 CustomContext 클래스에 설정
|
||||||
CustomContextHolder.getContext().setCorrelationId(httpServletRequest.getHeader(CustomContext.CORRELATION_ID));
|
CustomContextHolder.getContext().setCorrelationId(httpServletRequest.getHeader(CustomContext.CORRELATION_ID));
|
||||||
|
|
||||||
// 그 외 필요한 항목 넣을 수 있음 (인증 토큰 등...)
|
// 그 외 필요한 항목 넣을 수 있음 (인증 토큰 등...)
|
||||||
|
CustomContextHolder.getContext().setAuthToken(httpServletRequest.getHeader(CustomContext.AUTH_TOKEN));
|
||||||
|
|
||||||
logger.debug("상관관계 ID {} 로 실행된 동적 라우팅", CustomContextHolder.getContext().getCorrelationId());
|
logger.debug("상관관계 ID {} 로 실행된 동적 라우팅", CustomContextHolder.getContext().getCorrelationId());
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public class CustomContextHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static final void setContext(CustomContext ctx) {
|
public static final void setContext(CustomContext ctx) {
|
||||||
Assert.notNull(ctx, "customcontxt is null.");
|
Assert.notNull(ctx, "CustomContext is null.");
|
||||||
customContext.set(ctx);
|
customContext.set(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import org.springframework.http.client.ClientHttpResponse;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RestTemplate 인스턴스에서 실행되는 모든 HTTP 기반 서비스 발신 요청에 상관관계 ID 삽입
|
* RestTemplate 인스턴스에서 실행되는 모든 HTTP 기반 서비스 발신 요청에 상관관계 ID 삽입 + 토큰
|
||||||
*/
|
*/
|
||||||
public class CustomContextInterceptor implements ClientHttpRequestInterceptor {
|
public class CustomContextInterceptor implements ClientHttpRequestInterceptor {
|
||||||
/**
|
/**
|
||||||
@@ -20,7 +20,9 @@ public class CustomContextInterceptor implements ClientHttpRequestInterceptor {
|
|||||||
HttpHeaders headers = httpRequest.getHeaders();
|
HttpHeaders headers = httpRequest.getHeaders();
|
||||||
|
|
||||||
headers.add(CustomContext.CORRELATION_ID, CustomContextHolder.getContext().getCorrelationId());
|
headers.add(CustomContext.CORRELATION_ID, CustomContextHolder.getContext().getCorrelationId());
|
||||||
|
|
||||||
// 그 외 필요한 항목 넣을 수 있음 (인증 토큰 등...)
|
// 그 외 필요한 항목 넣을 수 있음 (인증 토큰 등...)
|
||||||
|
headers.add(CustomContext.AUTH_TOKEN, CustomContextHolder.getContext().getAuthToken()); // HTTP 헤더에 인증 토큰 추가
|
||||||
|
|
||||||
return clientHttpRequestExecution.execute(httpRequest, bytes);
|
return clientHttpRequestExecution.execute(httpRequest, bytes);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
server:
|
server:
|
||||||
port: 8070
|
port: 8070
|
||||||
Reference in New Issue
Block a user