Config Package
SecurityConfig
JWT Package
OAuth Package
인증 코드를 통해 유저정보 저장 및 JWT 토큰 생성까지의 Process
From Client “/api/auth/kakao” Post Request Receive [AuthController]
public AuthTokens kakaoLogin(@RequestParam String code) {
String accessToken = kakaoAuthService.getAccessToken(code);
Map<String, Object> userInfo = kakaoAuthService.getUserInfo(accessToken);
return kakaoAuthService.generateJwtTokens(userInfo);
}
String accessToken = kakaoAuthService.getAccessToken(code);
public String getAccessToken(String code) {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "authorization_code");
body.add("client_id", clientId);
body.add("redirect_uri", redirectUri);
body.add("code", code);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers);
RestTemplate rt = new RestTemplate();
ResponseEntity<String> response = rt.exchange(
tokenUri,
HttpMethod.POST,
request,
String.class
);
String responseBody = response.getBody();
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode;
try {
jsonNode = objectMapper.readTree(responseBody);
} catch (IOException e) {
throw new RuntimeException("Failed to parse access token response", e);
}
return jsonNode.get("access_token").asText();
}
Map<String, Object> userInfo = kakaoAuthService.getUserInfo(accessToken);
public Map<String, Object> getUserInfo(String accessToken) {
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer " + accessToken);
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(headers);
RestTemplate rt = new RestTemplate();
// 카카오 유저 정보 API로 POST 요청을 보내는 부분
ResponseEntity<String> response = rt.exchange(
"<https://kapi.kakao.com/v2/user/me>",
HttpMethod.POST,
request,
String.class
);
String responseBody = response.getBody();
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode;
try {
jsonNode = objectMapper.readTree(responseBody);
} catch (IOException e) {
throw new RuntimeException("Failed to parse user info response", e);
}
Long id = jsonNode.get("id").asLong();
String email = jsonNode.get("kakao_account").get("email").asText();
String name = jsonNode.get("properties").get("nickname").asText();
Map<String, Object> userInfo = new HashMap<>();
userInfo.put("id", id);
userInfo.put("email", email);
userInfo.put("name", name);
// 유저가 이미 존재하는지 확인
Optional<UserDocument> existingUser = userRepository.findByEmail(email);
if (existingUser.isEmpty()) {
// 유저가 존재하지 않으면 새로 저장
UserDocument newUser = UserDocument.builder()
.name(name)
.email(email)
.userRole(UserRole.USER)
.build();
userRepository.save(newUser);
}
return userInfo;
}
return kakaoAuthService.generateJwtTokens(userInfo);
public AuthTokens generateJwtTokens(Map<String, Object> userInfo) {
String email = (String) userInfo.get("email");
String name = (String) userInfo.get("name");
// 클레임 생성
Map<String, Object> claims = new HashMap<>();
claims.put("role", "USER");
claims.put("name", name);
claims.put("email", email);
// AccessToken과 RefreshToken 생성
String accessToken = JwtUtil.generateToken(claims, 600); // 600분 유효기간
String refreshToken = JwtUtil.generateToken(claims, 60 * 24 * 14); // 14일 유효기간
// JWT 토큰으로 Authentication 객체 생성
Authentication authentication = JwtUtil.getAuthentication(accessToken);
// SecurityContextHolder에 인증 정보 설정
SecurityContextHolder.getContext().setAuthentication(authentication);
// AuthTokens 객체 생성 및 반환
return new AuthTokens(accessToken, refreshToken, "Bearer", 60 * 60L);
}
Header에서 JWT 토큰 추출, 이후 유저정보를 식별하는 Process
UserService.getCurrentUser
public UserDocument getCurrentUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
throw new CustomException(USER_NOT_AUTHENTICATED);
}
String userEmail = ((PrincipalDetail) authentication.getPrincipal()).getUsername();
UserDocument userDocument = userRepository.findByEmail(userEmail)
.orElseThrow(() -> new CustomException(USER_NOT_FOUND));
if (userDocument.getDeletedAt() != null) {
throw new CustomException(USER_ALREADY_DELETED);
}
return userDocument;
}
SecurityContextHolder를 통해 현재 보안 컨텍스트의 인증 정보를 획득
스프링 시큐리티에서 현재 인증된 사용자에 대한 정보를 저장
Authentication객체를 포함 Authentication 객체 : 인증된 사용자에 대한 정보
위 객체는 PrincipalDetail로 형변환
형변환이 가능한 이유
: 스프링 시큐리티는 UserDetails 인터페이스를 사용하여 사용자 정보를 표현
PricipalDetail 클래스는 UserDetails를 상속받았기 때문에 DownCasting 형변환
이후 DB에서 User 식별 후 반환