사용자 인증은 거의 모든 웹 애플리케이션에서 필수적인 부분입니다.
이 포스트에서는 Java와 Spring Boot를 사용하여 백엔드를,
React를 사용하여 프론트엔드를 구현하며 JWT(JSON Web Token)를 사용하여
사용자 인증을 처리하는 방법에 대해 살펴보겠습니다.
@ValidAspect
@PostMapping("/login")
public ResponseEntity<?> login(@Valid @RequestBody LoginReqDto loginReqDto, BindingResult bindingResult) {
return ResponseEntity.ok(authService.signin(loginReqDto));
}
public JwtRespDto signin(LoginReqDto loginReqDto) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginReqDto.getEmail(), loginReqDto.getPassword());
Authentication authentication =
authenticationManagerBuilder.getObject().authenticate(authenticationToken);
return jwtTokenProvider.generateToken(authentication);
}
로그인 요청은 "/login" 엔드포인트를 통해 처리됩니다.
클라이언트에서 로그인 요청이 오면 요청 본문에 포함된 LoginReqDto 객체를 이용해
이메일과 비밀번호로 이루어진 UsernamePasswordAuthenticationToken 객체를 생성합니다.
이 객체는 인증 매니저를 통해 인증이 진행되며, 인증 성공 시 JWT 토큰이 생성되어 반환됩니다.
@GetMapping("/authenticated")
public ResponseEntity<?> authenticated(@RequestHeader(value = "Authorization") String accessToken) {
return ResponseEntity.ok(authService.authenticated(accessToken));
}
public boolean authenticated(String accessToken) {
return jwtTokenProvider.validateToken(jwtTokenProvider.getToken(accessToken));
}
"/authenticated" 엔드포인트는
클라이언트가 전달한 토큰의 유효성을 확인하는 엔드포인트입니다.
사용자 인증 상태를 확인하기 위해 요청 헤더의 "Authorization" 필드에서 JWT 토큰을 추출하고,
해당 토큰의 유효성을 검증합니다.
@GetMapping("/principal")
public ResponseEntity<?> principal(String accessToken) {
return ResponseEntity.ok(authService.getPrincipal(accessToken));
}
public PrincipalRespDto getPrincipal(String accessToken) {
Claims claims = jwtTokenProvider.getClaims(jwtTokenProvider.getToken(accessToken));
User userEntity = authRepository.findUserByEmail(claims.getSubject());
...
return PrincipalRespDto.builder()
.userId(userEntity.getUserId())
.email(userEntity.getEmail())
.name(userEntity.getName())
.phone(userEntity.getPhone())
...
.build();
}
"/principal" 엔드포인트는 현재 인증된 사용자의 주요 정보를 가져오는 엔드포인트입니다.
요청 헤더의 "Authorization" 필드에서 JWT 토큰을 추출하고,
해당 토큰의 주체(claims)를 이용하여 사용자 정보를 가져옵니다.
리액트에서는 사용자 로그인 상태를 관리하기 위해 Recoil이라는 상태 관리 라이브러리를 사용했습니다.
const signInUser = useMutation(async (loginData) => {
try {
const option = {
headers: {
'Content-Type': 'application/json'
}
}
const response = await axios.post(`http://localhost:8080/api/v1/auth/login`, loginData, option);
...
const accessToken = response.data.grantType + " " + response.data.accessToken;
localStorage.setItem('accessToken', accessToken);
setAuthState(true);
navigate('/');
}catch (error) {
setErrorMessages({email: '', password: '', ...error.response.data.errorData});
}
})
이벤트 핸들러 signInUser는 입력된 로그인 데이터를 서버에 전송하고
응답으로 받은 JWT 토큰을 로컬 스토리지에 저장합니다.
이처럼 서버 사이드와 클라이언트 사이드에서 JWT를 활용해 로그인과 인증을 구현할 수 있습니다.
이 방식을 사용하면 세션과 쿠키를 관리하는 번거로움 없이,
서버와 클라이언트 사이의 인증을 안전하게 처리할 수 있습니다.
우리의 메인 컨텐츠인 카카오맵 api 를 사용하여 화면에서 마커를 찍고
그 마커 정보를 서버에 넘겨 저장하는 기능에 대하여 알아보겠습니다.