우아한테크코스 레벨3 미션인 팀 프로젝트 미션에서 구현한 구글 OAuth 기능이다.
https://github.com/woowacourse-teams/2022-momo
com/woowacourse/momo/auth/controller/OauthController.java
package com.woowacourse.momo.auth.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import lombok.RequiredArgsConstructor;
import com.woowacourse.momo.auth.service.OauthService;
import com.woowacourse.momo.auth.service.dto.response.LoginResponse;
import com.woowacourse.momo.auth.service.dto.response.OauthLinkResponse;
@RequiredArgsConstructor
@RequestMapping("/api/auth/oauth2/google")
@RestController
public class OauthController {
private final OauthService oauthService;
@GetMapping(value = "/login", params = {"redirectUrl"})
public ResponseEntity<OauthLinkResponse> access(@RequestParam String redirectUrl) {
OauthLinkResponse response = oauthService.generateAuthUrl(redirectUrl);
return ResponseEntity.ok(response);
}
}
구글 OAuth 를 구현하기 위한 controller 이다. 첫번째 메서드는 웹 사이트를 이용하는 사용자가 구글 OAuth 를 이용하여 로그인하고 싶다는 것을 알리는 메서드이다. 구글 로그인 페이지에 접속할 수 있도록 URL 을 반환한다.
com/woowacourse/momo/auth/service/OauthService.java
package com.woowacourse.momo.auth.service;
import static com.woowacourse.momo.global.exception.exception.ErrorCode.OAUTH_USERINFO_REQUEST_FAILED_BY_NON_2XX_STATUS;
import static com.woowacourse.momo.global.exception.exception.ErrorCode.OAUTH_USERINFO_REQUEST_FAILED_BY_NON_EXIST_BODY;
import java.util.Optional;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.RequiredArgsConstructor;
import com.woowacourse.momo.auth.service.dto.response.LoginResponse;
import com.woowacourse.momo.auth.service.dto.response.OauthLinkResponse;
import com.woowacourse.momo.auth.support.JwtTokenProvider;
import com.woowacourse.momo.auth.support.PasswordEncoder;
import com.woowacourse.momo.auth.support.google.GoogleConnector;
import com.woowacourse.momo.auth.support.google.GoogleProvider;
import com.woowacourse.momo.auth.support.google.dto.GoogleUserResponse;
import com.woowacourse.momo.global.exception.exception.MomoException;
import com.woowacourse.momo.member.domain.Member;
import com.woowacourse.momo.member.domain.MemberRepository;
@RequiredArgsConstructor
@Service
public class OauthService {
private final TokenService tokenService;
private final MemberRepository memberRepository;
private final JwtTokenProvider jwtTokenProvider;
private final PasswordEncoder passwordEncoder;
private final GoogleConnector oauthConnector;
private final GoogleProvider oauthProvider;
public OauthLinkResponse generateAuthUrl(String redirectUrl) {
String oauthLink = oauthProvider.generateAuthUrl(redirectUrl);
return new OauthLinkResponse(oauthLink);
}
}
서비스 계층에서는 단순하게 반환된 값을 전달하는 역할만을 한다.
com/woowacourse/momo/auth/support/google/GoogleProvider.java
package com.woowacourse.momo.auth.support.google;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import lombok.Getter;
@Getter
@Component
public class GoogleProvider {
private final String clientId;
private final String clientSecret;
private final String authUrl;
private final String accessTokenUrl;
private final String userInfoUrl;
private final String grantType;
private final String scope;
private final String temporaryPassword;
public GoogleProvider(@Value("${oauth2.google.client.id}") String clientId,
@Value("${oauth2.google.client.secret}") String clientSecret,
@Value("${oauth2.google.url.auth}") String authUrl,
@Value("${oauth2.google.url.token}") String accessTokenUrl,
@Value("${oauth2.google.url.userinfo}") String userInfoUrl,
@Value("${oauth2.google.grant-type}") String grantType,
@Value("${oauth2.google.scope}") String scope,
@Value("${oauth2.member.temporary-password}") String temporaryPassword) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.authUrl = authUrl;
this.accessTokenUrl = accessTokenUrl;
this.userInfoUrl = userInfoUrl;
this.grantType = grantType;
this.scope = scope;
this.temporaryPassword = temporaryPassword;
}
public String generateAuthUrl(String redirectUrl) {
Map<String, String> params = new HashMap<>();
params.put("scope", scope);
params.put("response_type", "code");
params.put("client_id", clientId);
params.put("redirect_uri", redirectUrl);
return authUrl + "?" + concatParams(params);
}
private String concatParams(Map<String, String> params) {
return params.entrySet()
.stream()
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining("&"));
}
}
GoogleProvider 객체에서는 미리 등록해둔 정보를 이용하여 사용자가 구글 로그인을 하기 위한 URL 을 만든다. 사용자는 이 URL 을 전달받아 화면에 표출하여 구글 로그인 화면에 접속할 수 있다.
resources/security/application-oauth2.yml
oauth2:
redirect-path: /auth/google
google:
client:
id: 755121207871-raacm7vn0v386ep86q22kl4s35cvg0io.apps.googleusercontent.com
secret: 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
url:
auth: <https://accounts.google.com/o/oauth2/auth>
token: <https://oauth2.googleapis.com/token>
userinfo: <https://www.googleapis.com/oauth2/v2/userinfo>
grant-type: authorization_code
scope: openid%20https://www.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/userinfo.profile
member.temporary-password: 4ey6d756ft7gye5g7a86
해당 정보는 외부에 노출되면 안되는 민감한 정보이므로 서브모듈을 이용하여 관리하고 있다.
com/woowacourse/momo/auth/controller/OauthController.java
package com.woowacourse.momo.auth.controller;
@RequiredArgsConstructor
@RequestMapping("/api/auth/oauth2/google")
@RestController
public class OauthController {
private final OauthService oauthService;
@GetMapping(value = "/login", params = {"code", "redirectUrl"})
public ResponseEntity<LoginResponse> login(@RequestParam String code, @RequestParam String redirectUrl) {
LoginResponse loginResponse = oauthService.requestAccessToken(code, redirectUrl);
return ResponseEntity.ok(loginResponse);
}
}
code
에는 Authorization Code
을 입력받는다.
com/woowacourse/momo/auth/service/OauthService.java