WEB/Security

실전프로젝트 - 인증 프로세스 Ajax 인증 구현

Tony Lim 2023. 1. 15. 14:35
728x90


인증 필터 - AjaxAuthenticationFilter

AbstractAuthenticationProcessingFilter 상속

필터 작동 조건 = AntPathRequestMatcher("/api/login") 로 요청 정보와 매칭하고 요청 방식이 Ajax이면 필터 작동

public class AjaxLoginProcessFilter extends AbstractAuthenticationProcessingFilter {

    private ObjectMapper objectMapper = new ObjectMapper();


    public AjaxLoginProcessFilter() {
        super(new AntPathRequestMatcher("/api/login"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {

        if (!isAjax(request))
            throw new IllegalArgumentException("Authentication is not supported");

        AccountDto accountDto = objectMapper.readValue(request.getReader(), AccountDto.class);
        if (!StringUtils.hasLength(accountDto.getUsername())  || !StringUtils.hasLength(accountDto.getPassword()))
            throw new IllegalArgumentException("Username or Password is empty");

        AjaxAuthenticationToken ajaxAuthenticationToken = new AjaxAuthenticationToken(accountDto.getUsername(), accountDto.getPassword());

        return getAuthenticationManager().authenticate(ajaxAuthenticationToken);
    }

    private boolean isAjax(HttpServletRequest request) {

        if("XMLHttpRequest".equals(request.getHeader("X-Requested-With")))
            return true;
        return false;
    }
}

filter에서 사용할 AuthenticationManager를 주입받아야한다.

HttpSecuirty가 build될시에 init configure를 호출하는 시점에 나의 필터에도 spring에서 applicationContext에 사전에 등록한 AuthenticationManager를 주입하고 싶다.

public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
    private boolean flag;

    @Override
    public void init(HttpSecurity http) throws Exception {
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        ApplicationContext context = http.getSharedObject(ApplicationContext.class);

        // here we lookup from the ApplicationContext. You can also just create a new instance.
        AjaxLoginProcessFilter filter = new AjaxLoginProcessFilter();
        filter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
    }

    public MyCustomDsl flag(boolean value) {
        this.flag = value;
        return this;
    }

    public static MyCustomDsl customDsl() {
        return new MyCustomDsl();
    }
}
http.apply(MyCustomDsl.customDsl());

https://stackoverflow.com/questions/812415/why-is-springs-applicationcontext-getbean-considered-bad

 

Why is Spring's ApplicationContext.getBean considered bad?

I asked a general Spring question: Auto-cast Spring Beans and had multiple people respond that calling Spring's ApplicationContext.getBean() should be avoided as much as possible. Why is that? ...

stackoverflow.com


인증처리자 - AjaxAuthenticationProvider

http
        .authenticationProvider(ajaxAuthenticationProvider())
        .authenticationProvider(customAuthenticationProvider());

Provider를 아무것도 등록 안하면 formLogin 에서 configure에 apply된 DaoAuthenticationProvider가 기본으로 등록되고

@Bean으로는 하나의 Custom한 AuthenticationProvider만 등록이 가능하다. ApplicationContext에서 AuthenticationProvider.class로 bean을 가져오는데 2개 이상 등록하면 해당 로직을 타지 않는다.

위에처럼 등록하는게 best인듯하다.


인증 핸들러 - AjaxAuthenticationSuccessHandler , AjaxAuthenticationFailureHandler

public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
    private boolean flag;

    @Override
    public void init(HttpSecurity http) throws Exception {
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        ApplicationContext context = http.getSharedObject(ApplicationContext.class);

        // here we lookup from the ApplicationContext. You can also just create a new instance.
        AjaxLoginProcessFilter filter = new AjaxLoginProcessFilter();
        filter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        filter.setAuthenticationSuccessHandler(new AjaxAuthenticationSuccessHandler());
        filter.setAuthenticationFailureHandler(new AjaxAuthenticationFailureHandler());
        http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
    }

configure 메소드에서 만든 handler들을 추가한다.


인증 및 인가 예외 처리 - AjaxLoginUrlAuthenticationEntryPoint, AjaxAccessDeniedHandler

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

    http.csrf().disable();
    http.apply(MyCustomDsl.customDsl());

    http
            .antMatcher("/api/**")
            .authorizeRequests()
            .antMatchers("/", "/users", "user/login/**", "/login*").permitAll()
            .antMatchers("/mypage").hasRole("USER")
            .antMatchers("/message").hasRole("MANAGER")
            .antMatchers("/config").hasRole("ADMIN")
            .anyRequest().authenticated();

    http
            .authenticationProvider(ajaxAuthenticationProvider());


    http
            .exceptionHandling()
            .authenticationEntryPoint(new AjaxLoginAuthenticationEntryPoint())
            .accessDeniedHandler(new AjaxAccessDeniedHandler());

    return http.build();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

    http.csrf().disable();

    http
            .authorizeRequests()
            .antMatchers("/", "/users", "user/login/**", "/login").permitAll()
            .antMatchers("/mypage").hasRole("USER")
            .antMatchers("/message").hasRole("MANAGER")
            .antMatchers("/config").hasRole("ADMIN")
            .anyRequest().authenticated();

    http
            .formLogin()
            .loginPage("/login")
            .loginProcessingUrl("/login_proc")
            .authenticationDetailsSource(authenticationDetailsSource)
            .defaultSuccessUrl("/")
            .successHandler(authenticationSuccessHandler)
            .failureHandler(authenticationFailureHandler)
            .permitAll();

    http
            .exceptionHandling()
            .accessDeniedHandler(accessDeniedHandler());
    http
            .authenticationProvider(customAuthenticationProvider());

    return http.build();

여러개의 SecurityFilterChain을 등록하면 FilterChainProxy에서 antMatcher에 적힌것을보고 비교하여 알맞은 체인을 타게 하는데 아무것도 안적으면 anyRequest로 다 통과된다.

이렇게 bean을 등록하는 순서를 바꿔서 anyRequest는 맨 마지막 순서에 놓도록하자


Ajax Custom DSLs 구현하기

Custom DSLs

AbstractHttpConfigurer = 스프링 시큐리티 초기화 설정 클래스

필터, 핸들러, 메서드, 속성 등을 한곳에서 정의하여 처리할 수 있는 편리함 제공

public void init(HttpSecurity http) throws Exception - 초기화
public void configure(HttpSecurity http) - 설정

위에서 AJaxFilter를 추가할 때 사용한것이다.

 

 

 

728x90