springframework/시작하자SpringSecurity

36.CustomAjaxAuthenticationProvider

Jungsoomin :) 2020. 9. 29. 21:58

본 강의를 학습하는데 어려움이 따라 디버깅을 하여 해결한 그대로 기술한다.

 

AuthenticationProvider 구현클래스는 Ajax 방식이라도 AuthenticationProvider 상속클래스와 동일

 

일단 학습과정에서 사용한 SecurityConfig 클래스 는 2개이므로, 2개의 클래스에 대해 SecurityFilterChain 이 만들어질 것이고, 나는 순서(@Order)를 정해줘야한다.

  • CustomAuthenticationProvider Bean 을 지정.
  • 매서드를 재정의 하여 AuthenticationManager 추출
  • configure(AuthenticationManagerBuilder)AuthenticationProvider 등록
  • CutomAjaxAuthenticationFilter Bean 을 생성. 이 과정에서 AuthenticationManager 를 등록시킴 ( Setter )
  • configure(HttpSecurity)addFilterBefore() 메서드로 UsernamepasswordAuthenticationFitler 전에 CustomAjaxAuthenticationFitler 등록 
@Configuration
@EnableWebSecurity
@Order(0)
public class AjaxSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public AuthenticationProvider ajaxAuthenticationProvider(){
        return new AjaxAuthenticationProvider();
    }

    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(ajaxAuthenticationProvider());
    }

    @Bean
    public AjaxLoginProcessingFilter ajaxLoginProcessingFilter() throws Exception {
        AjaxLoginProcessingFilter ajaxLoginProcessingFilter = new AjaxLoginProcessingFilter();
        ajaxLoginProcessingFilter.setAuthenticationManager(authenticationManagerBean());
        return ajaxLoginProcessingFilter;
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .antMatcher("/api/**")
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(ajaxLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)// 해당필터 앞에 위치시킴
                .csrf().disable();

    }
}

해당클래스는 저번 글에 기술한 AbsractAuthenticationProcessingFilter 구현클래스의 내용이다.

public class AjaxLoginProcessingFilter extends AbstractAuthenticationProcessingFilter {

    private ObjectMapper objectMapper = new ObjectMapper();

    public AjaxLoginProcessingFilter() {//생성자에 AntPathRequestMatcher 를 넘겨 경로검사
        super(new AntPathRequestMatcher("/api/login"));
    }

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

        if (!isAjax(request)) {
            throw new IllegalStateException("Authentication is not supported");
        }

        //jackson 의 ObjectMapper 로 Json 데이터를 객체화
        AccountDTO accountDTO = objectMapper.readValue(request.getReader(), AccountDTO.class);

        if(StringUtils.isEmpty(accountDTO.getUsername()) || StringUtils.isEmpty(accountDTO.getPassword())){
            throw new IllegalArgumentException("Username or Password is empty");
        }

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


        return getAuthenticationManager().authenticate(ajaxAuthenticationToken);//AuthenticationManager 에게 넘겨줌
    }

    private boolean isAjax(HttpServletRequest request) {

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

CustomAjaxAuthenticationProvider 

CustomAjaxAuthenticationProvider 객체는 AuthenticationProvider 를 구현 중이다.

  • CustomUserDetailsService 주입, PasswordEncoder 주입
  • CustomAjaxAuthenticationToken 인지 검사( Support )
  • 인증 과정 중 CustomUserDetails 를 받아 CustomAjaxAuthenitcationToken 을 생성하고 리턴
  • 설정클래스 에서 등록한 AjaxAuthenticationFilter 에 등록한 AuthenticationManager 가 이를 받게
public class AjaxAuthenticationProvider implements AuthenticationProvider {
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    @Transactional
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = (String)authentication.getCredentials();

        AccountContext accountContext = (AccountContext)userDetailsService.loadUserByUsername(username);

        if(!passwordEncoder.matches(password, accountContext.getPassword())){
            throw new BadCredentialsException("BadCredentialsException");
        }

        AjaxAuthenticationToken ajaxAuthenticationToken = new AjaxAuthenticationToken(accountContext.getAccount(), null, accountContext.getAuthorities());

        return ajaxAuthenticationToken;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(AjaxAuthenticationToken.class);
    }
}

CustomAuthenticationToken

  • 내용은 UsernameAthenticatonToken 에서 받아온다.
public class AjaxAuthenticationToken extends AbstractAuthenticationToken { // 정보 값은 UsernamePasswordAuthenticationToken 을 참조한다.

    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;


    private final Object principal;
    private Object credentials;


    public AjaxAuthenticationToken(Object principal, Object credentials) { // 인증전 생성
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        setAuthenticated(false);
    }


    public AjaxAuthenticationToken(Object principal, Object credentials,
                                   Collection<? extends GrantedAuthority> authorities) { // 인증 성공시 생성
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true); // must use super, as we override
    }


    public Object getCredentials() {
        return this.credentials;
    }

    public Object getPrincipal() {
        return this.principal;
    }

    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        if (isAuthenticated) {
            throw new IllegalArgumentException(
                    "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        }

        super.setAuthenticated(false);
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
        credentials = null;
    }

}

어떤 과정을 거치며 디버깅을 했는가.

  1. SecurityFilterChain이 제대로 등록되었는지 FilterChainProxy 에서 확인 
  2.  CustomAjaxAuthenticationFilterFilterChainProxy 의 필터 명단에 있는지 확인
  3. 브레이크 포인트를 찍어 AuthenticationManagerAjaxAuthenticationProviderparent 명단에서 찾는지 확인
  4. AjaxAuthenticationProvider 에 브레이크 포인트를 찍어 정상동작하는지 확인 ,  Support 메서드가 참이 되는지 확인
  5. AuthenticationManager에서 CustomAjaxAuthenticaitonToken 을 인식하는지 확인
  6. 인증이 완료되는지 확인.

이유는 SecurityConfig 클래스 중 하나에 CustomAjaxAuthenticationFilter Bean 이 생성되고 있었던 점

FilterChainProxySecurityFilterChainAjaxCustomAuthenticationProvider가 아니라 다른 CustomAuthenticationProvider가 등록되는 점을 해결.