결론 : code 재사용해서 포스트맨으로 테스트하면 에러남!!
구글이 POST라고 해서 POST 하면 안 된다. GET 맵핑!!
React + Spring boot 조합이지만 React 코드는 없다. 프론트가 하는 인가코드 받기를 백엔드에서도 다루었다. 이후 삭제할 예정
구현하면서 정말 너무 헷갈렸던 부분만 집중적으로 다루었다.(굵은 글씨, 빨간색 표시한 부분)
이번 목표는 인가코드 받고, 인가코드로 구글 Access Token 을 단순 출력하는 것까지 구현한다.
스프링시큐리티를 제외한 전체 구현은 출처 첫번째 블로그가 도움이 많이 되었습니다. 참고해주세요
1.공식문서
2.인가코드 받기
원래 클라이언트가 해야하는 작업이다. 백엔드에서 테스트를 하기 위해 임시로 구현하게 되었다. 만약 프론트와 작업하는데 안된다면 인가코드 받기를 직접 구현해보는 것을 추천한다. 나의 경우 이해도 빠르고 어디가 잘못됐는지 빠르게 알 수 있었다.
@RestController
@RequestMapping("/auth/oauth2")
@RequiredArgsConstructor
public class OAuthController {
private final OAuthService oAuthService;
@GetMapping("generate-uri")
public String test(){
return oAuthService.generate();
}
}
@Service
@RequiredArgsConstructor
public class OAuthService {
private final String GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
@Value("${oauth2.google.client-id}")
private String GOOGLE_CLIENT_ID;
@Value("${oauth2.google.client-secret}")
private String GOOGLE_CLIENT_SECRET;
@Value("${oauth2.google.redirect-uri}")
private String LOGIN_REDIRECT_URI;
public String generate() {
return "https://accounts.google.com/o/oauth2/v2/auth" + "?"
+ "client_id=" + GOOGLE_CLIENT_ID + "&"
+ "redirect_uri=" + LOGIN_REDIRECT_URI + "&"
+ "response_type=code&"
+ "scope=" + "email" + "&"
+ "access_type=offline&"
+ "prompt=consent";
}
}
포스트맨 출력 결과

포스트맨에서 받아보면 내가 구글에서 설정한 redirect_uri가 함께 만들어져서 나온다.
포스트맨에서 나온 url 에 접속하면 계정 선택 페이지가 나오고 로그인을 할 수 있다.
계정 선택을 하면 아까 설정한 '승인된 리디렉션 URI' 로 리디렉션 된다.

1. 승인된 리디렉션 URI 는 백엔드 URI 를 입력했다.
2. URI 는 구글 Access Token 을 받기 위한 URI로 설정해야한다. (코드로 나중에 설명)
3. 승인된 리디렉션 URI 주소는 한 개만 설정해야 redirect_uri_mismatch 오류가 안 난다. 한 개만 설정해야하는 건 필수가 아니고 새로고침 하면 정상적으로 되었는데 오류나는게 찜찜해서 다른 URI 는 모두 삭제했다.
백엔드의 yml, 클라이언트쪽 URI, 구글에 등록한 URI 모두 똑같이 맞췄다.
위에서 승인된 리디렉션 URI로 리디렉션 된다고 했다. 인가코드 받는 것만 구현하면 리디렉션 안되고 구글 Access Token 까지 받는 코드를 만들어야 리디렉션 되는 것을 볼 수 있다.
리디렉션되어서 구글 AccessToken 을 받게 되면 이런 형식으로 온다.
http://localhost:8080/auth/oauth2/code/google?code={코드들어옴}&scope=email 블라블라

주소가 저렇게 오는데 프론트에서 code={} 이 부분을 잘라서 백엔드에게 넘겨준다.
code 테스트 하겠다고 포스트맨에서 테스트하면 invalid_grant 에러 난다. code는 재사용할 수 없다. 포스트맨 사용X
4.구글에게서 엑세스 토큰 받기
사용자 정보를 얻기 위해서 인가코드를 구글에게 주는 작업을 해볼 것이다.
공식문서 4. code를 액세스 토큰 및 ID 토큰으로 교환 을 참고하자
프론트와 같이 한다면 code를 백엔드에게 주었을 것이다.

