JWT + 카카오 소셜 로그인 (2)
📌 React와 Spring Boot를 사용하여 JWT를 기반으로 로그인 세션을 관리하는 과정
1. React에서 API 요청
React 클라이언트에서는 이메일과 비밀번호를 입력 후 로그인 API를 호출하여 사용자 인증을 처리
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
import axios from 'axios';
class UserService {
static BASE_URL = 'http://localhost:8088';
static async login(email, password) {
const response = await axios.post(`${this.BASE_URL}/user/login`, {
email,
password,
});
return response.data;
}
}
- axios.post를 사용하여 로그인 API를 호출
2. 로그인 및 토큰 재발급 컨트롤러
서버단 컨트롤러에서는 로그인 및 토큰 재발급 요청을 처리
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
@RequiredArgsConstructor
@RequestMapping("/user")
public class UserController {
private UserService userService;
@PostMapping("/login")
public ResponseEntity<UserDTO> login(@RequestBody UserDTO userDTO) {
return ResponseEntity.ok(userService.login(userDTO));
}
// 새로고침 토큰
@PostMapping("/refresh")
public ResponseEntity<UserDTO> refreshToken(@RequestBody UserDTO userDTO) {
return ResponseEntity.ok(userService.refreshToken(userDTO));
}
}
/login
엔드포인트에서 사용자 인증 후 JWT 토큰을 반환/refresh
엔드포인트에서 새로고침 토큰을 사용해 새로운 JWT 토큰을 반환
3. UserService 클래스
UserService
클래스에서는 사용자 로그인 및 토큰 재발급 로직을 구현
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class UserService {
public UserDTO login(UserDTO userDTO) {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(userDTO.getEmail(), userDTO.getPassword())
);
UserEntity user = userRepository.findByEmail(userDTO.getEmail())
.orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다. 이메일: " + userDTO.getEmail()));
// 조회된 사용자 정보를 바탕으로 JWT 토큰 생성
String jwt = jwtUtils.generateToken(user);
// 비어있는 맵과 사용자 정보를 바탕으로 새로고침 토큰 생성
String refreshToken = jwtUtils.generateRefreshToken(new HashMap<>(), user);
UserDTO response = UserDTO.toDTO(user);
response.setToken(jwt);
response.setRole(user.getRole());
response.setRefreshToken(refreshToken);
return response;
}
public UserDTO refreshToken(UserDTO userDTO) {
UserDTO response = new UserDTO();
// 토큰에서 사용자 이메일 추출
String ourEmail = jwtUtils.extractUsername(userDTO.getToken());
// 이메일로 사용자 정보 조회, 없으면 예외 발생
UserEntity users = userRepository.findByEmail(ourEmail)
.orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다. 이메일: " + ourEmail));
// 토큰 유효성 검사 후 유효하다면 새로운 토큰 생성
if (jwtUtils.isTokenValid(userDTO.getToken(), users)) {
String jwt = jwtUtils.generateToken(users);
response.setToken(jwt);
response.setRefreshToken(userDTO.getToken());
}
return response;
}
}
- 로그인 시 사용자 인증을 처리하고 JWT 및 새로고침 토큰을 생성
- 새로고침 토큰 유효성을 검사하고, 유효하다면 새로운 JWT를 생성
4. JWTUtils 클래스
JWTUtils
클래스에서는 JWT 생성 및 검증에 필요한 유틸리티 메서드를 구현
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.petstagram.service.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.function.Function;
@Component
public class JWTUtils {
private SecretKey key;
private static final long ACCESS_TOKEN_EXPIRATION_TIME = 1800000; // 30분
private static final long REFRESH_TOKEN_EXPIRATION_TIME = 604800000; // 7일
public JWTUtils(@Value("${jwt.secret}") String secretString) {
byte[] keyBytes = Base64.getDecoder().decode(secretString.getBytes(StandardCharsets.UTF_8));
this.key = new SecretKeySpec(keyBytes, "HmacSHA256");
}
public String generateToken(UserDetails userDetails){
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRATION_TIME))
.signWith(key)
.compact();
}
public String generateRefreshToken(HashMap<String, Object> claims, UserDetails userDetails){
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + REFRESH_TOKEN_EXPIRATION_TIME))
.signWith(key)
.compact();
}
public String extractUsername(String token){
return extractClaims(token, Claims::getSubject);
}
private <T> T extractClaims(String token, Function<Claims, T> claimsResolver){
return claimsResolver.apply(Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody());
}
public boolean isTokenValid(String token, UserDetails userDetails){
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
public boolean isTokenExpired(String token){
return extractClaims(token, Claims::getExpiration).before(new Date());
}
}
generateToken
메서드는 액세스 토큰을 생성generateRefreshToken
메서드는 새로고침 토큰을 생성extractUsername
메서드는 토큰에서 사용자 이름을 추출isTokenValid
메서드는 토큰 유효성을 검사합니다.출
5. OurUserDetailsService 클래스
OurUserDetailsService
클래스는 사용자 인증에 필요한 사용자 정보를 데이터베이스에서 조회
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.petstagram.service.utils;
import com.petstagram.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class OurUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByEmail(username)
.orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다. email = " + username));
}
}
loadUserByUsername
메서드는 이메일로 사용자 정보를 조회
이 기사는 저작권자의
CC BY 4.0
라이센스를 따릅니다.