저번시간에 이어 JWT를 이용하여 로그인 API를 만들어보고자 한다.
우선 내가 생각한 인증 프로세스는 대략 이렇다.
최초 로그인시에는 사용자의 아이디와 패스워드를 받은 후 검증을 통해 JWT 객체를 반환하고, 이후 또 다른 API 요청시 발급받은 JWT를 함께 보낸다.
1. 먼저 POST /login 을 생성한다.
/**
* 로그인수행
* @return
*/
@PostMapping("/login")
public ResponseEntity<TokenDto> loginSuccess(@RequestBody AuthDto authDto) {
TokenDto token = userService.loginProcess(authDto.getLoginId(), authDto.getPassword());
return ResponseEntity.ok(token);
}
파라미터로 인증용 객체를 생성한후, 요청 ID 와 PW를 받았다.
이전 포스팅에 설명된 Spring Security 관련 흐름을 보면 이렇다.
1. 사용자의 요청 정보 전송
2. AuthenticationFilter가 요청을 받아서 UsernamePasswordAuthenticationToken(인증용 객체)을 생성한다.
3~4. AuthenticationManager가 인증을 처리할 수 있는 AuthenticationProvider 에게 전달한다.
5~6. DB에 있는 사용자 정보와 요청받은 정보를 비교 후 loadUserByUsername() 메소드 수행
7~8. 성공시 Authentication 객체리턴
9. Authentication 객체를 SecurityContextHolder에 담은 이후 각 결과별 Handler 처리
여기서 입력받은 ID/PW 로 요청을 받아 UsernamePasswordAuthenticationToken(ID,PW) 를 통해 인증용 객체를 생성 후
AuthenticationProvider()로 인증용 객체를 전달한다.
AuthenticationProvider는 인증 성공, 실패, 결정할 수 없음을 나타낼 수 있고, 나머지 AuthenticationProvider가 결정을 할 수 있도록 전달한다.
따라서, password 기반 인증 경우 ID/PW 가 유효한지 내부에서 Validaion Check가 가능하다.
2. ID, PW 인증 기반이므로 기본적으로 시큐리티에서 제공하는 passwordEncoder 를 Bean 등록해준다.
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
여러가지 패스워드 암호화방식이 있겠지만 나는 보편적으로 쓰이는 BCryptPasswordEncoder를 이용하여 해싱 처리를 했다.
3. AuthenticationProvider 를 커스텀한 CustomAuthenticationProvider 를 생성한다.
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final UserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (!passwordEncoder.matches(password, userDetails.getPassword())) {
throw new BadCredentialsException("패스워드가 일치하지 않습니다.");
}
Authentication authenticatedUser = new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticatedUser);
return authenticatedUser;
}
입력받은 ID 와 PW 유효성을 체크 후 인증정보를 SecurityContextHolder 에 저장한다.
(※ 참고로 ID를 체크하고 싶다면 UserDetailsService를 Custom 하면 된다.)
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers(AUTH_WHITELIST);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.authorizeHttpRequests( // 요청에 대한 인가 규칙설정
authorize -> authorize
.anyRequest()
.authenticated()
)
.csrf(AbstractHttpConfigurer::disable) // CSRF 토큰 보호 해제
.httpBasic(AbstractHttpConfigurer::disable) // HTTP 기본 인증 비활성화
.formLogin(AbstractHttpConfigurer::disable) // 폼 기반 로그인 비활성화
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class) // 인증필터추가
.build();
}
Security와 filter 에 적용시키지 않을 리소스를 WebSecurityCustomizer를 Bean 등록한다.
httpSecurity.authorizeHttpRequests 에 requestMatchers.permitAll() 로 처리해도 상관이 없으나, 그렇게 하면 또 필터에 shouldNotFilter를 오버라이드해서 써야 하므로 귀찮으니 난 걍 저걸 쓰겠다.
REST API 방식을 사용할 때는 쿠키를 사용해서 인증하는 방식을 잘 사용하지 않기에 CSRF 설정은 껐다.
4. Service 에 로직 추가
@Transactional
public TokenDto loginProcess(String id, String password) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(id, password);
Authentication authentication = customAuthenticationProvider.authenticate(authenticationToken);
TokenDto token = jwtTokenProvider.generateToken(authentication);
userRepository.updateLoginInfo(token.getRefreshToken(),id);
return token;
}
인증객체 생성 후 커스텀한 Provider로 인증을 수행한다. 검증된 정보로 토큰을 생성 후 DB에 리프레시토큰 및 접속시간을 업데이트 시킨다.
5. Test
테스트해보니 잘된다!!
이 후 인증이 필요한 API는 발급받은 JWT 를 Header 에 담아서 통신하면 되겠다.
기본적으로 필요한것들만 정리하였고, 추가로 필요한 로직은 저기서 입맛에 맞게 추가하면 된다.
'web > SpringBoot' 카테고리의 다른 글
Global Exception 처리 (0) | 2023.09.06 |
---|---|
DataJpaTest 중 삽질해결 기록.... (0) | 2023.08.31 |
Spring Boot 3.1.x 으로 RestFul API 서버 만들기(2) (2) | 2023.08.28 |
SpringBoot 구조와 원리 (0) | 2023.08.22 |
Spring Boot 3.1.x 으로 RestFul API 서버 만들기(1) (1) | 2023.08.21 |