엔드포인트 https://oauth2.googleapis.com/token 가 필요하고 code, client_id, client_secret, redirect_uri, grant_type를 필수 파라미터로 보내야 하고 POST 요청을 보내야한다.
결론은 GET 요청으로 Controller 에서 맵핑해야한다. 필수 파라미터는 5개지만 Controller는 code만!!! 파라미터로 보내면 된다.(프론트에서 보내준 code)
공식문서에서 Content-Type: application/x-www-form-urlencoded 라고 되어있다.
결론은 Params 로 보내면 된다.
난 이게 더 헷갈렸는데 포스트맨(이거 쓰면 에러남, 코드 재사용 X)에서 body 로 Content-Type: application/x-www-form-urlencoded 체크해서 보내야하는지 고민했다.
@RestController
@RequestMapping("/auth/oauth2")
@RequiredArgsConstructor
public class OAuthController {
private final OAuthService oAuthService;
@GetMapping("/code/google")
public ResponseEntity<String> getGoogleAccessToken(@RequestParam("code") String accessToken){
return oAuthService.getGoogleAccessToken(accessToken);
}
}
구글에서 설정한 승인된 URI 와 똑같이 설정된 것을 볼 수 있다. 똑같이 설정해서 승인된 URI 로 구글 Access Token을 받을 수 있는 것이다.(아까 나중에 설명하겠다고 한게 이 부분이다.)
@Service
@RequiredArgsConstructor
public class OAuthService {
private final String GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
@Value("${oauth2.google.client-id}")
private String GOOGLE_CLIENT_ID;
@Value("${oauth2.google.client-secret}")
private String GOOGLE_CLIENT_SECRET;
@Value("${oauth2.google.redirect-uri}")
private String LOGIN_REDIRECT_URI;
public ResponseEntity<String> getGoogleAccessToken(String accessCode) {
RestTemplate restTemplate=new RestTemplate();
Map<String, String> params = new HashMap<>();
params.put("code", accessCode);
params.put("client_id", GOOGLE_CLIENT_ID);
params.put("client_secret", GOOGLE_CLIENT_SECRET);
params.put("redirect_uri", LOGIN_REDIRECT_URI);
params.put("grant_type", "authorization_code");
ResponseEntity<String> responseEntity=restTemplate.postForEntity(GOOGLE_TOKEN_URL, params,String.class);
if(responseEntity.getStatusCode() == HttpStatus.OK){
return responseEntity;
}
return null;
}
}
공식 문서는 post 요청을 보내고 5개의 파라미터가 필요하다고 했다. params.put 을 통해 5개의 파라미터를 넣고
restTemplate.postForEntity 를 통해 post 요청을 하는 것이다.
즉 GET 으로 요청하고 service 에 있는 코드를 통해 5개 파라미터를 담아 최종적으로 POST 요청을 하는 것이다.
처음부터 인가코드 받고 로그인하면 아래와 같이 나온다.

이후 id_token에 있는 유저정보를 꺼내와 내 db와 이메일을 확인하고 없다면 강제 회원가입 시키고 있다면 우리측(구글x) jwt 를 발급해주면 된다.
이 부분은 다음에 작성할 예정이다.
틀린 부분 있다면 댓글로 알려주시면 정말 감사하겠습니다.
출처
'Springboot' 카테고리의 다른 글
| 김영한 스프링 입문:섹션2 스프링 웹 개발 기초 정리 (1) | 2024.03.05 |
|---|---|
| String 을 LocalDate 바꾸는 법 (0) | 2024.01.20 |
| Spring boot github 잔디같은 기능을 프로젝트에 구현하자 (0) | 2024.01.04 |
| 스프링부트 서블릿 HttpservletResponse 500번 상태코드, 메세지 바꾸기 (0) | 2023.12.30 |
| 홍팍 스프링부트 6 (0) | 2023.08.03 |