본문 바로가기
우아한테크코스/모락 프로젝트

Github OAuth 로그인 구현하기

by 자바지기 2022. 7. 16.
반응형

mo-rak.com

OAuth 기능을 구현한 이유?

우리의 서비스에서 직접적으로 회원가입, 로그인 기능을 구현하여 회원 정보를 관리하고 사용하기보다는

이미 인증된 서비스에서 관리하는 회원 정보를 가져와 사용하기 위해서 OAuth 기능을 구현하였습니다.

 

진행 과정

깃허브 공식 문서

테스트에 사용된 코드는 깃허브에서 확인해볼 수 있습니다.

1. github settings -> Developer settings로 접근합니다.

2. New Github App을 클릭하여 새로운 Github App을 만듭니다.

3. App설정을 진행합니다.

다음과 같이 설정을 진행했습니다.

 

여기서 Callbak URL은 사용자가 깃허브 로그인 페이지에서 로그인에 성공을 하면 깃허브 측에서 이동 시켜줄 URL입니다.

 

4. 사용자의 github ID 요청하기

GET https://github.com/login/oauth/authorize?client_id={client_id}

client_id는 앱을 선택할 때 GitHub 앱 설정 에서 찾을 수 있습니다.

다음과 같이 url을 통해 접근하면 다음과 같은 화면을 볼 수 있습니다.

로그인을 진행하면 code와 함께 callback url로 redirection하게 됩니다.

callback url을 http://localhost:8080/callback로 설정했기 때문에 code와 함께

http://localhost:8080/callback?code={code} 로 이동하게 되었습니다.

이렇게 얻게 된 code를 access token과 교환해야합니다.

 

5. 사용자 정보 가져오기

이렇게 얻게 된 code를 이용해 access token을 얻고,

access token을 이용해 유저의 정보를 얻어와야합니다.

이 과정을 코드를 작성해보았습니다.

전체적인 코드는 다음과 같습니다. github oauth 로그인을 진행한 유저의 이름을 가져오는 로직을 간단하게 구현해보았습니다.

@GetMapping("/callback")
public ResponseEntity<String> getUserInfo(@RequestParam String code) {
    String accessToken = getAccessToken(code);
    String userName = getUserName(accessToken);
    return ResponseEntity.ok(userName);
}

private String getAccessToken(String code) {
    return restTemplate.postForObject(
            "https://github.com/login/oauth/access_token",
            new OAuthAccessTokenRequest(clientId, clientSecret, code),
            OAuthAccessTokenResponse.class
    ).getAccessToken();
}

public String getUserName(String accessToken) {
    HttpHeaders headers = new HttpHeaders();
    headers.setBearerAuth(accessToken);
    HttpEntity<Void> request = new HttpEntity<>(headers);

    return restTemplate.exchange(
            MEMBER_INFO_URL,
            HttpMethod.GET,
            request,
            OAuthMemberInfoResponse.class
    ).getBody().getName();
}

하나씩 자세하게 살펴보겠습니다.

@GetMapping("/callback")
public ResponseEntity<String> getUserInfo(@RequestParam String code) {
    String accessToken = getAccessToken(code);
    String userName = getUserName(accessToken);
    return ResponseEntity.ok(userName);
}

OAuth 로그인 시 정해준 callback url로 code와 함께 이동하므로

callback url로 정해준 /callback 으로 요청이 들어올 경우 요청을 처리하는 controller를 만들었습니다.
또한 @RequestParam으로 code를 받도록 하였습니다.

5 .1 access token 받아오기

유저의 정보를 얻기 위해서, 먼저 access token을 알아야합니다.

access token을 얻기 위해 callback을 통해 얻은 code를 이용해 github에게 다음과 같은 요청해야합니다.

POST https://github.com/login/oauth/access_token

요청의 body는 다음과 같은 정보들이 있어야합니다.

 

client_id string GitHub 앱의 클라이언트 ID
client_secret string GitHub 앱의 클라이언트 암호
code string 이전 요청에서 응답으로 받은 코드

 

요청에 성공하면 다음과 같은 형식의 응답을 받게 됩니다.

{
  "access_token": "ghu_16C7e42F292c6912E7710c838347Ae178B4a",
  "expires_in": 28800,
  "refresh_token": "ghr_1B4a2e77838347a7E420ce178F2E7c6912E169246c34E1ccbF66C46812d16D5B1A9Dc86A1498",
  "refresh_token_expires_in": 15811200,
  "scope": "",
  "token_type": "bearer"
}

 

따라서 다음과 같이 코드를 작성하여 서버에서 github쪽으로 요청을 보냈습니다.

