본문 바로가기
프로젝트/USports

[USports] Member OAuth with 프론트엔드

by JayAlex07 2024. 1. 11.

[USports] Member OAuth with 프론트엔드

 

OAuth with 프론트엔드

 

앞서 OAuth2 client를 가지고 간편 로그인을 구현했다

 

OAuth2 client를 가지고 구현을 했을 때에, http://주소/oauth2/authorization/kakao 를 a 태그에 넣었을 때 작동을 했다

  • 그리고 /success 로 redirect해서, 응답 값을 프론트에 보냈다

 

BUT, 우리 프론트엔드 개발자 분은 Next.js를 사용했고, 위 주소를 axios, get으로 하면, 잘 안 돌아간다고 했다

  • 프론트 측에도 로그인을 하는 로직이 따로 있다
  • http://주소/oauth2/authorization/kakao 를 redirection 없이 썼을 때는 응답값이 안 나오고
  • /success 로 redirect 하게 되면, 프론트에서 get 요청을 한 것이 아니라서, 로그인을 제대로 구현할 수 없다고 했다

img

 

위에 대한 설명

  • OAuth2 Client를 사용하면, 위 그림에서 인가 코드, OAuth Access Token 받기, 그리고 resource owner의 정보 받기를 모두 해준다
  • 모든 것이 완료되면, successHandler를 통해, 응답값과 성공을 한 후 redirect url로 보냈다
  • BUT 백엔드 쪽에서 알아서 url을 redirect를 할 때에, 프론트에서 짜 놓은 로그인 로직과 맞지 않아서 문제가 생겼던 것 같다

 

해결!

 

프론트에서 인가 코드를 받고, 그 인가 코드를 시작으로 유저 정보를 추출해 냈다

  • OAuth2 Client 관련된 것들은 모두 삭제하고, 따로 구현을 했다

 

WebClient를 사용하였다

그 전에 스프링에서 RestTemplate보다 WebClient를 사용하는 것을 권고했던 것이 생각 났다
특히, RestTemplate보다 더 최신 기능이라고 나와있다

  • 카카오 Access Token을 받아왔다
  • 카카오 Access Token을 가지고, resource owner의 정보를 카카오에서 받아왔다

 

 

Controller

  • GET 을 통해서, 프론트에서 인가 코드를 받으면, 파라미터에 적힌 인가 코드를 가지고 온다
  • 하나의 URL을 통해서 카카오 access token을 받아오고, 그 토큰을 통해서 유저 정보를 가지고 온다
@RestController
@RequestMapping("/login/oauth2/code")
@RequiredArgsConstructor
@Slf4j
public class OAuthController {

  private final OAuthService oAuthService;
  private final TokenProvider tokenProvider;
  private final NotificationService notificationService;
  private final CookieService cookieService;

  @GetMapping("/kakao")
  public ResponseEntity<MemberLogin.Response> kakaoLogin(
      @RequestParam("code") String code,
      HttpServletRequest httpServletRequest,
      HttpServletResponse httpServletResponse
  ) {

    // 프론트에서 받아온 인가 코드를 통해 카카오 token을 받는다
    KakaoToken kakaoToken = oAuthService.kakaoGetAccessToken(code);

    // 토큰을 가지고 유저 정보를 통해, 회원가입을 시키거나 로그인을 시킨다
    MemberResponse memberResponse = oAuthService.kakaoLogin(kakaoToken);

    notificationService.checkUnreadNotificationAndSetSession(memberResponse.getMemberId(), httpServletRequest);

    TokenDto tokenDto = tokenProvider.saveTokenInRedis(memberResponse.getEmail());
    cookieService.setCookieForLogin(httpServletResponse,tokenDto.getAccessToken());

    return ResponseEntity.ok(MemberLogin.Response.builder()
            .memberResponse(memberResponse)
            .tokenDto(tokenDto)
        .build());
  }

}

 

