diff --git a/auth-service/src/main/java/com/assu/cloud/authservice/security/JWTOAuth2Config.java b/auth-service/src/main/java/com/assu/cloud/authservice/security/JWTOAuth2Config.java index 79dcdcb..b2bffcc 100644 --- a/auth-service/src/main/java/com/assu/cloud/authservice/security/JWTOAuth2Config.java +++ b/auth-service/src/main/java/com/assu/cloud/authservice/security/JWTOAuth2Config.java @@ -49,12 +49,13 @@ public class JWTOAuth2Config extends AuthorizationServerConfigurerAdapter { */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + // 스프링 OAuth 의 TokenEnhancerChain 를 등록하면 여러 TokenEnhancer 후킹 가능 TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer, jwtAccessTokenConverter)); endpoints.tokenStore(tokenStore) // JWT, JWTTokenStoreConfig 에서 정의한 토큰 저장소 .accessTokenConverter(jwtAccessTokenConverter) // JWT, 스프링 시큐리티 OAuth2 가 JWT 사용하도록 연결 - .tokenEnhancer(tokenEnhancerChain) // JWT + .tokenEnhancer(tokenEnhancerChain) // JWT, endpoints 에 tokenEnhancerChain 연결 .authenticationManager(authenticationManager) .userDetailsService(userDetailsService); } diff --git a/auth-service/src/main/java/com/assu/cloud/authservice/security/JWTTokenEnhancer.java b/auth-service/src/main/java/com/assu/cloud/authservice/security/JWTTokenEnhancer.java index f06c869..bdc941f 100644 --- a/auth-service/src/main/java/com/assu/cloud/authservice/security/JWTTokenEnhancer.java +++ b/auth-service/src/main/java/com/assu/cloud/authservice/security/JWTTokenEnhancer.java @@ -1,6 +1,5 @@ package com.assu.cloud.authservice.security; -import com.assu.cloud.authservice.config.CustomConfig; import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; @@ -28,6 +27,7 @@ public class JWTTokenEnhancer implements TokenEnhancer { additionalInfo.put("userId", userId); + // 모든 추가 속성은 HashMap 에 추가하고, 메서드에 전달된 accessToken 변수에 추가 ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); return accessToken; } diff --git a/auth-service/src/main/java/com/assu/cloud/authservice/security/JWTTokenStoreConfig.java b/auth-service/src/main/java/com/assu/cloud/authservice/security/JWTTokenStoreConfig.java index fcf4a4a..d9ae9e1 100644 --- a/auth-service/src/main/java/com/assu/cloud/authservice/security/JWTTokenStoreConfig.java +++ b/auth-service/src/main/java/com/assu/cloud/authservice/security/JWTTokenStoreConfig.java @@ -52,6 +52,11 @@ public class JWTTokenStoreConfig { return converter; } + /** + * OAuth2 에 JWT 토큰 확장 클래스인 JWTTokenEnhancer 클래스를 사용한다고 알리기 위해 빈으로 노출 + * 여기서 노출하면 JWTOAuth2Config 에서 사용 가능 + * @return + */ @Bean public TokenEnhancer jwtTokenEnhancer() { return new JWTTokenEnhancer(); diff --git a/event-service/pom.xml b/event-service/pom.xml index 368a199..ce128fb 100644 --- a/event-service/pom.xml +++ b/event-service/pom.xml @@ -61,6 +61,12 @@ spring-security-oauth2-autoconfigure + + org.springframework.security + spring-security-jwt + 1.1.1.RELEASE + + org.springframework.boot spring-boot-starter-test diff --git a/event-service/src/main/java/com/assu/cloud/eventservice/EventServiceApplication.java b/event-service/src/main/java/com/assu/cloud/eventservice/EventServiceApplication.java index ac650d9..2536a0b 100644 --- a/event-service/src/main/java/com/assu/cloud/eventservice/EventServiceApplication.java +++ b/event-service/src/main/java/com/assu/cloud/eventservice/EventServiceApplication.java @@ -4,11 +4,14 @@ import com.assu.cloud.eventservice.utils.CustomContextInterceptor; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; 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.openfeign.EnableFeignClients; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; import org.springframework.security.oauth2.client.OAuth2RestTemplate; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.web.client.RestTemplate; import java.util.Collections; import java.util.List; @@ -16,9 +19,32 @@ import java.util.List; @EnableEurekaClient @SpringBootApplication @EnableFeignClients -@EnableResourceServer +@EnableResourceServer // 보호 자원으로 설정 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 @Bean public OAuth2RestTemplate restTemplate(UserInfoRestTemplateFactory factory) { @@ -31,9 +57,11 @@ public class EventServiceApplication { factory.getUserInfoRestTemplate().setInterceptors(interceptors); } return factory.getUserInfoRestTemplate(); - } + }*/ - /*@LoadBalanced // 스프링 클라우드가 리본이 지원하는 RestTemplate 클래스 생성하도록 지시 + /* + 기본 RestTemplate + @LoadBalanced // 스프링 클라우드가 리본이 지원하는 RestTemplate 클래스 생성하도록 지시 @Bean public RestTemplate getRestTemplate() { // return new RestTemplate(); diff --git a/event-service/src/main/java/com/assu/cloud/eventservice/client/MemberRestTemplateClient.java b/event-service/src/main/java/com/assu/cloud/eventservice/client/MemberRestTemplateClient.java index f2ad1e0..9fec1e5 100644 --- a/event-service/src/main/java/com/assu/cloud/eventservice/client/MemberRestTemplateClient.java +++ b/event-service/src/main/java/com/assu/cloud/eventservice/client/MemberRestTemplateClient.java @@ -12,7 +12,7 @@ import org.springframework.stereotype.Component; @Component public class MemberRestTemplateClient { - private final OAuth2RestTemplate restTemplate; + /*private final OAuth2RestTemplate restTemplate; private final CustomConfig customConfig; public MemberRestTemplateClient(OAuth2RestTemplate restTemplate, CustomConfig customConfig) { @@ -36,5 +36,5 @@ public class MemberRestTemplateClient { ); return restExchange.getBody(); - } + }*/ } diff --git a/event-service/src/main/java/com/assu/cloud/eventservice/config/CustomConfig.java b/event-service/src/main/java/com/assu/cloud/eventservice/config/CustomConfig.java index 9bc63d8..52803d7 100644 --- a/event-service/src/main/java/com/assu/cloud/eventservice/config/CustomConfig.java +++ b/event-service/src/main/java/com/assu/cloud/eventservice/config/CustomConfig.java @@ -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; + } } diff --git a/event-service/src/main/java/com/assu/cloud/eventservice/controller/EventController.java b/event-service/src/main/java/com/assu/cloud/eventservice/controller/EventController.java index 1b58a05..aaede61 100644 --- a/event-service/src/main/java/com/assu/cloud/eventservice/controller/EventController.java +++ b/event-service/src/main/java/com/assu/cloud/eventservice/controller/EventController.java @@ -49,8 +49,8 @@ public class EventController { return "[EVENT] Gift is " + gift; } - @GetMapping("userInfo/{name}") + /*@GetMapping("userInfo/{name}") public String userInfo(@PathVariable("name") String name) { return "[EVENT-MEMBER] " + memberRestTemplateClient.userInfo(name); - } + }*/ } diff --git a/event-service/src/main/java/com/assu/cloud/eventservice/security/JWTTokenStoreConfig.java b/event-service/src/main/java/com/assu/cloud/eventservice/security/JWTTokenStoreConfig.java new file mode 100644 index 0000000..0c83cb1 --- /dev/null +++ b/event-service/src/main/java/com/assu/cloud/eventservice/security/JWTTokenStoreConfig.java @@ -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; + } +} diff --git a/event-service/src/main/java/com/assu/cloud/eventservice/utils/CustomContext.java b/event-service/src/main/java/com/assu/cloud/eventservice/utils/CustomContext.java index 58724d7..1daae93 100644 --- a/event-service/src/main/java/com/assu/cloud/eventservice/utils/CustomContext.java +++ b/event-service/src/main/java/com/assu/cloud/eventservice/utils/CustomContext.java @@ -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 correlationId = new ThreadLocal<>(); + private static final ThreadLocal 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); + } } diff --git a/event-service/src/main/java/com/assu/cloud/eventservice/utils/CustomContextFilter.java b/event-service/src/main/java/com/assu/cloud/eventservice/utils/CustomContextFilter.java index c9a5803..c427eab 100644 --- a/event-service/src/main/java/com/assu/cloud/eventservice/utils/CustomContextFilter.java +++ b/event-service/src/main/java/com/assu/cloud/eventservice/utils/CustomContextFilter.java @@ -12,6 +12,8 @@ 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()); diff --git a/event-service/src/main/java/com/assu/cloud/eventservice/utils/CustomContextHolder.java b/event-service/src/main/java/com/assu/cloud/eventservice/utils/CustomContextHolder.java index fa17cb8..3e3562b 100644 --- a/event-service/src/main/java/com/assu/cloud/eventservice/utils/CustomContextHolder.java +++ b/event-service/src/main/java/com/assu/cloud/eventservice/utils/CustomContextHolder.java @@ -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); } diff --git a/event-service/src/main/java/com/assu/cloud/eventservice/utils/CustomContextInterceptor.java b/event-service/src/main/java/com/assu/cloud/eventservice/utils/CustomContextInterceptor.java index cf4e4bf..321b8c4 100644 --- a/event-service/src/main/java/com/assu/cloud/eventservice/utils/CustomContextInterceptor.java +++ b/event-service/src/main/java/com/assu/cloud/eventservice/utils/CustomContextInterceptor.java @@ -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); } diff --git a/event-service/src/main/resources/application.yaml b/event-service/src/main/resources/application.yaml index a74ec55..3fdae5b 100644 --- a/event-service/src/main/resources/application.yaml +++ b/event-service/src/main/resources/application.yaml @@ -1,2 +1,2 @@ server: - port: 8070 + port: 8070 \ No newline at end of file