From 7244ccfd8144d2eb3dc066459fb0cc1b3a8a3867 Mon Sep 17 00:00:00 2001 From: Jinwoo Lee Date: Fri, 14 Jul 2023 21:23:41 +0900 Subject: [PATCH 1/7] chore: add naver-login security path --- .../repl/gifthub/config/SecurityConfig.java | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/swmaestro/repl/gifthub/config/SecurityConfig.java b/src/main/java/org/swmaestro/repl/gifthub/config/SecurityConfig.java index bbe9611e..75ef1530 100644 --- a/src/main/java/org/swmaestro/repl/gifthub/config/SecurityConfig.java +++ b/src/main/java/org/swmaestro/repl/gifthub/config/SecurityConfig.java @@ -29,33 +29,33 @@ public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { httpSecurity.csrf(csrf -> csrf.disable()) - .authorizeHttpRequests(authorizeHttpRequests -> - authorizeHttpRequests.requestMatchers("/auth/sign-up", "/auth/sign-in", "/auth/kakao/**", - "/swagger-resources/**", "/swagger-ui/**", "/v3/api-docs/**", "/webjars/**", "/error").permitAll() - .anyRequest().authenticated()) - .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) - .exceptionHandling(exceptionHandling -> exceptionHandling - .accessDeniedHandler(new AccessDeniedHandler() { - @Override - public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException, IOException { - // 권한 문제가 발생했을 때 이 부분을 호출한다. - response.setStatus(403); - response.setCharacterEncoding("utf-8"); - response.setContentType("text/html; charset=UTF-8"); - response.getWriter().write("권한이 없습니다."); - } - }) - .authenticationEntryPoint(new AuthenticationEntryPoint() { - @Override - public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { - // 인증문제가 발생했을 때 이 부분을 호출한다. - response.setStatus(401); - response.setCharacterEncoding("utf-8"); - response.setContentType("text/html; charset=UTF-8"); - response.getWriter().write("인증되지 않은 사용자입니다."); - } - }) - ); + .authorizeHttpRequests(authorizeHttpRequests -> + authorizeHttpRequests.requestMatchers("/auth/sign-up", "/auth/sign-in", "/auth/kakao/**", "auth/naver-login", + "/swagger-resources/**", "/swagger-ui/**", "/v3/api-docs/**", "/webjars/**", "/error").permitAll() + .anyRequest().authenticated()) + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) + .exceptionHandling(exceptionHandling -> exceptionHandling + .accessDeniedHandler(new AccessDeniedHandler() { + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException, IOException { + // 권한 문제가 발생했을 때 이 부분을 호출한다. + response.setStatus(403); + response.setCharacterEncoding("utf-8"); + response.setContentType("text/html; charset=UTF-8"); + response.getWriter().write("권한이 없습니다."); + } + }) + .authenticationEntryPoint(new AuthenticationEntryPoint() { + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { + // 인증문제가 발생했을 때 이 부분을 호출한다. + response.setStatus(401); + response.setCharacterEncoding("utf-8"); + response.setContentType("text/html; charset=UTF-8"); + response.getWriter().write("인증되지 않은 사용자입니다."); + } + }) + ); return httpSecurity.build(); } From 607e6492c1229c4fbeb722d916f1896ffa77d10c Mon Sep 17 00:00:00 2001 From: Jinwoo Lee Date: Fri, 14 Jul 2023 21:28:43 +0900 Subject: [PATCH 2/7] feat: implement get_naver_oauth_url function --- .../gifthub/auth/service/NaverService.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/main/java/org/swmaestro/repl/gifthub/auth/service/NaverService.java diff --git a/src/main/java/org/swmaestro/repl/gifthub/auth/service/NaverService.java b/src/main/java/org/swmaestro/repl/gifthub/auth/service/NaverService.java new file mode 100644 index 00000000..a27c2373 --- /dev/null +++ b/src/main/java/org/swmaestro/repl/gifthub/auth/service/NaverService.java @@ -0,0 +1,46 @@ +package org.swmaestro.repl.gifthub.auth.service; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Service; +import org.springframework.web.util.UriComponentsBuilder; +import org.swmaestro.repl.gifthub.auth.repository.MemberRepository; + +@Service +@PropertySource("classpath:application.yml") +public class NaverService { + private final MemberService memberService; + private final MemberRepository memberRepository; + private final String clientId; + private final String state; + private final String responseType; + private final String authorizationUri; + private final String redirectUri; + + + public NaverService(MemberService memberService, + MemberRepository memberRepository, + @Value("${naver.client-id}") String clientId, + @Value("${naver.state}") String state, + @Value("${naver.response-type}") String responseType, + @Value("${naver.authorization-uri}") String authorizationUri, + @Value("${naver.redirect-uri}") String redirectUri) { + this.memberService = memberService; + this.memberRepository = memberRepository; + this.clientId = clientId; + this.state = state; + this.responseType = responseType; + this.authorizationUri = authorizationUri; + this.redirectUri = redirectUri; + } + + public String getAuthorizationUrl() { + return UriComponentsBuilder + .fromUriString(authorizationUri) + .queryParam("client_id", clientId) + .queryParam("response_type", responseType) + .queryParam("redirect_uri", redirectUri) + .queryParam("state", state) + .build().toString(); + } +} From 972f40a856105fc4d54492bea805da138e022a3b Mon Sep 17 00:00:00 2001 From: Jinwoo Lee Date: Fri, 14 Jul 2023 21:30:03 +0900 Subject: [PATCH 3/7] feat: implement naver-oauth request api --- .../repl/gifthub/auth/controller/AuthController.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/org/swmaestro/repl/gifthub/auth/controller/AuthController.java b/src/main/java/org/swmaestro/repl/gifthub/auth/controller/AuthController.java index e18eb0a6..28606205 100644 --- a/src/main/java/org/swmaestro/repl/gifthub/auth/controller/AuthController.java +++ b/src/main/java/org/swmaestro/repl/gifthub/auth/controller/AuthController.java @@ -2,6 +2,8 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import org.swmaestro.repl.gifthub.auth.dto.SignInDto; @@ -9,9 +11,12 @@ import org.swmaestro.repl.gifthub.auth.dto.TokenDto; import org.swmaestro.repl.gifthub.auth.service.AuthService; import org.swmaestro.repl.gifthub.auth.service.MemberService; +import org.swmaestro.repl.gifthub.auth.service.NaverService; import org.swmaestro.repl.gifthub.auth.service.RefreshTokenService; import org.swmaestro.repl.gifthub.util.JwtProvider; +import java.io.IOException; + @RestController @RequestMapping("/auth") @RequiredArgsConstructor @@ -21,6 +26,7 @@ public class AuthController { private final AuthService authService; private final RefreshTokenService refreshTokenService; private final JwtProvider jwtProvider; + private final NaverService naverService; @PostMapping("/sign-up") @Operation(summary = "회원가입 메서드", description = "사용자가 회원가입을 하기 위한 메서드입니다.") @@ -51,4 +57,10 @@ public TokenDto validateRefreshToken(@RequestHeader("Authorization") String refr refreshTokenService.storeRefreshToken(tokenDto, jwtProvider.getUsername(refreshToken)); return tokenDto; } + + @GetMapping("/naver-login") + @Operation(summary = "네이버 로그인 메서드", description = "네이버 로그인을 하기 위한 메서드입니다.") + public void naverLogin(HttpServletRequest request, HttpServletResponse response) throws IOException { + response.sendRedirect(naverService.getAuthorizationUrl()); + } } From 1be4085bfbd837c6b61c2cfd3621d8d564e15840 Mon Sep 17 00:00:00 2001 From: Jinwoo Lee Date: Sat, 15 Jul 2023 18:43:03 +0900 Subject: [PATCH 4/7] feat: implement NaverCallbackTest --- .../auth/controller/AuthControllerTest.java | 78 +++++++++++++------ 1 file changed, 54 insertions(+), 24 deletions(-) diff --git a/src/test/java/org/swmaestro/repl/gifthub/auth/controller/AuthControllerTest.java b/src/test/java/org/swmaestro/repl/gifthub/auth/controller/AuthControllerTest.java index 79f8d3f1..24ac922f 100644 --- a/src/test/java/org/swmaestro/repl/gifthub/auth/controller/AuthControllerTest.java +++ b/src/test/java/org/swmaestro/repl/gifthub/auth/controller/AuthControllerTest.java @@ -11,13 +11,16 @@ import org.swmaestro.repl.gifthub.auth.dto.SignInDto; import org.swmaestro.repl.gifthub.auth.dto.SignUpDto; import org.swmaestro.repl.gifthub.auth.dto.TokenDto; +import org.swmaestro.repl.gifthub.auth.entity.Member; import org.swmaestro.repl.gifthub.auth.service.AuthService; import org.swmaestro.repl.gifthub.auth.service.MemberService; +import org.swmaestro.repl.gifthub.auth.service.NaverService; import org.swmaestro.repl.gifthub.auth.service.RefreshTokenService; import org.swmaestro.repl.gifthub.util.JwtProvider; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -43,44 +46,47 @@ public class AuthControllerTest { @MockBean private JwtProvider jwtProvider; + @MockBean + private NaverService naverService; + @Test public void signUpTest() throws Exception { SignUpDto signUpDto = SignUpDto.builder() - .username("jinlee1703") - .password("abc123##") - .nickname("이진우") - .build(); + .username("jinlee1703") + .password("abc123##") + .nickname("이진우") + .build(); TokenDto tokenDto = TokenDto.builder() - .accessToken("myawesomejwt") - .refreshToken("myawesomejwt") - .build(); + .accessToken("myawesomejwt") + .refreshToken("myawesomejwt") + .build(); mockMvc.perform(post("/auth/sign-up") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(signUpDto))) - .andExpect(status().isOk()); + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(signUpDto))) + .andExpect(status().isOk()); } @Test public void signInTest() throws Exception { SignInDto loginDto = SignInDto.builder() - .username("jinlee1703") - .password("abc123##") - .build(); + .username("jinlee1703") + .password("abc123##") + .build(); TokenDto tokenDto = TokenDto.builder() - .accessToken("myawesomejwt") - .refreshToken("myawesomejwt") - .build(); + .accessToken("myawesomejwt") + .refreshToken("myawesomejwt") + .build(); when(authService.signIn(any(SignInDto.class))).thenReturn(tokenDto); mockMvc.perform(post("/auth/sign-up") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(loginDto))) - .andExpect(status().isOk()); + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(loginDto))) + .andExpect(status().isOk()); } @Test @@ -92,9 +98,9 @@ public void validateRefreshTokenTest() throws Exception { String username = "jinlee1703"; TokenDto tokenDto = TokenDto.builder() - .accessToken(newAccessToken) - .refreshToken(newRefreshToken) - .build(); + .accessToken(newAccessToken) + .refreshToken(newRefreshToken) + .build(); when(refreshTokenService.createNewAccessTokenByValidateRefreshToken(refreshToken)).thenReturn(null); when(refreshTokenService.createNewRefreshTokenByValidateRefreshToken(refreshToken)).thenReturn(null); @@ -102,7 +108,31 @@ public void validateRefreshTokenTest() throws Exception { mockMvc.perform(post("/auth/refresh") - .header("Authorization", refreshToken)) - .andExpect(status().isUnauthorized()); + .header("Authorization", refreshToken)) + .andExpect(status().isUnauthorized()); + } + + @Test + public void naverSignInCallbackTest() throws Exception { + String accesstoken = "myawesome_accesstoken"; + String code = "myawesome_code"; + String state = "myawesome_state"; + TokenDto token = TokenDto.builder() + .accessToken(accesstoken) + .refreshToken(accesstoken) + .build(); + Member member = Member.builder() + .username("jinlee1703@naver.com") + .nickname("이진우") + .build(); + + when(naverService.getNaverToken("token", code)).thenReturn(token); + when(naverService.getNaverUserByToken(token)).thenReturn(member); + + mockMvc.perform(get("/auth/sign-in/naver/callback") + .queryParam("code", code) + .queryParam("state", state) + .header("Authorization", "Bearer " + accesstoken)) + .andExpect(status().isOk()); } } From b4c46c10e60183cd32620fdfdd700f2dc2875a7e Mon Sep 17 00:00:00 2001 From: Jinwoo Lee Date: Sat, 15 Jul 2023 18:46:54 +0900 Subject: [PATCH 5/7] chore: add naver oauth path --- .../java/org/swmaestro/repl/gifthub/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/swmaestro/repl/gifthub/config/SecurityConfig.java b/src/main/java/org/swmaestro/repl/gifthub/config/SecurityConfig.java index 75ef1530..872b536b 100644 --- a/src/main/java/org/swmaestro/repl/gifthub/config/SecurityConfig.java +++ b/src/main/java/org/swmaestro/repl/gifthub/config/SecurityConfig.java @@ -30,7 +30,7 @@ public class SecurityConfig { public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { httpSecurity.csrf(csrf -> csrf.disable()) .authorizeHttpRequests(authorizeHttpRequests -> - authorizeHttpRequests.requestMatchers("/auth/sign-up", "/auth/sign-in", "/auth/kakao/**", "auth/naver-login", + authorizeHttpRequests.requestMatchers("/auth/sign-up", "/auth/sign-in", "/auth/kakao/**", "/auth/sign-in/**", "/swagger-resources/**", "/swagger-ui/**", "/v3/api-docs/**", "/webjars/**", "/error").permitAll() .anyRequest().authenticated()) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) From 6459bc4b758518f44a4f1ca74932f0a09842f2ec Mon Sep 17 00:00:00 2001 From: Jinwoo Lee Date: Sat, 15 Jul 2023 18:50:57 +0900 Subject: [PATCH 6/7] feat: implement naver-service for oauth --- .../gifthub/auth/service/NaverService.java | 99 ++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/swmaestro/repl/gifthub/auth/service/NaverService.java b/src/main/java/org/swmaestro/repl/gifthub/auth/service/NaverService.java index a27c2373..af6a9342 100644 --- a/src/main/java/org/swmaestro/repl/gifthub/auth/service/NaverService.java +++ b/src/main/java/org/swmaestro/repl/gifthub/auth/service/NaverService.java @@ -1,11 +1,21 @@ package org.swmaestro.repl.gifthub.auth.service; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Service; import org.springframework.web.util.UriComponentsBuilder; +import org.swmaestro.repl.gifthub.auth.dto.TokenDto; +import org.swmaestro.repl.gifthub.auth.entity.Member; import org.swmaestro.repl.gifthub.auth.repository.MemberRepository; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + @Service @PropertySource("classpath:application.yml") public class NaverService { @@ -16,6 +26,10 @@ public class NaverService { private final String responseType; private final String authorizationUri; private final String redirectUri; + private final String clientSecret; + private final String tokenUri; + private final String userInfoUri; + private final JsonParser parser = new JsonParser(); public NaverService(MemberService memberService, @@ -24,7 +38,10 @@ public NaverService(MemberService memberService, @Value("${naver.state}") String state, @Value("${naver.response-type}") String responseType, @Value("${naver.authorization-uri}") String authorizationUri, - @Value("${naver.redirect-uri}") String redirectUri) { + @Value("${naver.redirect-uri}") String redirectUri, + @Value("${naver.client-secret}") String clientSecret, + @Value("${naver.token-uri}") String tokenUri, + @Value("${naver.user-info-uri}") String userInfoUri) { this.memberService = memberService; this.memberRepository = memberRepository; this.clientId = clientId; @@ -32,6 +49,9 @@ public NaverService(MemberService memberService, this.responseType = responseType; this.authorizationUri = authorizationUri; this.redirectUri = redirectUri; + this.clientSecret = clientSecret; + this.tokenUri = tokenUri; + this.userInfoUri = userInfoUri; } public String getAuthorizationUrl() { @@ -43,4 +63,81 @@ public String getAuthorizationUrl() { .queryParam("state", state) .build().toString(); } + + public TokenDto getNaverToken(String type, String code) throws IOException { + URL url = new URL(UriComponentsBuilder + .fromUriString(tokenUri) + .queryParam("grant_type", "authorization_code") + .queryParam("client_id", clientId) + .queryParam("client_secret", clientSecret) + .queryParam("code", code) + .build() + .toString()); + + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + + int responseCode = con.getResponseCode(); + BufferedReader br; + + if (responseCode == 200) { // 정상 호출 + br = new BufferedReader(new InputStreamReader(con.getInputStream())); + } else { // 에러 발생 + br = new BufferedReader(new InputStreamReader(con.getErrorStream())); + } + + String inputLine; + StringBuffer response = new StringBuffer(); + while ((inputLine = br.readLine()) != null) { + response.append(inputLine); + } + + br.close(); + + JsonElement element = parser.parse(response.toString()); + + return TokenDto.builder() + .accessToken(element.getAsJsonObject().get("access_token").getAsString()) + .refreshToken(element.getAsJsonObject().get("refresh_token").getAsString()) + .build(); + } + + public Member getNaverUserByToken(TokenDto token) throws IOException { + String accessToken = token.getAccessToken(); + + URL url = new URL(userInfoUri); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + con.setRequestProperty("Authorization", "Bearer " + accessToken); + + int responseCode = con.getResponseCode(); + BufferedReader br; + + if (responseCode == 200) { // 정상 호출 + br = new BufferedReader(new InputStreamReader(con.getInputStream())); + } else { // 에러 발생 + br = new BufferedReader(new InputStreamReader(con.getErrorStream())); + } + + String inputLine; + StringBuffer response = new StringBuffer(); + while ((inputLine = br.readLine()) != null) { + response.append(inputLine); + } + + br.close(); + + JsonElement element = parser.parse(response.toString()); + + return Member.builder() + .username(element.getAsJsonObject().get("response").getAsJsonObject().get("email").getAsString()) + .nickname(element.getAsJsonObject().get("response").getAsJsonObject().get("nickname").getAsString()) + .build(); + } + + public void saveNaverUser(Member member) { + if (!memberService.isDuplicateUsername(member.getUsername())) { + memberRepository.save(member); + } + } } From 4427a8d6b9eb907545b15b15772b24f8ce3f11a7 Mon Sep 17 00:00:00 2001 From: Jinwoo Lee Date: Sat, 15 Jul 2023 18:51:26 +0900 Subject: [PATCH 7/7] feat: implement naver-oauth-callback api --- .../repl/gifthub/auth/controller/AuthController.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/swmaestro/repl/gifthub/auth/controller/AuthController.java b/src/main/java/org/swmaestro/repl/gifthub/auth/controller/AuthController.java index 28606205..89a50b5e 100644 --- a/src/main/java/org/swmaestro/repl/gifthub/auth/controller/AuthController.java +++ b/src/main/java/org/swmaestro/repl/gifthub/auth/controller/AuthController.java @@ -58,9 +58,18 @@ public TokenDto validateRefreshToken(@RequestHeader("Authorization") String refr return tokenDto; } - @GetMapping("/naver-login") + @GetMapping("/sign-in/naver") @Operation(summary = "네이버 로그인 메서드", description = "네이버 로그인을 하기 위한 메서드입니다.") public void naverLogin(HttpServletRequest request, HttpServletResponse response) throws IOException { response.sendRedirect(naverService.getAuthorizationUrl()); } + + @GetMapping("/sign-in/naver/callback") + @Operation(summary = "네이버 로그인 콜백 메서드", description = "네이버 로그인 콜백을 하기 위한 메서드입니다.") + public TokenDto naverCallback(@RequestParam String code, @RequestParam String state) throws IOException { + TokenDto token = naverService.getNaverToken("token", code); + naverService.saveNaverUser(naverService.getNaverUserByToken(token)); + return token; + } + }