OAuth(Open Authorization)
OAuth는 인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹 사이트 상의 자신들의 정보에 대한 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는, 접근 위임을 위한 개방형 표준다.
구글에 로그인하면 API를 통해 연동된 계정의 Google Calender 정보를 가져와 사용자에게 보여줄 수 있다.
이때 사용되는 프로토콜이 OAuth다.
더 잘 이해하기 위해 인증과 인가에 대한 개념을 구분해보자
Authentication(인증): 유저 본인을 확인하는 과정(로그인)
Authorization(인가): 인증 후 인가를 진행, 인가에 따라 유저 정보를 선택전으로 반환한다
파티에 초대된 손님을 예로 들어보면,
호스트가 파티에 입장하는 사람의 초대장을 확인하여 초대된 사람인지 확인하는 과정 = 인증
파티에서 접근할 수 있는 공간에 대해 접근을 허용하는 것 = 인가
추가적인 정보
OIDC
인증(로그인)만 하고 끝
OIDC인증은 그 자체로 비대칭키 보안이 적용된 인증만 완료하고 끝
반면, OAuth는 회원가입을 위한 유저 정보를 유저로붙터 받고, 인가 정보(선택정 유저 정보)를 조회가능하여 확장된 기능을 제공
OAuth 흐름
클라이언트가 구글 로그인을 하기 위해 화면에서 구글 로그인 클릭
- 요청이 Service Server에 전달되면 Service Server에서 인증서버(Authorization Server)에게 인가 코드를 요청한다.
- 인증서버는 클라이언트엑 인증을 요청 -> 클라이언트에 아직 유효한 계정 세션이 있는지 확인(있으면 3번 과정 건너뛴다)
- 로그인을 위한 정보 입력 후 요청 하면 인증 서버에서 로그인 정보를 바탕으로 인증을 시도
- 인증이 되면 사용자 정보 수집을 위한 동의 화면이 뜬다
- 동의하는 요청이 가면 토큰을 발급받기 위한 인가코드를 Service Server에게 전달한다(각 서비스에 redirect url을 등록하는데, 여기서 Service Server를 의미한다)
- Service Server는 전달받은 토큰을 가지고 사용자 정보를 가지고 있는 서버(여기서는 카카오 서버)에게 토큰과 함께 요청을 보낸다.
- 사용자의 정보를 조회해서 Service Server에 전달
- 사용자의 정보를 바탕으로 Service Server에서 회원가입을 진행하거나 로그인 진행
OAuth의 구성요소
Client: 우리가 사용하는 서비스(개발자가 개발하는 서버)
Resource Owner: 웹 서비스를 사용하려는 유저, 자원(개인정보)를 소유하는 자, 사용자
Authorization Server
- 자격증명 후 Authorization Code 반환
- Authorization Code 로 인가를 위한 Token 발행
- 사용자는 이 서버로 개인정보를 넘겨 인가코드를 받을 수 있다.
- Client는 이 서버로 인가코드를 넘겨 Token을 발급
Resource Server: 사용자의 정보를 가지고 있는 서버 (카카오 서버)
- Client는 Token을 이 서버로 넘겨 개인정보를 응답 받을 수 있다.
위 내용을 바탕으로 OAuth 사용해보기
OpenID Connect | Authentication | Google for Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. 의견 보내기 OpenID Connect 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Google의 OAuth 2.0 API는 인증과 승
developers.google.com
구글에서 제공해주는 OAuth 2.0 Playground에서 실행해보면서 감을 잡을 수 있다.
Github 에서 코드를 확인할 수 있다.
먼저 서비스 클래스에 OAuth를 위한 과정에 필요한 요청 URL을 정적 리터럴로 선언해놓았다.
private static final String authUrl = "https://accounts.google.com/o/oauth2/v2/auth";
private static final String tokenUrl = "https://oauth2.googleapis.com/token";
private static final String userInfoUrl = "https://www.googleapis.com/oauth2/v2/userinfo";
private static final String redirectUrl = "http://localhost:8080/login/oauth2/code/google";
- authUrl: 인가코드를 받기위한 url
- tokenUrl: 토큰을 발급받기 위한 url
- userInfoUrl: 유저 정보를 요청하기 위한 url
- redirectUrl: 구글에 등록한 code가 반환되는 리다이렉트 url (인가코드를 발급받기 위해 Redirect Url을 요청에 포함해야한다.) -> 컨트롤러에서 사용된다.
1. 인증 코드 가져오기
1. 구글 로그인을 하기 위한 컨트롤러 만들기
@GetMapping("/login/oauth2")
public void googleLogin(HttpServletResponse response) throws IOException {
String requestURL = customOAuthService.request();
response.sendRedirect(requestURL);
}
`/login/oauth2` 로 요청을 하면 구글 로그인 과정이 시작된다.
2. 리다이렉트하기 위한 요청 만들기(Authorization Server로부터 인가코드 받기 위한 요청)
public String request() {
return authServerRequest(); //google 인증 서버에 인증 코드 요청
}
public String authServerRequest() {
//google api와 통신하기 위한 요청 만들기(승인 코드를 받기 위한 요청) //
// authorizationCodeFlow.loadCredential(String);
Map<String, String> params = new HashMap<>();
params.put("client_id", clientId);
params.put("redirect_uri", redirectUrl);
params.put("response_type", "code");
params.put("scope", "email");
// params.put("state", createCSRFToken())
String parameterString=params.entrySet().stream()
.map(x->x.getKey()+"="+x.getValue())
.collect(Collectors.joining("&"));
String redirectURL=authUrl+"?"+parameterString;
log.info("redirect-URL={}", redirectURL);
return redirectURL;
}
요청에 대한 양식은 구글 공식 문서를 보면서 작성했다. 양식은 아래와 같다
https://accounts.google.com/o/oauth2/v2/auth?
scope=https%3A//www.googleapis.com/auth/drive.metadata.readonly%20https%3A//www.googleapis.com/auth/calendar.readonly&
access_type=offline&
include_granted_scopes=true&
response_type=code&
state=state_parameter_passthrough_value&
redirect_uri=https%3A//oauth2.example.com/code&
client_id=client_id
2. 인가를 위한 Token 받기
1. 구글에 등록한 Redirect Url로 인가 코드가 포함되어서 우리 서버에 다시 날라온다 -> 이걸 컨틀롤러로 잡아서 인가코드 얻기
@GetMapping("/login/oauth2/code/google")
public ResponseEntity<String> callback(@RequestParam(name = "code") String code) throws JsonProcessingException {
customOAuthService.oAuthLogin(code);
return new ResponseEntity<>("ok", HttpStatus.OK);
}
요청 매핑을 리다이렉트 경로와 동일하게 작성 -> http://localhost:8080/login/oauth2/code/google
2. 인가 코드를 가지고 Token요청하기
public void oAuthLogin(String code) throws JsonProcessingException {
ResponseEntity<String> response = requestAccessToken(code);
GoogleOAuthResponse oAuthResponse = getAccessToken(response);
log.info("accessToken: {}", oAuthResponse.getAccessToken());
// GoogleOAuthResponse googleOAuthToken =socialOauth.getAccessToken(accessToken);
//
ResponseEntity<String> userInfoResponse=requestUserInfo(oAuthResponse.getAccessToken());
//
GoogleUser googleUser = getUserInfo(userInfoResponse);
//
String user_id = googleUser.getEmail();
//
log.info("login user email: {}", user_id);
// return new GetSocialOAuthRes("1234",1,"asdf", googleOAuthToken.getToken_type());
}
- requestAccessToken(code): 인가코드를 가지고 Token이 포함된 요청 반환
- getAccessToken(response): response에서 토큰 파싱
- requestUserInfo(token): token을 가지고 유저 정보 가져오기 (Resource Server에서 인가받는 과정)
- getUserInfo(userInfoResponse): Resource Server에서 가져온 유저 정보를 서비스에서 사용하는 유저 정보에 맞게 변환
RequestAccessToken
public ResponseEntity<String> requestAccessToken(String code) {
RestTemplate restTemplate=new RestTemplate();
Map<String, Object> params = new HashMap<>();
params.put("code", code);
params.put("client_id", clientId);
params.put("client_secret", clientSecret);
params.put("redirect_uri", redirectUrl);
params.put("grant_type", "authorization_code");
ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(tokenUrl, params, String.class);
System.out.println(stringResponseEntity.toString());
return stringResponseEntity;
}
요청에 대한 형식은 구글 문서에 있다
GetAccessToken
private GoogleOAuthResponse getAccessToken(ResponseEntity<String> response) throws JsonProcessingException {
log.info("accessTokenBody: {}",response.getBody());
return objectMapper.readValue(response.getBody(), GoogleOAuthResponse.class);
}
resonse.getBody(): 여기에 Token이 존재한다.
GoogleOAuthResponse객체는 ObjectMapper를 사용해서 response를 GoogleOAuthResponse 객체로 역직렬화하기 위함.
GoogleOAythResponse
@AllArgsConstructor
@Getter
public class GoogleOAuthResponse {
@JsonProperty("access_token")
String accessToken;
@JsonProperty("expires_in")
String expiresIn;
String scope;
@JsonProperty("token_type")
String tokenType;
@JsonProperty("id_token")
String idToken;
}
RequestUserInfo
private ResponseEntity<String> requestUserInfo(String token) {
HttpHeaders headers = new HttpHeaders();
HttpEntity<MultiValueMap<String,String>> request = new HttpEntity<>(headers);
headers.add("Authorization","Bearer "+ token);
ResponseEntity<String> exchange = restTemplate.exchange(userInfoUrl, HttpMethod.GET, request, String.class);
System.out.println(exchange.getBody());
return exchange;
}
이 메서드를 통해서 Resource Server에서 유저 정보를 받아온다
GetUserInfo
private GoogleUser getUserInfo(ResponseEntity<String> response) throws JsonProcessingException {
log.info("Response Body: {}",response.getBody());
return objectMapper.readValue(response.getBody(), GoogleUser.class);
}
responseBody를 객체로 역직렬화하는 과정
다음에는 이 과정을 Spring Security 를 사용하여 간편화할 것이다.
관련 포스팅
'Spring > Spring Security' 카테고리의 다른 글
[OAuth] 구글 로그인 구현하기(2) - 필터와 인터셉터 차이와 SpringFilterChain (0) | 2025.05.19 |
---|---|
Spring Security를 사용해서 회원가입, 로그인 구현하기 - 자체회원가입, oauth 활용(0) (0) | 2024.04.29 |
Spring Security를 사용해서 로그인 구현하기 - JWT(2) (0) | 2024.04.29 |
Spring Security를 사용해서 로그인 구현하기 - JWT(1) (1) | 2024.04.28 |
Spring Security를 사용해서 로그인 구현하기 - JWT와 세션(0) (0) | 2024.04.27 |