본 강의를 학습하는데 어려움이 따라 디버깅을 하여 해결한 그대로 기술한다.
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;
}
}
어떤 과정을 거치며 디버깅을 했는가.
- SecurityFilterChain이 제대로 등록되었는지 FilterChainProxy 에서 확인
- CustomAjaxAuthenticationFilter 가 FilterChainProxy 의 필터 명단에 있는지 확인
- 브레이크 포인트를 찍어 AuthenticationManager가 AjaxAuthenticationProvider 를 parent 명단에서 찾는지 확인
- AjaxAuthenticationProvider 에 브레이크 포인트를 찍어 정상동작하는지 확인 , Support 메서드가 참이 되는지 확인
- AuthenticationManager에서 CustomAjaxAuthenticaitonToken 을 인식하는지 확인
- 인증이 완료되는지 확인.
이유는 SecurityConfig 클래스 중 하나에 CustomAjaxAuthenticationFilter Bean 이 생성되고 있었던 점
FilterChainProxy 의 SecurityFilterChain 중 AjaxCustomAuthenticationProvider가 아니라 다른 CustomAuthenticationProvider가 등록되는 점을 해결.
'springframework > 시작하자SpringSecurity' 카테고리의 다른 글
38.AjaxLoginUrlAuthenticationEntryPoint, AjaxAccessDeniedHandler (0) | 2020.09.30 |
---|---|
37.AjaxAuthenticationSuccessHandler,AjaxAuthenticationFailureHandler (0) | 2020.09.29 |
35.AjaxAuthenticationFilter (0) | 2020.09.29 |
34. Ajax Authentication Flow (0) | 2020.09.29 |
33.CustomAccessDeniedHandler (0) | 2020.09.29 |