spring boot

react에서 구글로그인 구현-III(feat. spring)

hoazzinews 2024. 12. 30. 06:13

build.gradle

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
	
	all {
        exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
    }
    
}

...

dependencies {

	// JWT
	implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
	runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
	runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
	
	// Log4j2
	implementation 'org.springframework.boot:spring-boot-starter-log4j2'
	
	// Google Oauth
	implementation 'com.google.auth:google-auth-library-oauth2-http:1.30.0'
    implementation 'com.google.api-client:google-api-client:1.33.2'  		// Google API Client
	
	...
}

 

oauth 패키지 생성

 

GoogleUser 클래스 만들기

package com.office.jwtex.member.oauth;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@JsonIgnoreProperties(ignoreUnknown = true)
public class GoogleUser {
	
	private String id;               // 사용자 고유 ID
    private String email;            // 이메일
    private String name;             // 전체 이름
    private String picture;          // 프로필 사진 URL
    private boolean verified_email;  // 이메일 인증 여부
    private String given_name;       // 이름 (예: John)
    private String family_name;      // 성 (예: Doe)
    
}

 

GoogleAuthService 클래스 만들기

private static final String CLIENT_ID = "-----";
	
    // ID Token 검증
    public GoogleIdToken verifyIdToken(String idToken) throws GeneralSecurityException, IOException {
    	log.info("verifyIdToken()");
    	
        // Google 공개 키 URL에서 공개 키를 가져옵니다.
        GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(new NetHttpTransport(), GsonFactory.getDefaultInstance())
                .setAudience(Collections.singletonList(CLIENT_ID))  // 올바른 클라이언트 ID로 검증
                .build();

        // ID Token 검증
        GoogleIdToken googleIdToken = verifier.verify(idToken);
        if (googleIdToken != null) {
            Payload payload = googleIdToken.getPayload();
            // 인증이 성공하면 payload에 포함된 사용자 정보 반환
            return googleIdToken;
            
        } else {
            throw new GeneralSecurityException("Invalid ID Token.");
            
        }
        
    }

	public GoogleUser getUserInfoFromAccessToken(String accessToken) throws IOException {
		log.info("getUserInfoFromAccessToken()");
		
		String url = "https://www.googleapis.com/oauth2/v1/userinfo?access_token=" + accessToken;
	    HttpRequest request = new NetHttpTransport()
	            .createRequestFactory()
	            .buildGetRequest(new GenericUrl(url))
	            .setParser(new GsonFactory().createJsonObjectParser());
	    
	    HttpResponse httpResponse = request.execute();
	    if (httpResponse.getStatusCode() == 200) {
	    	log.info("StatusCode == 200");
	        return new ObjectMapper().readValue(httpResponse.getContent(), GoogleUser.class);
	        
	    } else {
	    	log.info("StatusCode != 200");
	        throw new IOException("Failed to fetch user info: " + httpResponse.getStatusMessage());
	        
	    }
		
	}

 

OauthController 클래스 만들기

package com.office.jwtex.member.oauth;

import java.security.SecureRandom;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.office.jwtex.jwt.JwtService;
import com.office.jwtex.jwt.token.TokenGenerateAndSaveResult;
import com.office.jwtex.jwt.token.TokenResponse;
import com.office.jwtex.member.MemberConstant;
import com.office.jwtex.member.MemberDto;
import com.office.jwtex.member.MemberService;
import com.office.jwtex.member.response.SignInResponse;

import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
@RequestMapping("/member/oauth")
public class OauthController {

	private final GoogleAuthService googleAuthService;
	private final MemberService memberService;
	private final JwtService jwtService;
	
	public OauthController(
			GoogleAuthService googleAuthService, 
			MemberService memberService, 
			JwtService jwtService) {
		this.googleAuthService = googleAuthService;
		this.memberService = memberService;
		this.jwtService = jwtService;
		
	}
	
