Ajax 요청방식에 대한 스프링 시큐티티의 전반적인 순차과정 중 처음인 AjaxAuthenticationFilter를 구현한다.
- AbstractAuthenticationProcessingFilter 상속.
- 작동조건을 AntPathRequestMatcher 로 매칭, 요청방식이 Ajax 라면 작동
- AjaxAuthenticationToken 구현객체를 만들어 AuthenticationManager에게 전달
구현
- AbstaractAuthenticationProcessingFilter 의 생성자를 가져와 AntPathRequestMatcher 에 경로를 설정한 것을 주목
- isAjax 메서드를 만들어 X-Request-With 헤더 정보에 XMLHttpRequest 값이 있는지 검사(null-Safety 를 위해 String 문자열을 앞으로 배치)
- jackson 의 ObjectMapper 로 Json 데이터를 DTO 객체로 객체화
- SpringFramework 의 StringUtil 로 검사
- CustomAjaxAuthenticationToken 생성 후 AuthenticationManager 에게 넘겨줌 (getAuthenticationManager().authenticate())
import com.example.securityapp.domain.AccountDTO;
import com.example.securityapp.security.token.AjaxAuthenticationToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.StringUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
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-Request-With"))) {
return true;
}
return false;
}
}
CustomAjaxAuthentiationToken 구현
- 기본 골격은 UsernameAuthenticationFilter 를 사용
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import java.util.Collection;
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;
}
}
등록
- http.addFilterBefore(AjaxAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
- AjaxAuthenticationFilter 를 Bean 으로 등록
- 등록과정에서 AuthenticationManager 가 필요하므로 authenticationManagerBean 메서드를 재정의하여 super 클래스로 부터 Bean 등록
@Bean
public AjaxLoginProcessingFilter ajaxLoginProcessingFilter() throws Exception {
AjaxLoginProcessingFilter ajaxLoginProcessingFilter = new AjaxLoginProcessingFilter();
ajaxLoginProcessingFilter.setAuthenticationManager(authenticationManagerBean());
return ajaxLoginProcessingFilter;
}
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {//
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/","/users","user/login/**","/login*").permitAll()
.antMatchers("/mypage").hasRole("USER")
.antMatchers("/messages").hasRole("MANAGER")
.antMatchers("/config").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/")
.successHandler(customAuthenticationHandler)
.failureHandler(customAuthenticationFailureHandler)
.loginProcessingUrl("/login_proc")
.permitAll()
.and()
.exceptionHandling()
.accessDeniedHandler(customAccessDeniedHandler())
.and()
.addFilterBefore(ajaxLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)// 해당필터 앞에 위치시킴
.csrf().disable();
}
설정클래스의 필터 추가 API들
- addFilterBefore(filter,ClassType) : 추가하고자하는 필터가 기존 필터 앞에 위치할 떄
- addFilter(filter,ClassType) : 추가하려고 하는 필터가 가장 마지막에 위치
- addFilterAfter(filter,ClassType) : 추가하고자 한 필터가 기존필터 뒤에 위치할때
- addFilterAt(filter,ClassType) : 기존 필터위치를 대체하고자 할때
추가사항
- 인텔리제이 에서는 HttpRequest 파일로 api 요청을 할 수 있게 지원한다.
- HttpRequest 파일 생성 후 원하는 요청방식과 URL, 데이터 정보를 설정
- 보낼 데이터를 정의하고 보내면 된다.
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection) or
# paste cURL into the file and request will be converted to HTTP Request format.
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
POST http://localhost:8080/api/login
Content-Type: application/json
X-Requested-With: XMLHtttpRequest
{
"username":"user",
"password":"1111"
}
###
이후 작동과정에서 AuthenticationProvider가 AjaxAuthenticationToken을 인식하지 못하므로 AjaxAuthenticationProvider를 구현해서 AuthenticationManager 에 등록해야한다.
'springframework > 시작하자SpringSecurity' 카테고리의 다른 글
37.AjaxAuthenticationSuccessHandler,AjaxAuthenticationFailureHandler (0) | 2020.09.29 |
---|---|
36.CustomAjaxAuthenticationProvider (0) | 2020.09.29 |
34. Ajax Authentication Flow (0) | 2020.09.29 |
33.CustomAccessDeniedHandler (0) | 2020.09.29 |
32.CustomAuthenticationFailureHanlder (0) | 2020.09.29 |