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
라이센스를 따릅니다.