Service 프론트에서 받아온 인가 코드를 통해 카카오 token을 발급 받기

  • requestBody에 요청 값을 넣기 위해, MultiValueMap을 사용한다
    • 요청에 위에서 프론트에서 받아온 인가 코드도 있다
    • MultiValueMap은 스프링에서 제공하는 인터페이스다
    • 인터페이스 이름과 같이, 하나의 key에 여러 value를 넣을 수 있는 Map이다
      • value는 리스트로 저장이 된다
@Override
public KakaoToken kakaoGetAccessToken(String authorizationCode) {

  MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
  params.add("grant_type", "authorization_code")
  params.add("client_id", CLIENT_ID);
  params.add("redirect_uri", REDIRECT_URL);
  params.add("code", authorizationCode);
  params.add("client_secret", CLIENT_SECRET);

  WebClient webClient = WebClient.create(ACCESS_TOKEN_URI);
  String response = webClient.post()
      .uri(ACCESS_TOKEN_URI)
      .body(BodyInserters.fromFormData(params))
      .header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded;charset=utf-8")
      .retrieve()
      .bodyToMono(String.class)
      .block();

  // json 형태로 변환
  ObjectMapper objectMapper = new ObjectMapper();
  KakaoToken kakaoToken = null;

  try {
    kakaoToken = objectMapper.readValue(response, KakaoToken.class);

  } catch (JsonProcessingException e) {
      e.printStackTrace();
  }

  return kakaoToken;
}

 

Service, 유저 정보 가지고 오기

  • 헤더에만 카카오에서 받아온 access token을 받아오면 된다
private KakaoUserInfo getProfile(KakaoToken kakaoToken) {

  WebClient webClient = WebClient.create(USER_INFO_URI);
  String response = webClient.post()
      .uri(USER_INFO_URI)
      .header(HttpHeaders.AUTHORIZATION, "Bearer " + kakaoToken.getAccess_token())
      .header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded;charset=utf-8")
      .retrieve()
      .bodyToMono(String.class)
      .block();

  ObjectMapper objectMapper = new ObjectMapper();
  KakaoUserInfo kakaoUserInfo = null;

  try {
    kakaoUserInfo = objectMapper.readValue(response, KakaoUserInfo.class);

  } catch (JsonProcessingException e) {
    e.printStackTrace();
  }

  return kakaoUserInfo;
}

 

KakaoToken과 KakaoUserInfo

  • 카카오에서 응답값을 받으면, 그 응답값을 사용할 수 있어야 한다
  • ObjectMapper를 통해서 응답값을 각 클래스 안에다 저장을 했다
  • 여기서 중요한 것은 응답값이 JSON 형태일 텐데, Key와 클래스에 만든 변수명이 일치해야 한다

 

KakaoToken

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class KakaoToken {

  // 카카오에서 받아오는 access_token이라 응답값의 key와 같아야 한다
  private String token_type;
  private String access_token;
  private int expires_in;
  private String refresh_token;
  private int refresh_token_expires_in;
  private String scope;

}

 

KakaoUserInfo

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class KakaoUserInfo {

  public Long id;
  public String connected_at;
  public KakaoAccount kakao_account;
  public Properties properties;

  @Getter
  @Setter
  @AllArgsConstructor
  @NoArgsConstructor
  @Builder
  public static class KakaoAccount {
    public boolean profile_nickname_needs_agreement;

    public Profile profile;

    @Getter
    @Setter
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    public static class Profile {
      public String nickname;

    }

    public boolean has_email;
    public boolean email_needs_agreement;
    public boolean is_email_valid;
    public boolean is_email_verified;
    public String email;

    public boolean has_gender;
    public boolean gender_needs_agreement;
    public String gender;
  }

  @Getter
  @Setter
  @AllArgsConstructor
  @NoArgsConstructor
  @Builder
  public static class Properties {
    public String nickname;
  }
}

'프로젝트 > USports' 카테고리의 다른 글

[USports] HTTPS  (1) 2024.01.14
[USports] Spring AWS 배포  (0) 2024.01.14
[USports] Websocket 구현  (1) 2023.12.25
[USports] Websocket 소개  (0) 2023.12.18
[USports] Member OAuth2  (0) 2023.12.09