From a97dd31f8a24dfe3f051e4d234619889e64f43de Mon Sep 17 00:00:00 2001 From: Jumong Date: Mon, 8 Jul 2024 20:36:27 +0900 Subject: [PATCH] 1. eureka server 8070 -> 8761. 2. gateway oauth2login + oauth2ResourceServer + MapReactiveUserDetailService implemented. from spring 3.2.x, need to implement MapReactiveUserDetailService by my own. ref: https://github.com/spring-projects/spring-boot/issues/37504#issuecomment-1826301655 --- .../src/main/resources/config/gateway.yml | 2 +- .../driver/src/main/resources/application.yml | 2 +- .../src/main/resources/application-test.yml | 2 +- .../src/main/resources/application.yml | 2 +- .../src/main/resources/application.yml | 2 +- backend/java/backend/gateway/pom.xml | 19 ++++- .../gateway/config/SecurityConfig.java | 70 ++++++++++++++++--- .../gateway/controller/AuthController.java | 54 ++++++++++++++ .../com/example/gateway/dto/AuthResponse.java | 14 ++++ .../service/CustomOAuthUserService.java | 43 ++++++++++++ .../src/main/resources/application.yaml | 12 +++- .../src/main/resources/application.yml | 2 +- .../src/main/resources/application.yml | 2 +- .../route/src/main/resources/application.yml | 2 +- .../user/src/main/resources/application.yml | 2 +- 15 files changed, 209 insertions(+), 21 deletions(-) create mode 100644 backend/java/backend/gateway/src/main/java/com/example/gateway/controller/AuthController.java create mode 100644 backend/java/backend/gateway/src/main/java/com/example/gateway/dto/AuthResponse.java create mode 100644 backend/java/backend/gateway/src/main/java/com/example/gateway/service/CustomOAuthUserService.java diff --git a/backend/java/backend/configserver/src/main/resources/config/gateway.yml b/backend/java/backend/configserver/src/main/resources/config/gateway.yml index b3899f7c..fb5f58e7 100644 --- a/backend/java/backend/configserver/src/main/resources/config/gateway.yml +++ b/backend/java/backend/configserver/src/main/resources/config/gateway.yml @@ -8,4 +8,4 @@ eureka: registerWithEureka: true fetchRegistry: true serviceUrl: - defaultZone: "http://localhost:8070/eureka/" \ No newline at end of file + defaultZone: "http://localhost:8761/eureka/" \ No newline at end of file diff --git a/backend/java/backend/driver/src/main/resources/application.yml b/backend/java/backend/driver/src/main/resources/application.yml index 890d9ee7..abced85d 100644 --- a/backend/java/backend/driver/src/main/resources/application.yml +++ b/backend/java/backend/driver/src/main/resources/application.yml @@ -55,7 +55,7 @@ eureka: fetchRegistry: true registerWithEureka: true serviceUrl: - defaultZone: http://localhost:8070/eureka/ + defaultZone: http://localhost:8761/eureka/ --- diff --git a/backend/java/backend/eats-order-service/eats-order-application/src/main/resources/application-test.yml b/backend/java/backend/eats-order-service/eats-order-application/src/main/resources/application-test.yml index 6d96127d..20c59f2e 100644 --- a/backend/java/backend/eats-order-service/eats-order-application/src/main/resources/application-test.yml +++ b/backend/java/backend/eats-order-service/eats-order-application/src/main/resources/application-test.yml @@ -78,7 +78,7 @@ retry-config: # fetchRegistry: true # registerWithEureka: true # serviceUrl: -# defaultZone: http://localhost:8070/eureka/ +# defaultZone: http://localhost:8761/eureka/ topic-names: payment-request-topic-name: payment-request diff --git a/backend/java/backend/eats-order-service/eats-order-application/src/main/resources/application.yml b/backend/java/backend/eats-order-service/eats-order-application/src/main/resources/application.yml index d2ae57fc..9a2f363d 100644 --- a/backend/java/backend/eats-order-service/eats-order-application/src/main/resources/application.yml +++ b/backend/java/backend/eats-order-service/eats-order-application/src/main/resources/application.yml @@ -71,7 +71,7 @@ retry-config: # fetchRegistry: true # registerWithEureka: true # serviceUrl: -# defaultZone: http://localhost:8070/eureka/ +# defaultZone: http://localhost:8761/eureka/ topic-names: payment-request-topic-name: payment-request diff --git a/backend/java/backend/eats-search/src/main/resources/application.yml b/backend/java/backend/eats-search/src/main/resources/application.yml index 8ad04817..e94f7cac 100644 --- a/backend/java/backend/eats-search/src/main/resources/application.yml +++ b/backend/java/backend/eats-search/src/main/resources/application.yml @@ -49,5 +49,5 @@ eureka: fetchRegistry: true registerWithEureka: true serviceUrl: - defaultZone: http://localhost:8070/eureka/ + defaultZone: http://localhost:8761/eureka/ diff --git a/backend/java/backend/gateway/pom.xml b/backend/java/backend/gateway/pom.xml index d3fea9ec..9f9d0235 100644 --- a/backend/java/backend/gateway/pom.xml +++ b/backend/java/backend/gateway/pom.xml @@ -39,12 +39,29 @@ spring-boot-starter-security - + + org.springframework.security + spring-security-oauth2-client + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + io.netty netty-all + + com.google.api-client + google-api-client + 1.32.1 + + + com.google.http-client + google-http-client-jackson + 1.29.2 + org.projectlombok lombok diff --git a/backend/java/backend/gateway/src/main/java/com/example/gateway/config/SecurityConfig.java b/backend/java/backend/gateway/src/main/java/com/example/gateway/config/SecurityConfig.java index 2ce27014..a7d28556 100644 --- a/backend/java/backend/gateway/src/main/java/com/example/gateway/config/SecurityConfig.java +++ b/backend/java/backend/gateway/src/main/java/com/example/gateway/config/SecurityConfig.java @@ -1,20 +1,31 @@ package com.example.gateway.config; +import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.reactive.CorsConfigurationSource; import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; +import reactor.core.publisher.Mono; import java.util.Arrays; import java.util.List; @Configuration @EnableWebFluxSecurity +@Slf4j public class SecurityConfig { @Bean @@ -38,18 +49,22 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) ).permitAll() .anyExchange().authenticated()) .httpBasic(Customizer.withDefaults()) - .formLogin(Customizer.withDefaults()) - .oauth2Login(Customizer.withDefaults()); -// .formLogin(formLogin -> formLogin -// .loginPage("/login") -// .usernameParameter("email") -// .passwordParameter("password") -// .failureUrl("/login?error") -// .successHandler(new RedirectServerAuthenticationSuccessHandler("/home")) -// ) + .oauth2Login(Customizer.withDefaults()) + .oauth2ResourceServer(oAuth2ResourceServerSpec -> oAuth2ResourceServerSpec + .jwt(jwtSpec -> jwtSpec.jwtAuthenticationConverter(grantedAuthoritiesExtractor()))); + + return http.build(); } + // from spring 3.2.2, need to implement MapReactiveUserDetailsService by my own. + // https://github.com/spring-projects/spring-boot/issues/38713#issuecomment-1852289101 + // https://github.com/spring-projects/spring-boot/issues/39096 + @Bean + MapReactiveUserDetailsService userDetailsService() { + UserDetails userDetails = User.withUsername("admin").password("admin").roles("ADMIN").build(); + return new MapReactiveUserDetailsService(List.of(userDetails)); + } @Bean CorsConfigurationSource corsConfigurationSource() { @@ -64,5 +79,42 @@ CorsConfigurationSource corsConfigurationSource() { return source; } + private Converter> grantedAuthoritiesExtractor() { + JwtAuthenticationConverter jwtAuthenticationConverter = + new JwtAuthenticationConverter(); + return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter); + } + + +// @Bean +// public GrantedAuthoritiesMapper userAuthoritiesMapper() { +// return (authorities) -> { +// Set mappedAuthorities = new HashSet<>(); +// +// authorities.forEach(authority -> { +// if (OidcUserAuthority.class.isInstance(authority)) { +// OidcUserAuthority oidcUserAuthority = (OidcUserAuthority) authority; +// +// OidcIdToken idToken = oidcUserAuthority.getIdToken(); +// OidcUserInfo userInfo = oidcUserAuthority.getUserInfo(); +// log.info("idToken: {}", idToken); +// log.info("userInfo: {}", userInfo); +// // Map the claims found in idToken and/or userInfo +// // to one or more GrantedAuthority's and add it to mappedAuthorities +// +// } else if (OAuth2UserAuthority.class.isInstance(authority)) { +// OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority) authority; +// +// Map userAttributes = oauth2UserAuthority.getAttributes(); +// +// // Map the attributes found in userAttributes +// // to one or more GrantedAuthority's and add it to mappedAuthorities +// +// } +// }); +// +// return mappedAuthorities; +// }; +// } } \ No newline at end of file diff --git a/backend/java/backend/gateway/src/main/java/com/example/gateway/controller/AuthController.java b/backend/java/backend/gateway/src/main/java/com/example/gateway/controller/AuthController.java new file mode 100644 index 00000000..04bf74d9 --- /dev/null +++ b/backend/java/backend/gateway/src/main/java/com/example/gateway/controller/AuthController.java @@ -0,0 +1,54 @@ +//package com.example.gateway.controller; +// +//import com.example.gateway.dto.AuthResponse; +//import com.google.api.client.auth.oauth2.TokenRequest; +//import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; +//import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; +//import com.google.api.client.http.javanet.NetHttpTransport; +//import com.google.api.client.json.jackson.JacksonFactory; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.http.ResponseEntity; +//import org.springframework.web.bind.annotation.PostMapping; +//import org.springframework.web.bind.annotation.RequestBody; +//import org.springframework.web.bind.annotation.RestController; +// +//import java.util.Collections; +// +//@RestController +//@Slf4j +//public class AuthController { +// @Value("${spring.security.oauth2.client.registration.google.client-id}") +// private String clientId; +// +// @PostMapping("/api/auth/google") +// public ResponseEntity authenticateGoogle(@RequestBody TokenRequest tokenRequest) { +// try { +// log.info("authenticate Google tokenRequest: {}", tokenRequest); +// GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(new NetHttpTransport(), new JacksonFactory()) +// .setAudience(Collections.singletonList(clientId)) +// .build(); +// +// GoogleIdToken idToken = verifier.verify(tokenRequest.toString()); +// if (idToken != null) { +// GoogleIdToken.Payload payload = idToken.getPayload(); +// +// String userId = payload.getSubject(); +// String email = payload.getEmail(); +// boolean emailVerified = Boolean.valueOf(payload.getEmailVerified()); +// String name = (String) payload.get("name"); +// String pictureUrl = (String) payload.get("picture"); +// +// // 여기서 사용자 정보를 데이터베이스에 저장하거나 업데이트할 수 있습니다. +// // 그리고 자체 JWT 토큰을 생성하여 반환할 수 있습니다. +// return ResponseEntity.ok(new AuthResponse(userId, email, name)); +// } else { +// return ResponseEntity.badRequest().body("Invalid ID token."); +// } +// } catch (Exception e) { +// return ResponseEntity.badRequest().body("Token verification failed: " + e.getMessage()); +// } +// } +// +// +//} \ No newline at end of file diff --git a/backend/java/backend/gateway/src/main/java/com/example/gateway/dto/AuthResponse.java b/backend/java/backend/gateway/src/main/java/com/example/gateway/dto/AuthResponse.java new file mode 100644 index 00000000..c2f084e9 --- /dev/null +++ b/backend/java/backend/gateway/src/main/java/com/example/gateway/dto/AuthResponse.java @@ -0,0 +1,14 @@ +package com.example.gateway.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; + +@Builder +@AllArgsConstructor +public class AuthResponse { + private String userId; + private String email; + private String name; + + // constructor, getters and setters +} \ No newline at end of file diff --git a/backend/java/backend/gateway/src/main/java/com/example/gateway/service/CustomOAuthUserService.java b/backend/java/backend/gateway/src/main/java/com/example/gateway/service/CustomOAuthUserService.java new file mode 100644 index 00000000..5176cc84 --- /dev/null +++ b/backend/java/backend/gateway/src/main/java/com/example/gateway/service/CustomOAuthUserService.java @@ -0,0 +1,43 @@ +//package com.example.gateway.service; +// +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +//import org.springframework.stereotype.Service; +// +//@Slf4j +//@Service +//@RequiredArgsConstructor +//public class CustomOAuthUserService implements OAuth2UserService { +// private final SocialMemberRepository socialMemberRepository; +// +// @Override +// public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { +// OAuth2UserService delegate = new DefaultOAuth2UserService(); +// OAuth2User oAuth2User = delegate.loadUser(userRequest); +// log.info("oauth2user = {}",oAuth2User); +// String email = oAuth2User.getAttribute("email"); +// String nickname = UUID.randomUUID().toString().substring(0,15); +// String password = "default"; +//// Role role = Role.ROLE_USER; +// +// +// Optional socialMember = socialMemberRepository.findByEmail(email); +// +// List authorities = new ArrayList(); +// +// if(socialMember.isEmpty()){ +// SocialMember savedSocialMember = SocialMember.createSocialMember(email, nickname); +// SaveMemberResponseDto savedResponse = socialMemberRepository.save(savedSocialMember); +// authorities.add(new SimpleGrantedAuthority("ROLE_FIRST_JOIN")); +// User savedUser = new User (String.valueOf(savedResponse.getId()),password,authorities); +// return new CustomUserDetails(String.valueOf(savedResponse.getId()),authorities,savedUser,oAuth2User.getAttributes()); +// } +// else{ +// authorities.add(new SimpleGrantedAuthority("ROLE_EXIST_USER")); +// User savedUser = new User (String.valueOf(socialMember.get().getUserId()),password,authorities); +// return new CustomUserDetails(savedUser.getUsername(),authorities,savedUser,oAuth2User.getAttributes()); +// } +// } +// +//} \ No newline at end of file diff --git a/backend/java/backend/gateway/src/main/resources/application.yaml b/backend/java/backend/gateway/src/main/resources/application.yaml index 2d7a8d33..bb268d49 100644 --- a/backend/java/backend/gateway/src/main/resources/application.yaml +++ b/backend/java/backend/gateway/src/main/resources/application.yaml @@ -18,8 +18,16 @@ spring: oauth2: client: registration: - github.client-id: ${GITHUB_CLIENT_ID} - github.client-secret: ${GITHUB_CLIENT_SECRET} + google: + client-id: ${GOOGLE_CLIENT_ID} + client-secret: ${GOOGLE_CLIENT_SECRET} + scope: + - email + - profile + resourceserver: + jwt: + jwk-set-uri: "https://www.googleapis.com/oauth2/v3/cert" + issuer-uri: "https://accounts.google.com" # kubernetes: # discovery: diff --git a/backend/java/backend/location-redis/src/main/resources/application.yml b/backend/java/backend/location-redis/src/main/resources/application.yml index 7008e2f9..bf3191dc 100644 --- a/backend/java/backend/location-redis/src/main/resources/application.yml +++ b/backend/java/backend/location-redis/src/main/resources/application.yml @@ -49,7 +49,7 @@ eureka: fetchRegistry: true registerWithEureka: true serviceUrl: - defaultZone: http://localhost:8070/eureka/ + defaultZone: http://localhost:8761/eureka/ #broker: diff --git a/backend/java/backend/monitoring/src/main/resources/application.yml b/backend/java/backend/monitoring/src/main/resources/application.yml index fba1b7d4..bbe0ac79 100644 --- a/backend/java/backend/monitoring/src/main/resources/application.yml +++ b/backend/java/backend/monitoring/src/main/resources/application.yml @@ -54,6 +54,6 @@ eureka: fetchRegistry: true registerWithEureka: true serviceUrl: - defaultZone: http://localhost:8070/eureka/ + defaultZone: http://localhost:8761/eureka/ diff --git a/backend/java/backend/route/src/main/resources/application.yml b/backend/java/backend/route/src/main/resources/application.yml index 2f043c44..90733cf9 100644 --- a/backend/java/backend/route/src/main/resources/application.yml +++ b/backend/java/backend/route/src/main/resources/application.yml @@ -46,5 +46,5 @@ eureka: fetchRegistry: true registerWithEureka: true serviceUrl: - defaultZone: http://localhost:8070/eureka/ + defaultZone: http://localhost:8761/eureka/ diff --git a/backend/java/backend/user/src/main/resources/application.yml b/backend/java/backend/user/src/main/resources/application.yml index 249d2bc6..a0e56a10 100644 --- a/backend/java/backend/user/src/main/resources/application.yml +++ b/backend/java/backend/user/src/main/resources/application.yml @@ -59,7 +59,7 @@ eureka: fetchRegistry: true registerWithEureka: true serviceUrl: - defaultZone: http://localhost:8070/eureka/ + defaultZone: http://localhost:8761/eureka/ #logging: # level: