From ba7fb7b46ceab00675129e421cfdec1c27d9f050 Mon Sep 17 00:00:00 2001 From: thedevluffy Date: Fri, 26 Jun 2020 21:27:09 +0900 Subject: [PATCH] =?UTF-8?q?[#171]=20feat:=20auth=20=EA=B4=80=EB=A0=A8=20co?= =?UTF-8?q?ntroller=20=EB=A5=BC=20=EA=B5=AC=ED=98=84=ED=95=9C=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tecobrary-core-api/build.gradle | 2 + .../web/oauth/GithubOAuthController.java | 43 ++++++++++ .../tecobrary/web/oauth/jwt/JwtGenerator.java | 84 +++++++++++++++++++ .../web/oauth/service/GithubUserService.java | 44 ++++++++++ .../user/repository/UserRepository.java | 4 + .../web/github/dto/GithubApiResponseDto.java | 2 + 6 files changed, 179 insertions(+) create mode 100644 tecobrary-core-api/src/main/java/com/woowacourse/tecobrary/web/oauth/GithubOAuthController.java create mode 100644 tecobrary-core-api/src/main/java/com/woowacourse/tecobrary/web/oauth/jwt/JwtGenerator.java create mode 100644 tecobrary-core-api/src/main/java/com/woowacourse/tecobrary/web/oauth/service/GithubUserService.java diff --git a/tecobrary-core-api/build.gradle b/tecobrary-core-api/build.gradle index 067d220..9255d37 100644 --- a/tecobrary-core-api/build.gradle +++ b/tecobrary-core-api/build.gradle @@ -27,6 +27,8 @@ bootJar { dependencies { implementation project(':tecobrary-domain') implementation project(':tecobrary-external-service') + + implementation 'io.jsonwebtoken:jjwt:0.9.1' } task copyEnv(type: Copy) { diff --git a/tecobrary-core-api/src/main/java/com/woowacourse/tecobrary/web/oauth/GithubOAuthController.java b/tecobrary-core-api/src/main/java/com/woowacourse/tecobrary/web/oauth/GithubOAuthController.java new file mode 100644 index 0000000..9529f13 --- /dev/null +++ b/tecobrary-core-api/src/main/java/com/woowacourse/tecobrary/web/oauth/GithubOAuthController.java @@ -0,0 +1,43 @@ +package com.woowacourse.tecobrary.web.oauth; + +import com.woowacourse.tecobrary.common.dto.CoreApiResponse; +import com.woowacourse.tecobrary.web.github.api.GithubApiService; +import com.woowacourse.tecobrary.web.github.dto.GithubApiResponseDto; +import com.woowacourse.tecobrary.web.github.dto.UserJwtInfoDto; +import com.woowacourse.tecobrary.web.github.utils.GithubUserApiUrlBuilder; +import com.woowacourse.tecobrary.web.oauth.jwt.JwtGenerator; +import com.woowacourse.tecobrary.web.oauth.service.GithubUserService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.servlet.view.RedirectView; + +@Controller +@Slf4j +@CrossOrigin(origins = "*") +@RequiredArgsConstructor +public class GithubOAuthController { + + private final GithubUserApiUrlBuilder githubUserApiUrlBuilder; + private final GithubApiService githubApiService; + private final GithubUserService githubUserService; + private final JwtGenerator jwtGenerator; + + @GetMapping("/login/github/oauth2") + public RedirectView github() { + return new RedirectView(githubUserApiUrlBuilder.oauth()); + } + + @GetMapping("/login/tecobrary") + public CoreApiResponse userAuth(@RequestParam String code) { + String accessToken = githubApiService.getGithubAccessToken(code); + UserJwtInfoDto userJwtInfoDto = githubUserService.getUserByGithubInfo(accessToken); + return CoreApiResponse.success(GithubApiResponseDto.builder() + .user(userJwtInfoDto) + .token(jwtGenerator.generateToken(userJwtInfoDto)) + .build()); + } +} diff --git a/tecobrary-core-api/src/main/java/com/woowacourse/tecobrary/web/oauth/jwt/JwtGenerator.java b/tecobrary-core-api/src/main/java/com/woowacourse/tecobrary/web/oauth/jwt/JwtGenerator.java new file mode 100644 index 0000000..dd0c6d3 --- /dev/null +++ b/tecobrary-core-api/src/main/java/com/woowacourse/tecobrary/web/oauth/jwt/JwtGenerator.java @@ -0,0 +1,84 @@ +package com.woowacourse.tecobrary.web.oauth.jwt; + +import com.woowacourse.tecobrary.web.github.dto.UserJwtInfoDto; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; + +@Component +public class JwtGenerator { + + private static final long JWT_TOKEN_VALIDITY = 60 * 60 * 24 * 7; // one week + + private final String secret; + + public JwtGenerator(@Value("jwt.secret") String secret) { + this.secret = secret; + } + + public String generateToken(UserJwtInfoDto userJwtInfoVo) { + Map claims = new LinkedHashMap<>(); + claims.put("id", userJwtInfoVo.getId()); + claims.put("email", userJwtInfoVo.getEmail()); + claims.put("name", userJwtInfoVo.getName()); + claims.put("authorization", userJwtInfoVo.getAuthorization()); + claims.put("avatarUrl", userJwtInfoVo.getAvatarUrl()); + + Map headers = new LinkedHashMap<>(); + headers.put("alg", "HS256"); + headers.put("typ", "JWT"); + return doGenerateToken(claims, headers); + } + + public Boolean validateToken(String token, UserJwtInfoDto userJwtInfoVo) { + String userNo = getUserIdFromToken(token); + return (userNo.equals(userJwtInfoVo.getId()) && !isTokenExpired(token)); + } + + public Boolean isTokenExpired(String token) { + try { + Date expiration = getExpirationDateFromToken(token); + return expiration.before(new Date()); + } catch (ExpiredJwtException e) { + return true; + } + } + + public String getUserIdFromToken(String token) { + return (String) getClaimFromToken(token, claims -> claims.get("id")); + } + + private String doGenerateToken(Map claims, Map headers) { + return Jwts.builder() + .signWith(SignatureAlgorithm.HS256, secret) + .setHeader(headers) + .setClaims(claims) + .setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000)) + .compact(); + } + + private Date getExpirationDateFromToken(String token) { + return getClaimFromToken(token, Claims::getExpiration); + } + + private T getClaimFromToken(String token, Function claimsResolver) { + Claims claims = getAllClaimsFromToken(token); + return claimsResolver.apply(claims); + } + + private Claims getAllClaimsFromToken(String token) { + return Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody(); + } +} diff --git a/tecobrary-core-api/src/main/java/com/woowacourse/tecobrary/web/oauth/service/GithubUserService.java b/tecobrary-core-api/src/main/java/com/woowacourse/tecobrary/web/oauth/service/GithubUserService.java new file mode 100644 index 0000000..604c287 --- /dev/null +++ b/tecobrary-core-api/src/main/java/com/woowacourse/tecobrary/web/oauth/service/GithubUserService.java @@ -0,0 +1,44 @@ +package com.woowacourse.tecobrary.web.oauth.service; + +import com.woowacourse.tecobrary.domain.user.entity.User; +import com.woowacourse.tecobrary.domain.user.repository.UserRepository; +import com.woowacourse.tecobrary.web.github.api.GithubApiService; +import com.woowacourse.tecobrary.web.github.dto.GithubUserInfoDto; +import com.woowacourse.tecobrary.web.github.dto.UserJwtInfoDto; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class GithubUserService { + + private final UserRepository userRepository; + private final GithubApiService githubApiService; + + public UserJwtInfoDto getUserByGithubInfo(String githubAccessToken) { + GithubUserInfoDto githubUserInfoDto = githubApiService.getGithubUserInfo(githubAccessToken); + + User user = userRepository.findByGithubId(githubUserInfoDto.getId()) + .orElse(newGithubUser(githubAccessToken, githubUserInfoDto)); + + return UserJwtInfoDto.builder() + .id(user.getId()) + .name(user.getName()) + .email(user.getEmail()) + .avatarUrl(user.getAvatarUrl()) + .authorization(user.getAuthorization()) + .build(); + } + + private User newGithubUser(String githubAccessToken, GithubUserInfoDto githubUser) { + String email = githubApiService.getGithubUserEmail(githubAccessToken); + User user = User.builder() + .githubId(githubUser.getId()) + .email(email) + .avatarUrl(githubUser.getAvatar_url()) + .name(githubUser.getName()) + .authorization("NONE") + .build(); + return userRepository.save(user); + } +} diff --git a/tecobrary-domain/src/main/java/com/woowacourse/tecobrary/domain/user/repository/UserRepository.java b/tecobrary-domain/src/main/java/com/woowacourse/tecobrary/domain/user/repository/UserRepository.java index 72ff179..c0c1695 100644 --- a/tecobrary-domain/src/main/java/com/woowacourse/tecobrary/domain/user/repository/UserRepository.java +++ b/tecobrary-domain/src/main/java/com/woowacourse/tecobrary/domain/user/repository/UserRepository.java @@ -4,6 +4,10 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface UserRepository extends JpaRepository { + + Optional findByGithubId(String githubId); } diff --git a/tecobrary-external-service/src/main/java/com/woowacourse/tecobrary/web/github/dto/GithubApiResponseDto.java b/tecobrary-external-service/src/main/java/com/woowacourse/tecobrary/web/github/dto/GithubApiResponseDto.java index bc8623a..3519ca1 100644 --- a/tecobrary-external-service/src/main/java/com/woowacourse/tecobrary/web/github/dto/GithubApiResponseDto.java +++ b/tecobrary-external-service/src/main/java/com/woowacourse/tecobrary/web/github/dto/GithubApiResponseDto.java @@ -11,6 +11,7 @@ package com.woowacourse.tecobrary.web.github.dto; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.ToString; @@ -23,6 +24,7 @@ public class GithubApiResponseDto { private UserJwtInfoDto user; private String token; + @Builder public GithubApiResponseDto(UserJwtInfoDto user, String token) { this.user = user; this.token = token;