diff --git a/README.md b/README.md index 0189f5b..476ccdd 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,15 @@ HOW TO RUN -- 액세스 토큰으로 사용자 정보 조회 [GET] http://localhost:8901/auth/user + +-- OAuth2 로 회원 서비스 보호 후 API 호출 +[GET] http://localhost:8090/member/name/rinda + +-- 권한 있는 사용자(assuAdmin) 의 액세스 토큰과 함께 PUT 메서드 API 호출 +[PUT] http://localhost:8090/member/rinda + +-- oauth2 전파 (이벤트 서비스에서 회원서비스 호출) +[GET] http://localhost:5555/api/evt/event/userInfo/rinda ``` --- diff --git a/auth-service/src/main/java/com/assu/cloud/authservice/utils/CustomContext.java b/auth-service/src/main/java/com/assu/cloud/authservice/utils/CustomContext.java index 668db7e..edb283d 100644 --- a/auth-service/src/main/java/com/assu/cloud/authservice/utils/CustomContext.java +++ b/auth-service/src/main/java/com/assu/cloud/authservice/utils/CustomContext.java @@ -10,15 +10,15 @@ import org.springframework.stereotype.Component; public class CustomContext { public static final String CORRELATION_ID = "assu-correlation-id"; - private String correlationId = new String(); + private static final ThreadLocal correlationId = new ThreadLocal<>(); // 그 외 필요한 항목 넣을 수 있음 (인증 토큰 등...) - public String getCorrelationId() { - return correlationId; + public static String getCorrelationId() { + return correlationId.get(); } - public void setCorrelationId(String correlationId) { - this.correlationId = correlationId; + public static void setCorrelationId(String cid) { + correlationId.set(cid); } } diff --git a/event-service/pom.xml b/event-service/pom.xml index 41655c6..368a199 100644 --- a/event-service/pom.xml +++ b/event-service/pom.xml @@ -56,6 +56,11 @@ org.springframework.cloud spring-cloud-starter-openfeign + + org.springframework.security.oauth.boot + spring-security-oauth2-autoconfigure + + 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 fc768b2..ac650d9 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 @@ -1,17 +1,55 @@ package com.assu.cloud.eventservice; +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.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Bean; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; + +import java.util.Collections; +import java.util.List; @EnableEurekaClient @SpringBootApplication @EnableFeignClients +@EnableResourceServer public class EventServiceApplication { + //@LoadBalanced + @Bean + public OAuth2RestTemplate restTemplate(UserInfoRestTemplateFactory factory) { + List interceptors = factory.getUserInfoRestTemplate().getInterceptors(); + + if (interceptors == null) { + factory.getUserInfoRestTemplate().setInterceptors(Collections.singletonList(new CustomContextInterceptor())); + } else { + interceptors.add(new CustomContextInterceptor()); + factory.getUserInfoRestTemplate().setInterceptors(interceptors); + } + return factory.getUserInfoRestTemplate(); + } + + /*@LoadBalanced // 스프링 클라우드가 리본이 지원하는 RestTemplate 클래스 생성하도록 지시 + @Bean + public RestTemplate getRestTemplate() { + // return new RestTemplate(); + RestTemplate template = new RestTemplate(); + List interceptors = template.getInterceptors(); + + if (interceptors == null) { + template.setInterceptors(Collections.singletonList(new CustomContextInterceptor())); + } else { + interceptors.add(new CustomContextInterceptor()); + template.setInterceptors(interceptors); + } + return template; + }*/ + public static void main(String[] args) { SpringApplication.run(EventServiceApplication.class, args); } - } 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 new file mode 100644 index 0000000..f2ad1e0 --- /dev/null +++ b/event-service/src/main/java/com/assu/cloud/eventservice/client/MemberRestTemplateClient.java @@ -0,0 +1,40 @@ +package com.assu.cloud.eventservice.client; + +import com.assu.cloud.eventservice.config.CustomConfig; +import com.assu.cloud.eventservice.utils.CustomContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.stereotype.Component; + +@Component +public class MemberRestTemplateClient { + + private final OAuth2RestTemplate restTemplate; + private final CustomConfig customConfig; + + public MemberRestTemplateClient(OAuth2RestTemplate restTemplate, CustomConfig customConfig) { + this.restTemplate = restTemplate; + this.customConfig = customConfig; + } + + String URL_PREFIX = "/api/mb/member/"; // 회원 서비스의 주울 라우팅경로와 회원 클래스 주소 + + private static final Logger logger = LoggerFactory.getLogger(MemberRestTemplateClient.class); + + public String userInfo(String name) { + logger.debug("===== In Member Service.userInfo: {}", CustomContext.getCorrelationId()); + + ResponseEntity restExchange = + restTemplate.exchange( + //"http://" + customConfig.getServiceIdZuul() + URL_PREFIX + "userInfo/{name}", // http://localhost:5555/api/mb/member/userInfo/rinda + "http://localhost:5555/api/mb/member/userInfo/{name}", // http://localhost:5555/api/mb/member/userInfo/rinda + HttpMethod.GET, + null, String.class, name + ); + + 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 b415734..9bc63d8 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 @@ -10,7 +10,14 @@ public class CustomConfig { @Value("${your.name}") private String yourName; + @Value("${service.id.zuul}") + private String serviceIdZuul; + public String getYourName() { return yourName; } + + public String getServiceIdZuul() { + return serviceIdZuul; + } } 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 4c0d9c4..1b58a05 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 @@ -1,5 +1,6 @@ package com.assu.cloud.eventservice.controller; +import com.assu.cloud.eventservice.client.MemberRestTemplateClient; import com.assu.cloud.eventservice.client.MemberFeignClient; import com.assu.cloud.eventservice.config.CustomConfig; import org.springframework.web.bind.annotation.GetMapping; @@ -13,10 +14,12 @@ public class EventController { private final CustomConfig customConfig; private final MemberFeignClient memberFeignClient; + private final MemberRestTemplateClient memberRestTemplateClient; - public EventController(CustomConfig customConfig, MemberFeignClient memberFeignClient) { + public EventController(CustomConfig customConfig, MemberFeignClient memberFeignClient, MemberRestTemplateClient memberRestTemplateClient) { this.customConfig = customConfig; this.memberFeignClient = memberFeignClient; + this.memberRestTemplateClient = memberRestTemplateClient; } @GetMapping(value = "name/{nick}") @@ -45,4 +48,9 @@ public class EventController { public String gift(@PathVariable("name") String gift) { return "[EVENT] Gift is " + gift; } + + @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/ResourceServerConfig.java b/event-service/src/main/java/com/assu/cloud/eventservice/security/ResourceServerConfig.java new file mode 100644 index 0000000..388a085 --- /dev/null +++ b/event-service/src/main/java/com/assu/cloud/eventservice/security/ResourceServerConfig.java @@ -0,0 +1,33 @@ +package com.assu.cloud.eventservice.security; + +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; + +/** + * 접근 제어 규칙 정의 + * 인증된 사용자는 모든 서비스에 접근 가능하거나, + * 특정 역할을 가진 애플리케이션만 PUT URL 로 접근하는 등 세밀하기 정의 가능 + */ +@Configuration +public class ResourceServerConfig extends ResourceServerConfigurerAdapter { + /** + * 모든 접근 규칙을 재정의한 configure() + * @param http + * @throws Exception + */ + @Override + public void configure(HttpSecurity http) throws Exception { + // 매서드로 전달된 HttpSecurity 객체로 모든 접근 규칙 구성 + + // 회원 서비스의 모든 URL 에 대해 인증된 사용자만 접근하도록 제한 + //http.authorizeRequests().anyRequest().authenticated(); + + http.authorizeRequests() + .antMatchers(HttpMethod.PUT, "/member/**") // 쉼표로 구분하여 엔드 포인트 목록 받음 + .hasRole("ADMIN") // ADMIN 권한을 가진 사용자만 PUT 호출 가능 + .anyRequest() // 서비스의 모든 엔드포인트도 인증된 사용자만 접근 가능하도록 설정 + .authenticated(); + } +} 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 17b29be..58724d7 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 @@ -10,15 +10,15 @@ import org.springframework.stereotype.Component; public class CustomContext { public static final String CORRELATION_ID = "assu-correlation-id"; - private String correlationId = new String(); + private static final ThreadLocal correlationId = new ThreadLocal<>(); // 그 외 필요한 항목 넣을 수 있음 (인증 토큰 등...) - public String getCorrelationId() { - return correlationId; + public static String getCorrelationId() { + return correlationId.get(); } - public void setCorrelationId(String correlationId) { - this.correlationId = correlationId; + public static void setCorrelationId(String cid) { + correlationId.set(cid); } } diff --git a/member-service/src/main/java/com/assu/cloud/memberservice/client/EventRestTemplateClient.java b/member-service/src/main/java/com/assu/cloud/memberservice/client/EventRestTemplateClient.java index 37fa0b0..55a3567 100644 --- a/member-service/src/main/java/com/assu/cloud/memberservice/client/EventRestTemplateClient.java +++ b/member-service/src/main/java/com/assu/cloud/memberservice/client/EventRestTemplateClient.java @@ -9,8 +9,8 @@ import org.springframework.web.client.RestTemplate; @Component public class EventRestTemplateClient { - RestTemplate restTemplate; - CustomConfig customConfig; + private final RestTemplate restTemplate; + private final CustomConfig customConfig; public EventRestTemplateClient(RestTemplate restTemplate, CustomConfig customConfig) { this.restTemplate = restTemplate; diff --git a/member-service/src/main/java/com/assu/cloud/memberservice/controller/MemberController.java b/member-service/src/main/java/com/assu/cloud/memberservice/controller/MemberController.java index b62e89e..f8114e9 100644 --- a/member-service/src/main/java/com/assu/cloud/memberservice/controller/MemberController.java +++ b/member-service/src/main/java/com/assu/cloud/memberservice/controller/MemberController.java @@ -40,4 +40,12 @@ public class MemberController { public String member(@PathVariable("name") String name) { return "[MEMBER-DELETE] " + name + " is deleted."; } + + /** + * 이벤트 서비스에서 OAuth2 로 호출 테스트 + */ + @GetMapping("userInfo/{name}") + public String userInfo(@PathVariable("name") String name) { + return "[MEMBER] " + name; + } } diff --git a/member-service/src/main/java/com/assu/cloud/memberservice/utils/CustomContext.java b/member-service/src/main/java/com/assu/cloud/memberservice/utils/CustomContext.java index bcde9cd..bf58a2a 100644 --- a/member-service/src/main/java/com/assu/cloud/memberservice/utils/CustomContext.java +++ b/member-service/src/main/java/com/assu/cloud/memberservice/utils/CustomContext.java @@ -10,15 +10,15 @@ import org.springframework.stereotype.Component; public class CustomContext { public static final String CORRELATION_ID = "assu-correlation-id"; - private String correlationId = new String(); + private static final ThreadLocal correlationId = new ThreadLocal<>(); // 그 외 필요한 항목 넣을 수 있음 (인증 토큰 등...) - public String getCorrelationId() { - return correlationId; + public static String getCorrelationId() { + return correlationId.get(); } - public void setCorrelationId(String correlationId) { - this.correlationId = correlationId; + public static void setCorrelationId(String cid) { + correlationId.set(cid); } } diff --git a/zuulserver/src/main/java/com/assu/cloud/zuulserver/utils/CustomContext.java b/zuulserver/src/main/java/com/assu/cloud/zuulserver/utils/CustomContext.java index a5bb75f..caea8d1 100644 --- a/zuulserver/src/main/java/com/assu/cloud/zuulserver/utils/CustomContext.java +++ b/zuulserver/src/main/java/com/assu/cloud/zuulserver/utils/CustomContext.java @@ -10,15 +10,15 @@ import org.springframework.stereotype.Component; public class CustomContext { public static final String CORRELATION_ID = "assu-correlation-id"; - private String correlationId = new String(); + private static final ThreadLocal correlationId = new ThreadLocal<>(); // 그 외 필요한 항목 넣을 수 있음 (인증 토큰 등...) - public String getCorrelationId() { - return correlationId; + public static String getCorrelationId() { + return correlationId.get(); } - public void setCorrelationId(String correlationId) { - this.correlationId = correlationId; + public static void setCorrelationId(String cid) { + correlationId.set(cid); } }