Google OAuth2 연동 방법
OAuth 2.0
OAuth 2.0은 권한 부여를 위한 개방형 표준 인증 프로토콜이다.
리소스를 소유하고 있는 사용자 대신 Application에 리소스에 접근할 수 있는 권한을 위임한다.
Flow
Google OAuth2 설정
상단의 '콘솔' 클릭
API 및 서비스 클릭
OAuth 동의 화면 클릭
- OAuth 동의 화면: 앱이름, 사용자 지원 이메일, 개발자 연락처 정보 등 필요한 정보 입력 > 저장 후 계속
- 범위: 테스트를 위해 이메일 주소, 개인정보 보기 2개 선택 > 저장 후 계속
테스트 사용자
- ADD USERS > 테스트 할 본인 이메일 주속 입력 > 저장 후 계속
사용자 인증 정보 클릭
- 상단의 + 사용자 인증 정보 만들기 클릭 > OAuth 클라이언트 ID 클릭 > 애플리케이션 유형 선택 (ex. 웹 애플리케이션) & 이름 입력 (자유) & 승인된 리디렉션 URI 추가 (사용자가 Google 에서 인증 받은 후 이 경로로 리디렉션됨, ex) http://localhost:8082/login/oauth2/code/google) > 만들기 버튼 클릭 > OAuth 클라이언트 생성됨 (클라이언트 ID 와 보안 비밀번호 를 Copy 또는 JSON 다운로드하여 보관)
Application 개발
Spring Boot > dependency 에 Security > OAuth2 Client 선택, Web > Spring Web 선택, Developer Tools > Lombok 선택
application.yaml 설정
spring:
profiles:
active: dev
server:
port: 8082
oauth2:
google:
client-id: Google OAuth 설정에서 발급받은 클라이언트 ID 입력
client-secret: Google OAuth 설정에서 발급받은 클라이언트 비밀번호 입력
redirect-uri: Google OAuth 설정에서 리디렉트 URI 입력
token-uri: https://oauth2.googleapis.com/token
resource-uri: https://www.googleapis.com/oauth2/v2/userinfo
kakao:
client-id:
client-secret:
redirect-uri:
token-uri:
resource-uri:
naver:
client-id:
client-secret:
redirect-uri:
token-uri:
resource-uri:
Test Controller 생성
@RestController
@RequestMapping(value = "/login/oauth2", produces = "application/json")
@RequiredArgsConstructor @Slf4j
public class LoginCtrl {
final LoginService loginService;
@GetMapping("/code/{company}")
public void googleLogin(@RequestParam String code, @PathVariable String company) {
loginService.googleLogin(code, company);
}
}
Test Service 생성
@Service @Slf4j @RequiredArgsConstructor
public class LoginService {
private final Environment env;
private final RestTemplate restTemplate;
public void googleLogin(String code, String company) {
log.info("where: {}, code: {}", company, code);
String accessToken = getAccessToken(code, registrationId);
log.info("access token: {}", accessToken);
JsonNode userResourceNode = getUserInfo(accessToken, registrationId);
if (userResourceNode == null) {
log.error("Can not get user resource with access-token: {}", accessToken);
return;
}
log.info("userResourceNode: {}", userResourceNode);
}
private String getAccessToken(String authorizationCode, String company) {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("code", authorizationCode);
params.add("client_id", env.getProperty("oauth2." + registrationId + ".client-id"));
params.add("client_secret", env.getProperty("oauth2." + registrationId + ".client-secret"));
params.add("redirect_uri", env.getProperty("oauth2." + registrationId + ".redirect-uri"));
params.add("grant_type", "authorization_code");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);
assert tokenUri != null;
ResponseEntity<JsonNode> responseNode = restTemplate.exchange(tokenUri, HttpMethod.POST, entity, JsonNode.class);
JsonNode accessTokenNode = responseNode.getBody();
return accessTokenNode.get("access_token").asText();
}
private JsonNode getUserInfo(String accessToken, String registrationId) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + accessToken);
HttpEntity<JsonNode> request = new HttpEntity<>(headers);
return restTemplate.exchange(Objects.requireNonNull(env.getProperty("oauth2." + registrationId + ".resource-uri")), HttpMethod.GET, request, JsonNode.class).getBody();
}
}
Application run 하고
Test
Browser 에서
아래와 같이 URL 호출 - {clientId}, {redirectUri} 는 위에서 발급받은 정보로 치환 - 하면
이렇게 google 인증 화면이 나오고 로그인을 정상적으로 하면
위에서 설정한 redirect-uri 가 호출된다. 즉 내가 만든 application 으로 authorizationCode 가 전달된다.
authorizationCode 와 초기에 발급받은 클라이언트 ID, 클라이언트 패스워드 를 이용해서
Access token 을 받고 Access token 을 이용해서 원하는 리소스 정보를 get 한다.
로그인한 사용자의 ID, 이름, nick-name, email 등의 공개된 리소스 정보를 얻을 수 있다.
위와 같이 얻어온 정보로 SSO 처리를 하거나 필요한 작업을 수행하면 되는데
처음 authorizationCode 가 유출되거나 탈취될 경우 공격자가 원하는 redirect uri 로 로그인 정보(ID/PW)가 유출될 수 있기 때문에 별도의 보안 작업이 필요하다.
이 부분은 (PKCE) 다음 시간에 이와 관련하여 정리해 보기로 한다.