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>
'spring boot' 카테고리의 다른 글
react에서 구글로그인 구현-II(feat. @react-oauth/google) (0) | 2024.12.30 |
---|---|
JWT를 이용한 인증-III(feat. react, spring boot, spring security) (0) | 2024.12.28 |
JWT를 이용한 인증-II(feat. react, spring boot) (1) | 2024.12.25 |
JWT를 이용한 인증-I(feat. react) (0) | 2024.12.25 |
Log4j2를 이용한 로깅(logging) 방법 - V (0) | 2024.12.12 |