JAN's History
로그인 필터 구현 본문
Spring 로그인 모식도
- 로그인 요청(POST: /login)
- 사용자가 Username과 Password를 제출합니다.
- UsernamePasswordAuthenticationFilter가 인증 요청을 처리합니다.
- 인증처리
- AuthenticationManager가 UserDetailsService와 UserRepository를 통해 사용자 정보를 확인하고 인증합니다.
- 인증에 성공하면 SuccessfulAuthentication 객체를 반환합니다.
- 인증에 실패하면 unsuccessfulAuthentication 객체를 반환합니다.
- JWT토큰 생성
- JWTUtil (또는 JWTProvider)이 SuccessfulAuthentication 객체를 사용해 JWT 토큰을 생성합니다.
- 응답 반환
- JWT 토큰이 응답에 포함되어 클라이언트로 전송됩니다.
- 회원 가입 (POST: /join)
- JoinController가 JoinService를 호출하여 새 사용자를 UserEntity에 저장합니다.
JWT 인증을 사용하려면 일반적으로 Spring Security의 기본 필터인 UsernamePasswordAuthenticationFilter를 비활성화해야 합니다. 이는 기본 인증 방식 대신 JWT 토큰 기반 인증을 사용하기 위함입니다.
UsernamePasswordAuthenticationFilter를 비활성화(disable)하는 이유는?
JWT를 사용하여 인증 메커니즘을 구현할 때 기존의 폼 기반 인증 방식을 사용하지 않기 때문입니다. JWT를 사용한 인증 방식에서는 클라이언트가 서버로부터 JWT 토큰을 받아 이후 요청에서 이 토큰을 사용하여 인증을 처리합니다. 이 과정은 전통적인 폼 로그인 방식과는 다르기 때문에 UsernamePasswordAuthenticationFilter를 비활성화해야 합니다.
➡️자세하게 알아보자
- 전통적인 스프링 시큐리티 설정에서는 폼 기반 로그인 방식이 기본으로 활성화되어 있습니다. 이는 사용자가 로그인 폼을 통해 자격 증명을 입력하고, 서버는 세션을 통해 인증 상태를 관리하는 방식입니다.
- JWT 기반 인증에서는 클라이언트가 자격 증명을 제출하면 서버가 JWT를 생성하여 반환합니다. 이후 클라이언트는 모든 요청에 이 JWT를 포함하여 서버에 보냅니다. 서버는 이 토큰을 검증하여 사용자 인증을 처리합니다.
- 세션을 사용하지 않음:
- JWT 인증은 세션을 사용하지 않고, 토큰을 통해 상태를 관리합니다. 따라서 상태 비저장(stateless) 방식으로 동작합니다.
- UsernamePasswordAuthenticationFilter는 기본적으로 세션을 통해 인증 상태를 관리하는 방식이므로, JWT를 사용하는 경우에는 비활성화해야 합니다.
- 커스텀 필터 사용:
- JWT 인증을 위해서는 클라이언트의 요청에서 JWT를 추출하고, 이를 검증하는 커스텀 필터가 필요합니다. 이 커스텀 필터는 UsernamePasswordAuthenticationFilter 대신 사용됩니다.
- 일반적으로 이 필터는 OncePerRequestFilter를 상속받아 구현하며, 요청이 서버에 도달할 때마다 한 번 실행되어 JWT를 검증합니다.
📍즉 이 JWT 프로젝트는 SecurityConfig에서 formLogin 방식을 disable 했기 때문에 기본적으로 활성화 되어 있는 해당 필터는 동작하지 않는다. 따라서 로그인을 진행하기 위해서 필터를 커스텀하여 등록해야 한다!
로그인 요청 받기 : 커스텀 UsernamePasswordAuthentication 필터 작성
로그인 검증을 위한 커스텀 UsernamePasswordAuthentication 필터 작성
- LoginFilter
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
public LoginFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//클라이언트 요청에서 username, password 추출
String username = obtainUsername(request);
String password = obtainPassword(request);
//스프링 시큐리티에서 username과 password를 검증하기 위해서는 token에 담아야 함
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, password, null);
//token에 담은 검증을 위한 AuthenticationManager로 전달
return authenticationManager.authenticate(authToken);
}
//로그인 성공시 실행하는 메소드 (여기서 JWT를 발급하면 됨)
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) {
}
//로그인 실패시 실행하는 메소드
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
}
}
SecurityConfig 설정
- SecurityConfig : 커스텀 로그인 필터 등록
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf((auth) -> auth.disable());
http
.formLogin((auth) -> auth.disable());
http
.httpBasic((auth) -> auth.disable());
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/login", "/", "/join").permitAll()
.anyRequest().authenticated());
//필터 추가 LoginFilter()는 인자를 받음 (AuthenticationManager() 메소드에 authenticationConfiguration 객체를 넣어야 함) 따라서 등록 필요
http
.addFilterAt(new LoginFilter(), UsernamePasswordAuthenticationFilter.class);
http
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
}
- SecurityConfig : AuthenticationMananger Bean 등록과 LoginFilter 인수 전달
@Configuration
@EnableWebSecurity
public class SecurityConfig {
//AuthenticationManager가 인자로 받을 AuthenticationConfiguraion 객체 생성자 주입
private final AuthenticationConfiguration authenticationConfiguration;
public SecurityConfig(AuthenticationConfiguration authenticationConfiguration) {
this.authenticationConfiguration = authenticationConfiguration;
}
//AuthenticationManager Bean 등록
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf((auth) -> auth.disable());
http
.formLogin((auth) -> auth.disable());
http
.httpBasic((auth) -> auth.disable());
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/login", "/", "/join").permitAll()
.anyRequest().authenticated());
//필터 추가 LoginFilter()는 인자를 받음 (AuthenticationManager() 메소드에 authenticationConfiguration 객체를 넣어야 함) 따라서 등록 필요
http
.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration)), UsernamePasswordAuthenticationFilter.class);
http
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
}
로그인 성공시 JWT 반환
로그인 성공시 successfulAuthentication() 메소드를 통해 JWT를 응답해야 한다. 따라서 JWT 응답 구문을 작성해야 하는데 JWT 발급 클래스를 아직 생성하지 않았기 때문에 다음 시간에 DB 기반 회원 검증 구현을 진행한 뒤 JWT 발급 및 검증을 진행하는 클래스를 생성하겠습니다.
'JWT' 카테고리의 다른 글
Spring Security - OAuth2 session 방식으로 구현 해보기(Google,Facebook,Naver) (0) | 2024.06.17 |
---|---|
Spring Security Session 권한처리 (1) | 2024.06.14 |
SecurityConfig 클래스 (0) | 2024.06.11 |
CSRF Token의 개념과 공격 (0) | 2024.06.08 |
JWT 인증 방식 시큐리티 동작 원리 (0) | 2024.06.05 |