String accessToken = getAccessToken(code);
private String getAccessToken(String code) {
    return restTemplate.postForObject(
            "https://github.com/login/oauth/access_token",
            new OAuthAccessTokenRequest(clientId, clientSecret, code),
            OAuthAccessTokenResponse.class
    ).getAccessToken();
}

위의 코드는 서버측에서 github로 요청을 보내고 access token을 받도록 구현하였습니다.

 

5.2 access token을 이용하여 사용자 정보 받아오기

앞의 요청으로 얻은 access token을 이용하여 github에 다시 요청을 보내, 유저의 정보를 가져와야합니다.

다음과 같은 요청을 통해 유저의 정보를 가져옵니다.

Authorization: token OAUTH-TOKEN
GET https://api.github.com/user

요청에 성공하면 다음과 같은 형태로 유저의 정보들을 받을 수 있습니다.

{
    "login": ,
    "id": ,
    "node_id": ,
    "avatar_url": ,
    "gravatar_id": "",
    "url": ,
    "html_url":,
    "followers_url": ,
    "following_url": ,
    "gists_url": ,
    "starred_url": ,
    "subscriptions_url": ,
    "organizations_url": ,
    "repos_url": ,
    "events_url": ,
    "received_events_url": ,
    "type": ,
    "site_admin": ,
    "name":,
    "company": ,
    "blog": ,
    "location":,
    "email": ,
    "hireable": ,
    "bio": ,
    "twitter_username": ,
    "public_repos": ,
    "public_gists": ,
    "followers": ,
    "following": ,
    "created_at": ,
    "updated_at":
}

 

따라서 다음과 같이 코드를 작성하였다. 유저의 정보들 중 원하는 정보인 이름만 받아오도록 구현하였습니다.

String userName = getUserName(accessToken);
public String getUserName(String accessToken) {
    HttpHeaders headers = new HttpHeaders();
    headers.setBearerAuth(accessToken);
    HttpEntity<Void> request = new HttpEntity<>(headers);

    return restTemplate.exchange(
            "https://api.github.com/user",
            HttpMethod.GET,
            request,
            OAuthMemberInfoResponse.class
    ).getBody().getName();
}
@NoArgsConstructor
@Getter
public class OAuthMemberInfoResponse {

    @JsonProperty("login")
    private String name;
}

결과적으로 user의 이름을 확인해보겠습니다.

return ResponseEntity.ok(userName);

 

코드를 실행한 결과 다음과 같은 응답을 받을 수 있었습니다.

 

github oauth 로그인 기능을 간단하게 알아보았습니다.

 

 

실제 프로젝트 구현 방법

 

위에서 보았던 flow는 다음과 같습니다.

1. 프론트 서버에서 깃허브로 로그인 요청을 보냅니다.

2. 깃허브 로그인 성공 시 깃허브에서 서버로 api 요청을 하게됩니다.

3. 서버가 깃허브와의 통신을 통해 유저의 정보를 가져옵니다.

 

이는 서버에서 모든 요청을 실행했기 때문에 순조롭게 진행될 수 있었습니다.

그러나 실제 프로젝트에서 이러한 방법으로 구현 시 에러가 발생했습니다. 

 

1. 프론트 서버에서 깃허브로 로그인 요청을 보냅니다.

2. 깃허브 로그인 성공 시 깃허브에서 백엔드 서버로 api 요청을 하게됩니다.

 

이 과정에서 에러가 발생합니다.

깃허브에서 백엔드 서버로 api 요청을 보내기 때문에 응답이 프론트 서버로 도달하지 않습니다.

 

따라서 실제 프로젝트에서의  flow는 다음과 같이 구현했습니다.

1. 프론트 서버에서 깃허브로 사용자의 로그인 요청을 보냅니다.

2. 깃허브 로그인 성공 시 프론트 서버로 callback 한다. 이때 프론트 서버에서 code를 얻게 됩니다.

3. 프론트 서버에서 백엔드 서버로 code를 보내 사용자 정보를 얻는 요청을 합니다.

4. 백엔드 서버에서 엑세스 토큰을 얻고, 사용자 정보를 얻습니다.

5. 백엔드 서버에서 사용자 정보를 프론트 서버로 응답합니다.

 

결론

1. 사용자의 github ID를 요청합니다.

2. 응답으로 code를 받습니다.

3. code를 통해 github로부터 access token을 요청합니다.

4. access token을 이용하여 사용자의 정보를 얻습니다.

 

 

 

 

모락 깃허브 : https://github.com/woowacourse-teams/2022-mo-rak

반응형

댓글