Skip to content

Commit

Permalink
Merge pull request #6 from GSM-MSG/feature/5-google-oauth2-login
Browse files Browse the repository at this point in the history
🔀 :: google oauth2 로그인
  • Loading branch information
ta2ye0n authored Oct 12, 2024
2 parents 5311305 + 3943c76 commit e0457b8
Show file tree
Hide file tree
Showing 33 changed files with 827 additions and 2 deletions.
8 changes: 8 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'

annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
}

tasks.named('test') {
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/gcms/v3/GcmsServerV3Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;

@SpringBootApplication
@ConfigurationPropertiesScan
public class GcmsServerV3Application {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.gcms.v3.domain.auth.exception;

import com.gcms.v3.global.error.BasicException;
import com.gcms.v3.global.error.ErrorCode;

public class UserNotFoundException extends BasicException {
public UserNotFoundException() {
super(ErrorCode.USER_NOT_FOUND);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.gcms.v3.domain.auth.presentation;

import com.gcms.v3.domain.auth.presentation.data.request.SignInRequestDto;
import com.gcms.v3.domain.auth.presentation.data.response.TokenInfoResponseDto;
import com.gcms.v3.domain.auth.service.SignInService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RequiredArgsConstructor
@RestController
@RequestMapping("/v3/auth")
public class AuthController {

private final SignInService signInService;

@PostMapping
public ResponseEntity<TokenInfoResponseDto> signIn (@RequestBody SignInRequestDto signInRequestDto) {
TokenInfoResponseDto res = signInService.execute(signInRequestDto);
return ResponseEntity.ok(res);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.gcms.v3.domain.auth.presentation.data.request;

public record SignInRequestDto (
String code
){
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.gcms.v3.domain.auth.presentation.data.response;

import lombok.Builder;

import java.time.LocalDateTime;

@Builder
public record TokenInfoResponseDto(
String accessToken,
String refreshToken,
LocalDateTime accessTokenExpiresIn,
LocalDateTime refreshTokenExpiresIn
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.gcms.v3.domain.auth.service;

import com.gcms.v3.domain.auth.presentation.data.request.SignInRequestDto;
import com.gcms.v3.domain.auth.presentation.data.response.TokenInfoResponseDto;

public interface SignInService {
TokenInfoResponseDto execute(SignInRequestDto signInRequestDto);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.gcms.v3.domain.auth.service.impl;

import com.gcms.v3.domain.auth.presentation.data.request.SignInRequestDto;
import com.gcms.v3.domain.auth.presentation.data.response.TokenInfoResponseDto;
import com.gcms.v3.domain.auth.service.SignInService;
import com.gcms.v3.domain.user.domain.entity.User;
import com.gcms.v3.domain.user.domain.entity.UserRole;
import com.gcms.v3.domain.user.domain.enums.Authority;
import com.gcms.v3.domain.user.domain.repository.UserRepository;
import com.gcms.v3.domain.user.domain.repository.UserRoleRepository;
import com.gcms.v3.global.oauth2.GoogleOAuth2UserInfo;
import com.gcms.v3.global.oauth2.OAuth2Service;
import com.gcms.v3.global.security.jwt.JwtTokenProvider;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@Transactional(rollbackOn = Exception.class)
@RequiredArgsConstructor
public class SignInServiceImpl implements SignInService {

private final OAuth2Service oAuth2Service;
private final UserRepository userRepository;
private final JwtTokenProvider jwtTokenProvider;
private final UserRoleRepository userRoleRepository;

public TokenInfoResponseDto execute(SignInRequestDto signInRequestDto) {
String accessToken = oAuth2Service.requestAccessToken(signInRequestDto.code());
GoogleOAuth2UserInfo googleOAuth2UserInfo = oAuth2Service.requestUserInfo(accessToken);

User user = userRepository.findByEmail(googleOAuth2UserInfo.getEmail())
.orElseGet(() -> toEntity(googleOAuth2UserInfo));

return jwtTokenProvider.generateToken(user.getEmail());
}

private User toEntity(GoogleOAuth2UserInfo googleOAuth2UserInfo) {
User user = User.builder()
.name(googleOAuth2UserInfo.getName())
.email(googleOAuth2UserInfo.getEmail())
.build();

userRepository.save(user);
saveAuthority(user);
return user;
}

private void saveAuthority(User user) {
UserRole userRole = UserRole.builder()
.user(user)
.authority(Authority.ROLE_STUDENT)
.build();

userRoleRepository.save(userRole);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ public class User {
@Column(columnDefinition = "VARCHAR(20)", nullable = false)
private String email;

@Column(columnDefinition = "TEXT", nullable = false)
@Column(columnDefinition = "TEXT")
private String profileImg;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.gcms.v3.domain.user.domain.repository;

import com.gcms.v3.domain.user.domain.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, byte[]> {
Optional<User> findByEmail(String email);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.gcms.v3.domain.user.domain.repository;

import com.gcms.v3.domain.user.domain.entity.User;
import com.gcms.v3.domain.user.domain.entity.UserRole;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface UserRoleRepository extends JpaRepository<UserRole, byte[]> {
List<UserRole> findByUser(User user);
}
10 changes: 10 additions & 0 deletions src/main/java/com/gcms/v3/global/error/BasicException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.gcms.v3.global.error;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public class BasicException extends RuntimeException{
ErrorCode errorCode;
}
21 changes: 21 additions & 0 deletions src/main/java/com/gcms/v3/global/error/ErrorCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.gcms.v3.global.error;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;


@AllArgsConstructor
@Getter
public enum ErrorCode {

INVALID_AUTH_TOKEN(HttpStatus.UNAUTHORIZED, "권한 정보가 없는 토큰입니다."),
EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "만료된 토큰입니다."),
INVALID_TOKEN_TYPE(HttpStatus.UNAUTHORIZED, "유효하지 않은 토큰 타입입니다."),
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 유저를 찾을 수 없습니다."),

INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 에러");

private final HttpStatus httpStatus;
private final String message;
}
21 changes: 21 additions & 0 deletions src/main/java/com/gcms/v3/global/error/ErrorResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.gcms.v3.global.error;

import lombok.Builder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

@Builder
public record ErrorResponse(
HttpStatus status,
String message
) {
public static ResponseEntity<ErrorResponse> toResponseEntity(ErrorCode e){
return ResponseEntity
.status(e.getHttpStatus())
.body(ErrorResponse.builder()
.status(e.getHttpStatus())
.message(e.getMessage())
.build()
);
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/gcms/v3/global/error/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.gcms.v3.global.error;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(BasicException.class)
protected ResponseEntity<ErrorResponse> handleCustomException(BasicException e) {
return ErrorResponse.toResponseEntity(e.getErrorCode());
}
}
31 changes: 31 additions & 0 deletions src/main/java/com/gcms/v3/global/oauth2/GoogleOAuth2UserInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.gcms.v3.global.oauth2;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.Map;

@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
@NoArgsConstructor
public class GoogleOAuth2UserInfo {

protected Map<String, Object> attributes;

public GoogleOAuth2UserInfo(Map<String, Object> attributes) {
this.attributes = attributes;
}

public String getId() {
return (String) attributes.get("sub");
}

public String getName() {
return (String) attributes.get("name");
}

public String getEmail() {
return (String) attributes.get("email");
}
}
26 changes: 26 additions & 0 deletions src/main/java/com/gcms/v3/global/oauth2/GoogleProperties.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.gcms.v3.global.oauth2;

import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
@Getter
public class GoogleProperties {
@Value("${spring.security.oauth2.client.provider.google.token-uri}")
private String tokenUri;

@Value("${spring.security.oauth2.client.registration.google.client-id}")
private String clientId;

@Value("${spring.security.oauth2.client.registration.google.client-secret}")
private String clientSecret;

@Value("${spring.security.oauth2.client.provider.google.user-info-uri}")
private String userInfoUri;

@Value("${spring.security.oauth2.client.registration.google.redirect-uri}")
private String redirectUri;

public static final String GRANT_TYPE = "authorization_code";
}
71 changes: 71 additions & 0 deletions src/main/java/com/gcms/v3/global/oauth2/OAuth2Service.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.gcms.v3.global.oauth2;

import lombok.RequiredArgsConstructor;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;

import static com.gcms.v3.global.oauth2.GoogleProperties.GRANT_TYPE;

@Service
@RequiredArgsConstructor
public class OAuth2Service {

private final GoogleProperties config;
private final RestTemplate restTemplate;

public String requestAccessToken(String code) {
String decode = URLDecoder.decode(code, StandardCharsets.UTF_8);

HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("client_id", config.getClientId());
body.add("client_secret", config.getClientSecret());
body.add("code", decode);
body.add("redirect_uri", config.getRedirectUri());
body.add("grant_type", GRANT_TYPE);

HttpEntity<?> request = new HttpEntity<>(body, httpHeaders);

ResponseEntity<Map<String, String>> response =
restTemplate.exchange(
config.getTokenUri(),
HttpMethod.POST,
request,
new ParameterizedTypeReference<>() {
}
);

return response.getBody().get("access_token");
}


public GoogleOAuth2UserInfo requestUserInfo(String accessToken) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
httpHeaders.setBearerAuth(accessToken);

MultiValueMap<String, String> body = new LinkedMultiValueMap<>();

HttpEntity<?> request = new HttpEntity<>(body, httpHeaders);

ResponseEntity<Map<String, Object>> response =
restTemplate.exchange(
config.getUserInfoUri(),
HttpMethod.GET,
request,
new ParameterizedTypeReference<>() {}
);

return new GoogleOAuth2UserInfo(response.getBody());
}
}
13 changes: 13 additions & 0 deletions src/main/java/com/gcms/v3/global/oauth2/config/ClientConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.gcms.v3.global.oauth2.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ClientConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Loading

0 comments on commit e0457b8

Please sign in to comment.