포스트

02.LoginFilter

LoginFilter

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
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.io.IOException;

@RequiredArgsConstructor
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
// UsernamePasswordAuthenticationFilter는 원래 HttpSecurity#formLogin에서 진행을 했는데
// jwt를 위해 formLogin을 disable 시켰기 때문에 개발자가 구현해줘야 한다

  private final AuthenticationManager authenticationManager;

  @Override
  // 미승인 Authentication 생성하는 메소드
  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException{

    // 요청을 가로채서 username과 password를 추출
    String username = obtainUsername(request);
    String password = obtainPassword(request);

    // 필터를 타는지 확인    
    System.out.println("username : " + username);

    // 스프링 시큐리티에서 username과 password를 검증하기 위해서는 token에 담아야 한다
    UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, password, null);
                                                                                        // username, password, role
    // token에 담은 검증을 AuthenticationManager로 전달
    return authenticationManager.authenticate(authToken);
  }

  @Override
  protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
    System.out.println("로그인 성공");
  }

  @Override
  protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
    System.out.println("로그인 실패");
  }
}

Security Config 클래스

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class SecurityConfig {

  private final AuthenticationConfiguration authenticationConfiguration;

  // 로그인필터의 생성자를 위해
  @Bean
  public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception{
    return configuration.getAuthenticationManager();
  }


  // 스프링 시큐리티에서 제공하는 인증, 인가를 위한 필터 모음
  // Application Context 초기화가 이루어 지면서 HttpSecurity 객체가 설정한 filterChain 형성

  @Bean
  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

    http
            .csrf(AbstractHttpConfigurer::disable);
            // == .csrf((auth) -> auth.disable());
            // CSRF(Cross-Site-Request Forgery 보호를 비활성화하는 메서드 호출
            // AbstractHttpConfigurer::disable -> AbstractHttpConfigurer에 정의된 disable 메소드에 대한 참조
    http
            .formLogin(AbstractHttpConfigurer::disable);
    // == formLogin((auth) -> auth.disable());

    http
            .httpBasic(AbstractHttpConfigurer::disable);

    http
            .authorizeHttpRequests(
                    (authorizeRequest) -> authorizeRequest
                            .requestMatchers("/", "/api/users/login", "/api/users/loginProc", "/api/users/join" , "/api/users/joinProc").permitAll()
                            .requestMatchers("/api/users/admin").hasRole("ADMIN")
                            .anyRequest().authenticated()
            );

    // jwt 구현으로 인해 수동적으로 loginFilter를 만들었기 때문에 시큐리티에서 filter를 인식하도록 해야함
    // 필터등록은 addFilter가 있는데
    // addFilterAt() 은 그 자리에 등록 , addFilterAfter()는 특정 필터 뒤에 등록 , addFilterBefore() 특정 필터 전에 등록
    // 첫번째 인자는 무슨 필터를, 두번째 인자는 위치
    // UsernamePasswordAuthenticationFilter의 역할을 수동적으로 만들어줬기 때문에 위치가 여기가 됨

    // 로그인 진행 url을 따로 만들어서 맵핑해두었다면
    LoginFilter loginFilter = new LoginFilter(authenticationManager(authenticationConfiguration));
    loginFilter.setFilterProcessesUrl("/api/users/loginProc");
    // 필터가 실행되기 위해 유입되는 경로

    http
            .addFilterAt(loginFilter, UsernamePasswordAuthenticationFilter.class);
   

    // 세션 설정. STATELESS 상태로 변경
    http
            .sessionManagement((session) -> session
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS));

    return http.build();
  }

  @Bean
  public BCryptPasswordEncoder bCryptPasswordEncoder(){
    return new BCryptPasswordEncoder();
  }
}
참고 사이트

개발자 유미