인증 필터 - 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
인증처리자 - 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를 추가할 때 사용한것이다.
'WEB > Security' 카테고리의 다른 글
실전프로젝트 - 인가 프로세스 DB 연동 서비스 계층 구현 (0) | 2023.01.20 |
---|---|
실전프로젝트 - 인가 프로세스 DB 연동 웹 계층 구현 (0) | 2023.01.16 |
스프링 시큐리티 주요 아키텍처 이해 (0) | 2023.01.10 |
스프링 시큐리티 기본 API 및 Filter 이해 (안겹치는것만) (0) | 2023.01.08 |
OAuth 2.0 Client + Resource Server + Authorization Server 연동 (0) | 2022.12.28 |