Skip to content

Commit

Permalink
[BE/#15] Feat : GitHub OAuth 2.0 구현
Browse files Browse the repository at this point in the history
- GitHub로 로그인하기 버튼을 누르면 사용자 정보를 요청하는 페이지가 나온다
- 사용자가 정보 제공하기(인증)를 수락하면 다시 메인 페이지로 돌아간다
- 토큰은 저장하지 않고 유저 정보는 저장한다
- 유저 정보가 중복을 저장되지 않도록 방어로직을 구현했다
  • Loading branch information
hanurii committed Jun 22, 2020
1 parent eca9598 commit a69f2c0
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 18 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class JwtService {
private final Key KEY = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());


public String makeJwt(String nickname, String name, String email) throws Exception {
public String makeJwt(Long id, String name, String avatarUrl) throws Exception {
Date expireTime = new Date();
expireTime.setTime(expireTime.getTime() + EXPIRE_TIME);

Expand All @@ -35,9 +35,9 @@ public String makeJwt(String nickname, String name, String email) throws Excepti

Map<String, Object> map= new HashMap<>();

map.put("nickname", nickname);
map.put("id", id);
map.put("name", name);
map.put("email", email);
map.put("avatarUrl", avatarUrl);

JwtBuilder builder = Jwts.builder().setHeader(headerMap)
.setClaims(map)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.List;

@Repository
Expand Down Expand Up @@ -59,6 +61,11 @@ public User findUserByUserId(Long userId) {
,userId);
}

public Boolean existUserByEmail(String email) {
String sql = "select exists(select * from user where email = ?) as success;";
return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> rs.getBoolean("success"), email);
}

public UserSummary findUserSummaryByIssueIdAndCommentId(Long issueId, Long commentId) {
return jdbcTemplate.queryForObject(
"SELECT user.id, user.name, user.avatar_url FROM user " +
Expand All @@ -70,4 +77,11 @@ public UserSummary findUserSummaryByIssueIdAndCommentId(Long issueId, Long comme
rs.getString("avatar_url"))
, issueId, commentId);
}

public void save(String name, String email, Long githubId, String avatarUrl) {
String sql =
"INSERT INTO user(name, email, github_id, created_date_time, avatar_url) " +
"VALUES(?, ?, ?, ?, ?)";
jdbcTemplate.update(sql,name, email, githubId, Timestamp.valueOf(LocalDateTime.now()), avatarUrl);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.codesquad.issuetracker.oauth.controller;

import com.codesquad.issuetracker.oauth.service.GitHubOauthService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.websocket.server.PathParam;
import java.io.IOException;

@RestController
public class GitHubController {
private static final Logger logger = LoggerFactory.getLogger(GitHubController.class);

public static final String REDIRECT_URL = "http://localhost:8080";

private final GitHubOauthService gitHubOauthService;

public GitHubController(GitHubOauthService gitHubOauthService) {
this.gitHubOauthService = gitHubOauthService;
}

@GetMapping("/login/oauth")
public void login(@PathParam("code") String code, HttpServletRequest request, HttpServletResponse response) throws IOException {
logger.info("##### code: {}", code);
gitHubOauthService.login( request, response, REDIRECT_URL, code);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.codesquad.issuetracker.oauth.domain;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.stereotype.Component;

@Component
public class GitHubTokenInfo {
@JsonProperty("access_token")
private String accessToken;
@JsonProperty("token_type")
private String tokenType;
@JsonProperty("scope")
private String scope;

public String getAccessToken() {
return accessToken;
}

public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}

public String getTokenType() {
return tokenType;
}

public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}

public String getScope() {
return scope;
}

public void setScope(String scope) {
this.scope = scope;
}

public String getAuthorization() {
return this.getTokenType() + " : " + this.getAccessToken();
}

@Override
public String toString() {
return "AccessTokenResponseSuccess{" +
"accessToken='" + accessToken + '\'' +
", tokenType='" + tokenType + '\'' +
", scope='" + scope + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.codesquad.issuetracker.oauth.service;

import com.codesquad.issuetracker.oauth.domain.GitHubTokenInfo;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public interface GitHubOauthService {

GitHubTokenInfo getAccessToken(String code);

void login(HttpServletRequest request, HttpServletResponse response, String url, String authorizationCode) throws IOException;

void getUserData(HttpServletRequest request, HttpServletResponse response, String url, String accessToken);

void sendUserCookies(HttpServletResponse response, String jwt, String url) throws IOException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.codesquad.issuetracker.oauth.service;

import com.codesquad.issuetracker.config.jwt.hamill.JwtService;
import com.codesquad.issuetracker.hamill.domain.User;
import com.codesquad.issuetracker.hamill.service.UserService_Hamill;
import com.codesquad.issuetracker.oauth.dao.TokenDao;

import com.codesquad.issuetracker.oauth.domain.GitHubTokenInfo;
import com.codesquad.issuetracker.oauth.domain.Token;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;


@Service
public class GitHubOauthServiceImpl implements GitHubOauthService {

private static final Logger logger = LoggerFactory.getLogger(GitHubOauthServiceImpl.class);

public static final String GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
public static final String GITHUB_USER_INFO_ACCESS_URL = "https://api.github.com/user";

private final TokenDao tokenDao;
private final UserService_Hamill userService_hamill;
private final JwtService jwtService;

@Value("${github.client_id}")
private String clientId;
@Value("${github.client_secret}")
private String clientSecret;

public GitHubOauthServiceImpl(TokenDao tokenDao, UserService_Hamill userService_hamill, JwtService jwtService) {
this.tokenDao = tokenDao;
this.userService_hamill = userService_hamill;
this.jwtService = jwtService;
}

public GitHubTokenInfo getAccessToken(String code) {
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
Map<String, String> header = new HashMap<>();
header.put("Accept", "application/json");
headers.setAll(header);

MultiValueMap<String, String> bodies = new LinkedMultiValueMap<>();
Map<String, String> body = new HashMap<>();
body.put("client_id", clientId);
body.put("client_secret", clientSecret);
body.put("code", code);
bodies.setAll(body);

HttpEntity<?> request = new HttpEntity<>(bodies, headers);
ResponseEntity<?> response = new RestTemplate().postForEntity(GITHUB_ACCESS_TOKEN_URL, request, GitHubTokenInfo.class);
return (GitHubTokenInfo) response.getBody();
}

@Override
public void login(HttpServletRequest request, HttpServletResponse response, String url, String authorizationCode) throws IOException {
GitHubTokenInfo gitHubTokenInfo = getAccessToken(authorizationCode);
logger.info("##### Access Token Type: {}, Access Token: {}",gitHubTokenInfo.getTokenType(), gitHubTokenInfo.getAccessToken());
this.getUserData(request, response, url, gitHubTokenInfo.getAccessToken());
}

public void getUserData(HttpServletRequest request, HttpServletResponse response, String url, String accessToken) {
try {
RestTemplate restTemplate = new RestTemplate();

HttpHeaders header = new HttpHeaders();
HttpEntity<?> entity = new HttpEntity<>(header);

UriComponents sendAccessTokenUrl = UriComponentsBuilder.fromHttpUrl(
GITHUB_USER_INFO_ACCESS_URL + "?access_token=" + accessToken).build();

// 이 한줄의 코드로 API를 호출해 MAP 타입으로 전달 받는다
ResponseEntity<Map> resultMap = restTemplate.exchange(sendAccessTokenUrl.toString(), HttpMethod.GET, entity, Map.class);

// 유저 정보가 없을 때만 유저 정보를 저장한다
if (!userService_hamill.existUserByEmail(Objects.requireNonNull(resultMap.getBody()).get("email").toString())) {
userService_hamill.save(
(Objects.requireNonNull(resultMap.getBody())).get("name").toString(),
resultMap.getBody().get("email").toString(),
Long.parseLong(resultMap.getBody().get("id").toString()),
resultMap.getBody().get("avatar_url").toString());
}

// 필요한 정보를 담아 JWT 토큰을 만든다
String jwt = jwtService.makeJwt(
Long.parseLong(resultMap.getBody().get("id").toString()),
resultMap.getBody().get("name").toString(),
resultMap.getBody().get("avatar_url").toString());
this.sendUserCookies(response, jwt, url);

} catch (HttpClientErrorException | HttpServerErrorException e) {
logger.info("##### HttpErrorException: {}", e.getMessage());
} catch (Exception e) {
logger.info("##### Exception: {}", e.getMessage());
}
}

public void sendUserCookies(HttpServletResponse response, String jwt, String url) throws IOException {
Cookie cookie = new Cookie("token", jwt);
response.addCookie(cookie);
response.sendRedirect(url);
}
}
4 changes: 2 additions & 2 deletions BE/src/main/resources/application-secret.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Hamill
github.client_id=c8a6d38c880c3ee02abe
github.client_secret=0ff79bb3b1a324b9f759eb70618ac14587212729
github.client_id=2963f5a20a4e0180d1f6
github.client_secret=2382bff998d2bd15f26750cfd2e44e98fa24d554
4 changes: 2 additions & 2 deletions BE/src/main/resources/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ CREATE TABLE IF NOT EXISTS user
(
id BIGINT AUTO_INCREMENT,
name VARCHAR(128),
email VARCHAR(128),
github_id BIGINT,
email VARCHAR(128) UNIQUE,
github_id BIGINT UNIQUE,
created_date_time DATETIME,
avatar_url VARCHAR(1024),
PRIMARY KEY (id)
Expand Down
15 changes: 15 additions & 0 deletions BE/src/main/resources/static/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Client</title>
</head>
<body>
<h3>로그인테스트</h3>
<script>
githubAuthorizationUrl = "https://github.com/login/oauth/authorize?client_id=2963f5a20a4e0180d1f6&scope=user%20public_repo";
</script>
<button type="button" onclick="location.href=githubAuthorizationUrl">Github으로 로그인하기</button>
</body>
</html>

0 comments on commit a69f2c0

Please sign in to comment.