이 글은 정수원님의 Infrean 강의를 학습한 내용을 정리하여 작성합니다.
Ajax 인증 - DSL로 Config 설정하기
Custom DSLs
- AbstractHttpConfigurer
- 스프링 시큐리티 초기화 설정 클래스
- 필터, 핸들러, 메서드, 속성 등을 한 곳에 정의하여 처리할 수 있는 편리함 제공
- public void init(H http) throws Exception - 초기화
- public void configure(H http) - 설정
- HttpSecurity의 apply(C configurer) 메서드 사용
참고: DSL이란
도메인 특화 언어(Domain-specific language)는 특정한 도메인을 적용하는데 특화된 컴퓨터 언어이다.
이는 어느 도메인에서나 적용 가능한 범용 언어(General-purpose language)와는 반대되는 개념이다.
도메인 특화 언어에는 매우 넓은 다양성이 존재한다.
HTML과 같이 웹페이지 분야에서 널리 쓰이는 언어가 있는가 하면, GNU Emacs 와 XEmacs를 위한 Emacs Lisp와 같이 한정된 분야에서 사용되는 언어도 있다.
도메인 특화 언어는 또한 언어의 종류로 세분화될 수 있다.
그리고 도메인 특화 마크업 언어, 도메인 특화 모델링 언어(일반적으로는, 설명 언어), 도메인 특화 프로그래밍 언어를 포함한다.
특정한 목적을 가지는 컴퓨터 프로그래밍 언어는 컴퓨터 역사에서 항상 존재해 왔으나, "도메인 특화 언어"라는 용어는 도메인 특화 모델링의 수요 증가로 더 알려지게 되었다.
범용 언어와 도메인 특화 언어의 경계선은 그리 분명하진 않다.
한 언어가 특정 도메인을 위해 특화된 기능을 가지고 있을 지라도 넓게 적용될 수 있고, 역으로 원칙적으로는 넓은 적용을 위한 설계였을 지라도 특정 도메인에서만 주로 사용될 수도 있다.
예를 들어, 펄은 원래 텍스트 프로세싱과 글루(glue) 언어를 위해 개발되었지만, 범용 프로그래밍 언어로 사용되었다.
반대로, PostScript는 튜링 완전한 언어로 원칙상으로는 어떤 작업에서도 사용 가능하지만 실제로는 페이지 설명 언어로 매우 좁은 분야에서 사용된다.
참고: https://ko.wikipedia.org/wiki/%EB%8F%84%EB%A9%94%EC%9D%B8_%ED%8A%B9%ED%99%94_%EC%96%B8%EC%96%B4
- 스프링 시큐리티 공식 Custom DSLs 가이드
제공해주는 Custom DSLs 예시는 다음과 같다.
public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
private boolean flag;
@Override
public void init(HttpSecurity http) throws Exception {
// any method that adds another configurer
// must be done in the init method
http.csrf().disable();
}
@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.
MyFilter myFilter = context.getBean(MyFilter.class);
myFilter.setFlag(flag);
http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
}
public MyCustomDsl flag(boolean value) {
this.flag = value;
return this;
}
public static MyCustomDsl customDsl() {
return new MyCustomDsl();
}
}
- 이러한 Custom DSL은 다음과 같이 사용할 수 있다.
@EnableWebSecurity
public class Config {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.apply(customDsl())
.flag(true)
.and()
...;
return http.build();
}
}
- 코드는 다음 순서로 동작한다.
- Config 구성 메서드의 코드가 호출된다.
- MyCustomDsl의 init() 메서드가 호출된다.
- MyCustomDsl의 configure() 메서드가 호출된다.
- 원하는 경우 SpringFactories를 사용하여 기본적으로 HttpSecurity에 MyCustomDsl을 추가할 수 있다.
- 예를 들어 다음 내용을 포함하는 META-INF/spring.factories라는 이름의 리소스를 classpath에 생성할 수 있다.
META-INF/spring.factories
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyCustomDsl
- 기본값을 비활성화하려는 사용자는 이를 명시적으로 사용할 수 있습니다.
@EnableWebSecurity
public class Config {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.apply(customDsl()).disable()
...;
return http.build();
}
}
구현
AjaxLoginConfigurer
public final class AjaxLoginConfigurer<H extends HttpSecurityBuilder<H>> extends
AbstractAuthenticationFilterConfigurer<H, AjaxLoginConfigurer<H>, AjaxLoginProcessingFilter> {
private AuthenticationSuccessHandler successHandler;
private AuthenticationFailureHandler failureHandler;
private AuthenticationManager authenticationManager;
public AjaxLoginConfigurer() {
super(new AjaxLoginProcessingFilter(), null);
}
@Override
public void init(H http) throws Exception {
super.init(http);
}
@Override
public void configure(H http) {
if(authenticationManager == null){
authenticationManager = http.getSharedObject(AuthenticationManager.class);
}
getAuthenticationFilter().setAuthenticationManager(authenticationManager);
getAuthenticationFilter().setAuthenticationSuccessHandler(successHandler);
getAuthenticationFilter().setAuthenticationFailureHandler(failureHandler);
SessionAuthenticationStrategy sessionAuthenticationStrategy = http
.getSharedObject(SessionAuthenticationStrategy.class);
if (sessionAuthenticationStrategy != null) {
getAuthenticationFilter().setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
}
RememberMeServices rememberMeServices = http
.getSharedObject(RememberMeServices.class);
if (rememberMeServices != null) {
getAuthenticationFilter().setRememberMeServices(rememberMeServices);
}
http.setSharedObject(AjaxLoginProcessingFilter.class,getAuthenticationFilter());
http.addFilterBefore(getAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
public AjaxLoginConfigurer<H> successHandlerAjax(AuthenticationSuccessHandler successHandler) {
this.successHandler = successHandler;
return this;
}
public AjaxLoginConfigurer<H> failureHandlerAjax(AuthenticationFailureHandler authenticationFailureHandler) {
this.failureHandler = authenticationFailureHandler;
return this;
}
public AjaxLoginConfigurer<H> setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
return this;
}
@Override
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
return new AntPathRequestMatcher(loginProcessingUrl, "POST");
}
}
- 스프링 시큐리티 공식 문서에서는 AbstractHttpConfigurer 클래스를 상속했지만 여기서는 AbstractAuthenticationFilterConfigurer를 상속 받도록 한다.
- AbstractAuthenticationFilterConfigurer는 AbstractHttpConfigure 클래스를 상속 받는다.
- 생성자
- 이전에 생성한 AjaxLoginProcessingFilter를 생성해 부모 클래스에 전달한다.
- configure()
- http는 HttpSecurity 객체이다.
- http.getSharedObject()
- HttpSecurity 는 공유 객체를 저장하고 가져올 수 있는 저장소 개념의 API가 제공된다.
- getAuthenticationFilter()
- 생성자에서 부모에게 AjaxLoginProcessingFilter를 전달했으므로 AjaxLoginProcessingFilter 객체가 반환된다.
- authenticationFilter에 authenticatonManger, successHandler, failureHandler를 각각 등록한다.
- session과 rememberMe 관련 기능을 설정한다.
- http.setSharedObject()
- HttpSecurity 공유 객체에 AjaxLoginProcessingFilter를 저장한다.
- http.addFilterBefore()
- 우리가 직접 생성한 AjaxLoginProcessingFilter를 UsernamePasswordAuthenticationFilter 앞에 등록한다.
- createLoginProcessingUrlMatcher()
- RequestMatcher 클래스에 loginPrcessingUrl을 POST 방식으로 등록하기 위해 사용한다.
- 나머지 메서드
- 공식 문서의 다음 예제처럼 우리가 생성한 Custom DSL에 별도의 파라미터로 전달하기 위해 사용한다.
- 현재 AjaxLoginConfigurer에서는 successHandler, failureHandler, authenticationManager를 받아오기 위해 사용된다.
AjaxSecurityConfig 설정
@Configuration
@Order(0)
public class AjaxSecurityConfig {
...
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.authorizeHttpRequests((authz) -> authz
.antMatchers("/api/messages").hasRole("MANAGER")
.anyRequest().authenticated()
);
http
.addFilterBefore(ajaxLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
http
.exceptionHandling()
.authenticationEntryPoint(new AjaxLoginAuthenticationEntryPoint())
.accessDeniedHandler(ajaxAccessDeniedHandler());
http.csrf().disable();
customConfigurerAjax(http);
return http.build();
}
private void customConfigurerAjax(HttpSecurity http) throws Exception {
http
.apply(new AjaxLoginConfigurer<>())
.successHandlerAjax(ajaxAuthenticationSuccessHandler())
.failureHandlerAjax(ajaxAuthenticationFailureHandler())
.setAuthenticationManager(authenticationManager(authenticationConfiguration))
.loginProcessingUrl("/api/login");
}
}
- AjaxLoginConfigurer 파일을 설정 파일에 등록하였다.
- AjaxLoginConfigurer 안에서 Filter와 Handler를 모두 등록하였으므로 AjaxSecurityConfig 파일 내부 Filter 설정은 삭제해도 된다.
실행
- 이전처럼 정상적으로 동작하는지 테스트가 필요하다.
AjaxLoginConfigurer
AjaxLoginConfigurer
- 서버를 실행하면 AjaxLoginConfigurer의 configure() 메소드가 정상적으로 동작하는 것을 확인할 수 있다.
포스트맨
결과
- 정상적으로 수행된다.
'스프링 시큐리티 > 실전프로젝트 - 인증 프로세스 Ajax 인증 구현' 카테고리의 다른 글
Ajax 로그인 구현 & CSRF 설정 (0) | 2023.02.14 |
---|---|
인증 및 인가 예외 처리 - AjaxLoginUrlAuthenticationEntryPoint, AjaxAccessDeniedHandler (0) | 2023.02.14 |
인증 핸들러 - AjaxAuthenticationSuccessHandler, AjaxAuthenticationFailureHandler (0) | 2023.02.14 |
인증 처리자 - AjaxAuthenticationProvider (0) | 2023.02.14 |
인증 필터 - AjaxAuthenticationFilter (0) | 2023.02.13 |