UsernamePasswordAuthenticationToken의 내용을 복사해 구현하도록 한다.
public class AjaxAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal;
private Object credentials;
/**
* 인증을 받기 전 사용자가 입력하는 정보를 담는 생성자
* (username, password)
*/
public AjaxAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
/**
* 인증 이후 인증에 성공한 결과를 담는 생성자
* @param principal 인증에 성공한 user 객체
* @param credentials 패스워드
* @param authorities 권한 정보
*/
public AjaxAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
public Object getCredentials() {
return this.credentials;
}
public Object getPrincipal() {
return this.principal;
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
Assert.isTrue(!isAuthenticated, "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
super.setAuthenticated(false);
}
public void eraseCredentials() {
super.eraseCredentials();
this.credentials = null;
}
}
첫 번째 생성자는 인증을 받기 전 사용자의 아이디와 패스워드를 담는 생성자
두 번째 생성자는 인증 후 인증에 성공한 결과를 담는 생성자이다.
필터 생성
AjaxLoginProcessingFilter
AbstractAuthenticationProcessingFilter 상속
필터 발동 조건
AntPathRequestMatcher("/api/login")로 요청정보와 매칭하고 요청 방식이 Ajax이면 필터 작동
public class AjaxLoginProcessingFilter extends AbstractAuthenticationProcessingFilter {
private ObjectMapper objectMapper = new ObjectMapper();
public AjaxLoginProcessingFilter() {
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");
}
AccountDto accountDto = objectMapper.readValue(request.getReader(), AccountDto.class);
if (!StringUtils.hasText(accountDto.getUsername()) || !StringUtils.hasText(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;
}
}
요청정보 매칭
요청방식 검사
이때, 요청방식이 Ajax인지 검사하는 기준은 자유롭게 하면 된다.
여기서는 사용자가 HttpHeader로 "XMLHTTPRequest" 정보를 담아 보내준다 가정하였다.
isAjax 함수를 호출해 요청방식이 Ajax 방식이 맞다면 필터가 동작하도록 한다.
아니라면 예외를 발생시킨다.
AuthenticationManager에게 인증 객체(AjaxAuthenticationToken) 전달
인증 받기 전 이므로 AjaxAuthenticationToken의 첫 번째 생성자를 사용해 AjaxAuthenticationToken 객체를 생성해 AuthenticationManager에게 인증 처리를 위임한다.
참고 위에서 사용하는 getAuthenticationManager()는 상속받은 AbstractAuthenticationProcessingFilter가 제공하는 함수이다.
설정 추가
직접 생성한 AjaxLoginProcessingFilter가 동작하도록 설정을 추가해야 한다.
주의! SecurityConfig 파일에서 다음과 같이 AjaxLoginProcessingFilter를 Bean으로 등록하고자 할 때, AuthenticationManager를 설정하지 않으면 오류가 발생한다. 이러한 문제를 해결하기 위해서는 다음과 같이 AuthenticationManager를 등록해주어야 한다. AuthenticationManager 는 초기화 때 생성되어 기본적으로 DaoAuthenticationProvider 와 같은 객체를 가지고 있다. 그리고 UsernamePasswordAuthenticationFilter 와 같은 클래스에서 참조하고 있다. 그렇다면 ajaxAuthenticationProvider 도 초기화때 생성된 AuthenticationManager 에서 추가해 주어야 한다. 그렇기 때문에 AjaxLoginProcessingFilter 에서 참조하고 있는 AuthenticationManager 에 ajaxAuthenticationProvider 를 추가해 주어야 정상 동작하게 된다.
HTTP 메소드 중 PATCH, POST, PUT, DELETE로 요청 시 반드시 CSRF 토큰명과 토큰값으로 요청해야 한다. 하지만 현재 포스트맨으로 테스트를 수행하므로 CSRF 토큰을 요청할 수 없다. 그러므로 잠시 CSRF 기능을 disable 상태로 변경한다.
CSRF 참조: https://yenjjun187.tistory.com/581
참고 필터를 추가할 때 위에서 사용한 addFilterBefore() 함수 말고도 다른 함수들이 존재한다.
- addFilterBefore() : 추가하고자 하는 필터를 기존 필터 앞에 위치시키고자 할 때 - addFilter() : 추가하고자 하는 필터를 마지막에 위치시킬 때 - addFilterAfter() : 기존 필터 다음에 위치시키고자 할 때 - addFilterAt() : 기존 필터 위치를 대체하고자 할 때
실행
실제 ajax 방식으로 인증 요청을 위해서는 화면을 구성해 자바스크립트의 JQuery 같은 기술을 사용해야 한다.