	@PostMapping("/google")
	public void google(@RequestBody Map<String, String> requestBody, HttpServletResponse response) {
		log.info("google()");
		
		String accessToken = requestBody.get("accessToken");
	    log.info("accessToken: {}", accessToken);
	    if (accessToken == null || accessToken.isEmpty()) {
	        throw new IllegalArgumentException("Access Token is required.");
	    }
        
        try {
        	// Google API로 Access Token 검증 및 사용자 정보 가져오기
            GoogleUser googleUser = googleAuthService.getUserInfoFromAccessToken(accessToken);
            
            String email = googleUser.getEmail();
            String name = googleUser.getName();
            String pictureUrl = googleUser.getPicture();
            
            MemberDto memberDto = memberService.findMemberByMail(email);
            if (memberDto == null) {
                memberService.signup(MemberDto.builder()
                    .id(googleUser.getId())
                    .pw(createNewPassword())
                    .mail(email)
                    .build());

                memberDto = memberService.getUserInfo(googleUser.getId());
            }
            
            TokenGenerateAndSaveResult tokenGenerateAndSaveResult = jwtService.generateTokenAndSave(memberDto);
            
            // 쿠키 설정
            response.addHeader(HttpHeaders.SET_COOKIE, tokenGenerateAndSaveResult.getResponseCookie().toString());
            
            // 응답 데이터 작성
            Map<String, Object> responseData = new HashMap<>();
            responseData.put("signInResponse", SignInResponse.builder()
                    .isSuccess(true)
                    .message(MemberConstant.SIGNIN_SUCCESS)
                    .userId(memberDto.getId())
                    .build());
            
            responseData.put("tokenResponse", TokenResponse.builder()
                    .accessToken(tokenGenerateAndSaveResult.getAccessToken())
                    .refreshToken(tokenGenerateAndSaveResult.getRefreshToken())
                    .build());
            
            // 응답 헤더 설정
            response.setContentType("application/json");
            response.setStatus(HttpServletResponse.SC_OK);
            
            // JSON 변환 및 출력
            ObjectMapper objectMapper = new ObjectMapper();
            response.getWriter().write(objectMapper.writeValueAsString(responseData));
            
		} catch (Exception e) {
			e.printStackTrace();
			
		}
		
	}
	
	private String createNewPassword() {
		log.info("createNewPassword()");
		
		char[] chars = new char[] {
				'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 
				'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 
				'u', 'v', 'w', 'x', 'y', 'z'
				};

		StringBuffer stringBuffer = new StringBuffer();
		SecureRandom secureRandom = new SecureRandom();
		secureRandom.setSeed(new Date().getTime());
		
		int index = 0;
		int length = chars.length;
		for (int i = 0; i < 8; i++) {
			index = secureRandom.nextInt(length);
		
			if (index % 2 == 0) 
				stringBuffer.append(String.valueOf(chars[index]).toUpperCase());
			else
				stringBuffer.append(String.valueOf(chars[index]).toLowerCase());
		
		}
		
		log.info("NEW PASSWORD: {}", stringBuffer.toString());
		
		return stringBuffer.toString();
		
	}
	
}

 

SecurityConfig 클래스 수정("/member/oauth/google" 추가)

http
.authorizeHttpRequests(auth -> auth
		.requestMatchers(
				"/", 
				"/member/signup", 
				"/member/signout", 
				"/member/oauth/google").permitAll()
		.anyRequest().authenticated());

 

MemberService 클래스에 findMemberByMail() 메서드 추가

public MemberDto findMemberByMail(String mail) {
	log.info("findMemberByMail()");
	
	return memberMapper.selectMemberByMail(mail);
	
}

 

MemberMapper 인터페이스에 selectMemberByMail() 메서드 추가

MemberDto selectMemberByMail(String mail);

 

member-mapper.xml에 <select id="selectMemberByMail">추가

<select id="selectMemberByMail" 
		parameterType="String" 
		resultType="com.office.jwtex.member.MemberDto">
		
	SELECT 
		* 
	FROM 
		MEMBER 
	WHERE 
		MAIL = #{mail}	

</select>

 

jwtex.zip
0.15MB