JWT를 활용한 React와 Spring Boot의 인증 처리
JWT(JSON Web Token)
JWT는 토큰 기반의 인증 방식으로, 서버에서 클라이언트에게 JWT를 발급하고, 클라이언트는 이 토큰을 API 요청 시마다 서버에 전달하여 인증을 받는 방식입니다.
Spring Boot에서 JWT 설정하기
Spring Boot에서 JWT를 사용해 로그인과 회원가입을 처리하는 API를 구현한 부분입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@PostMapping("/login")
public ResponseEntity<?> authenticateUser(@RequestBody LoginRequest loginRequest) {
// 인증 로직 (username과 password 검증)
// JWT 생성
String jwt = jwtProvider.generateToken(loginRequest.getUsername());
return ResponseEntity.ok(new JwtResponse(jwt));
}
@PostMapping("/signup")
public ResponseEntity<String> SignUp(@RequestBody User user) throws UserException {
try {
authService.saveUser(user);
return ResponseEntity.ok("유저 정보 저장 성공!!");
} catch(UserException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}
}
}
authService에서는 사용자 정보를 검증하고 JWT를 생성합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void saveUser(User user) throws UserException {
User existingUser = userRepository.findByUserName(user.getUserName());
if (existingUser != null) {
throw UserException.duplicateUserException();
}
userRepository.save(user);
}
public void loginUser(User user) throws UserException {
User existingUser = userRepository.findByUserNameAndUserPwd(user.getUserName(), user.getUserPwd());
if (existingUser == null) {
throw UserException.invalidUserException();
}
}
React에서 JWT로 API 호출하기
React에서는 로그인 후 받은 JWT 토큰을 localStorage에 저장하고, 이후 API를 호출할 때마다 이 토큰을 헤더에 추가하는 방식으로 인증을 처리했습니다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function login(username, password) {
axios.post('/api/auth/login', { username, password })
.then(response => {
localStorage.setItem('token', response.data.token);
})
.catch(error => {
console.error('Login error', error);
});
}
function fetchToken() {
const token = localStorage.getItem('token');
axios.get('/api/protected-resource', {
headers: {
'Authorization': `Bearer ${token}`
}
}).then(response => {
console.log(response.data);
}).catch(error => {
console.error('Fetching error', error);
});
}
문제
- 토큰 만료 처리 : 처음에는 토큰 만료 시 처리 방법에 대해 고민이 있었습니다. 최종적으로는 만료된 토큰으로 API 호출 시 서버에서 401 Unauthorized 응답을 받을 경우, 클라이언트에서 자동으로 로그아웃하고, localStorage에서 토큰을 삭제하는 방식으로 구현했습니다.
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
const handleLogin = (event) => {
event.preventDefault();
// 사용자 정보를 객체로 생성
let user = {
userName: name,
userPwd: pwd,
};
// 로그인 API 호출
UserService.login(user)
.then((res) => {
localStorage.setItem("accessToken", res.data.token);
navigate(`/`);
})
.catch((error) => {
if (error.response && error.response.status === 401) {
window.alert("아이디나 비밀번호가 다릅니다.");
localStorage.removeItem('accessToken');
navigate('/login');
} else {
console.log("Login error:", error);
window.alert("로그인 중 오류가 발생했습니다.");
}
});
};
- 성공 처리 : 로그인에 성공하면, 응답에서 받은 JWT 토큰을 localStorage에 저장하고 메인 페이지로 리다이렉트합니다.
- 오류 처리
보안 : JWT를
localStorage에 저장하는 것은 보안에 취약하다는 점을 알게 되었습니다. 이 문제는 다음 포스트에서 Spring Security를 활용하여 개선할 계획입니다.- Refresh Token : 리프레시 토큰을 사용하여 세션을 유지하는 방법도 고려했지만, 초기에는 리프레시 토큰을 관리하는 로직이 복잡하여 많은 시간을 소모했습니다. 현재는 토큰 만료 시 사용자에게 다시 로그인하도록 유도하는 방식으로 처리하고 있습니다.
This post is licensed under CC BY 4.0 by the author.