Security 6.1.2 REST 로그인 성공에 대한 고찰

Yoonhwan Kim·2023년 8월 28일
3

security

목록 보기
3/5

들어가기

안녕하세요. 작성을 시작하는건 8.29일이지만 최종 작성날짜는 언제일지 모르겠습니다.
설명이 많이 어색한 부분도 있고, 좀 정리가 안된 포인트가 몇 군데 존재합니다.

사실 정확하게 이렇게 해야합니다, 라고 하기에는 정확한 해답일지 모르겠습니다만,
그래도 이전 버전(5.7)과 동일하게 동작을 하도록 맞춰서 해결을 해보았습니다.

5.7 버전과 비교했습니다!. 참고 바랍니다.


들어가기에 앞서 이전 코드를 돌아보자.

직접 Rest API 형식으로 로그인을 구현한 코드


    public LoginAuthenticationFilter(final String defaultFilterProcessesUrl,
                                     final AuthenticationManager authenticationManager) {
        super(defaultFilterProcessesUrl, authenticationManager);
        // 로그인 이후 Context 생성 전략 설정
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response)
            throws AuthenticationException, IOException {

        String method = request.getMethod();

        if (!method.equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        ServletInputStream inputStream = request.getInputStream();

        LoginRequestDto loginRequestDto = new ObjectMapper().readValue(inputStream, LoginRequestDto.class);

        return this.getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(
                loginRequestDto.username,
                loginRequestDto.password
        ));
    }

    public record LoginRequestDto(
            String username,
            String password
    ){}

SecurityConfig

@Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        AuthenticationManagerBuilder sharedObject = http.getSharedObject(AuthenticationManagerBuilder.class);

        sharedObject.userDetailsService(this.userDetailsService);
        AuthenticationManager authenticationManager = sharedObject.build();

        http.authenticationManager(authenticationManager);

        http
            .csrf(AbstractHttpConfigurer::disable)
//            .formLogin(Customizer.withDefaults())
            .formLogin(AbstractHttpConfigurer::disable)
            .authorizeHttpRequests(authorizeRequest ->
                    authorizeRequest
                            .requestMatchers(
                                    antMatcher("/auth/**")
                            ).hasRole("MEMBER")
                            .requestMatchers(
                                    antMatcher("/h2-console/**")
                            ).permitAll()
                            .anyRequest().permitAll()
            )
            .addFilterAt(
                    this.abstractAuthenticationProcessingFilter(authenticationManager),
                    UsernamePasswordAuthenticationFilter.class)
            .headers(
                    headersConfigurer ->
                            headersConfigurer
                                    .frameOptions(
                                            HeadersConfigurer.FrameOptionsConfig::sameOrigin
                                    )
            );

        return http.build();
    }

1) 로그인 요청에 대한 형식을 변경했습니다.
2) UserDetailsService 클래스를 구현했습니다.


Security의 추가적인 변화

마지막으로 사용했던 버전은 5.7 입니다.
그리고 현재 작성중에 사용하는 버전은 6.1.2 버전입니다.

핵심 로직의 주요 변화 포인트는 다음과 같습니다.

1. SecurityContext를 담는 ThreadLocal 의 타입 변화
2. SecurityContextRepository 인터페이스의 실제 활용 코드

번외로 가장 중요한 클래스는 SecurityContextRepository의 구현체인
DelegatingSecurityContextRepository 클래스 입니다.

ThreadLocal의 변화

5.7 버전의 코드

final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {

	private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();

	@Override
	public void clearContext() {
		contextHolder.remove();
	}

	@Override
	public SecurityContext getContext() {
		SecurityContext ctx = contextHolder.get();
		if (ctx == null) {
			ctx = createEmptyContext();
			contextHolder.set(ctx);
		}
		return ctx;
	}

	@Override
	public void setContext(SecurityContext context) {
		Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
		contextHolder.set(context);
	}

	@Override
	public SecurityContext createEmptyContext() {
		return new SecurityContextImpl();
	}

}

6.1 버전의 코드

final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {

	private static final ThreadLocal<Supplier<SecurityContext>> contextHolder = new ThreadLocal<>();

	@Override
	public void clearContext() {
		contextHolder.remove();
	}

	@Override
	public SecurityContext getContext() {
		return getDeferredContext().get();
	}

	@Override
	public Supplier<SecurityContext> getDeferredContext() {
		Supplier<SecurityContext> result = contextHolder.get();
		if (result == null) {
			SecurityContext context = createEmptyContext();
			result = () -> context;
			contextHolder.set(result);
		}
		return result;
	}

	@Override
	public void setContext(SecurityContext context) {
		Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
		contextHolder.set(() -> context);
	}

	@Override
	public void setDeferredContext(Supplier<SecurityContext> deferredContext) {
		Assert.notNull(deferredContext, "Only non-null Supplier instances are permitted");
		Supplier<SecurityContext> notNullDeferredContext = () -> {
			SecurityContext result = deferredContext.get();
			Assert.notNull(result, "A Supplier<SecurityContext> returned null and is not allowed.");
			return result;
		};
		contextHolder.set(notNullDeferredContext);
	}

	@Override
	public SecurityContext createEmptyContext() {
		return new SecurityContextImpl();
	}

}

아무래도, 점차 Security 의 코드들이 함수형으로 변화해가면서 바뀐 코드가 아닐까 싶습니다. 이 코드에서는 DelegatingSecurityContextRepository 의 영향을 많이 받게됩니다.

SecurityContextRepository

기존에는 해당 인터페이스를 여러 추상 인증필터 (ex. AbstractAuthenticationProcessingFilter) 에서 사용하지 않았습니다.

모두 NullSecurityContextRepository 를 사용했는데요,

제가 본 6.1.2 부터는 직접적으로(?) 사용하게 됐습니다

예를 들면, AbstractAuthenticationProcessingFilterRememberMeAuthenticationFilter 와 같은 인증 필터들의 PropertySecurityContextRepository의 구현체를 직접 선언해서 쓰고 있는걸 볼 수 있습니다.

DelegatingSecurityContextRepository

	@Override
	public DeferredSecurityContext loadDeferredContext(HttpServletRequest request) {
		DeferredSecurityContext deferredSecurityContext = null;
		for (SecurityContextRepository delegate : this.delegates) {
			if (deferredSecurityContext == null) {
				deferredSecurityContext = delegate.loadDeferredContext(request);
			}
			else {
				DeferredSecurityContext next = delegate.loadDeferredContext(request);
				deferredSecurityContext = new DelegatingDeferredSecurityContext(deferredSecurityContext, next);
			}
		}
		return deferredSecurityContext;
	}

DelegatingSecurityContextRepository 에서 ThreadLocal에 등록할 SecurityContext 를 로딩을 합니다. DelegatingSecurityContextRepository 는 생성자를 만들기 위해서는 SecurityContextRepository 객체를 Collection 으로 받아서 여러개를 가집니다.

그 안에는 기본적으로 HttpSessionSecurityContextRepository , RequestAttributeSecurityContextRepository 이 2개의 클래스를 가지게 됩니다.

각각 하는 역할은 다음과 같습니다.

  • HttpSessionSecurityContextRepository - HttpSession 생성
  • RequestAttributeSecurityContextRepository - ServletRequest 에 인증객체를 속성에 추가

HttpSessionSecurityContextRepository

	@Override
	public DeferredSecurityContext loadDeferredContext(HttpServletRequest request) {
		Supplier<SecurityContext> supplier = () -> readSecurityContextFromSession(request.getSession(false));
		return new SupplierDeferredSecurityContext(supplier, this.securityContextHolderStrategy);
	}

DelegatingSecurityContextRepository 에서 loadDeferredContext(..) 에 의해 동작하는 코드입니다. 여기서 readSecurityContextFromSession 함수를 넘겨서
SupplierDeferredSecurityContext 객체를 전달합니다.

readSecurityContextFromSessionHttpSession 에서 세션을 읽어오는 역할을 하는 메서드입니다.

그 다음으로 DelegatingSecurityContextRepository 에서 deferredSecurityContext 변수값이 선언되었기 때문에 else 구문을 통해서 또 한번의 loadDeferredContext(..) 를 동작하는데 이 떄 구현체는 RequestAttributeSecurityContextRepository 입니다.

RequestAttritubteSecurityContextRepository

	@Override
	public DeferredSecurityContext loadDeferredContext(HttpServletRequest request) {
		Supplier<SecurityContext> supplier = () -> getContext(request);
		return new SupplierDeferredSecurityContext(supplier, this.securityContextHolderStrategy);
	}

getContext(..) 함수를 넘겨서 객체를 생성하고 반환합니다.

그러면 최종적으로 생성된 생성자 객체는 아래의 로직을 수행합니다.
SupplierDeferredSecurityContext 객체를 등록해두고 인증객체를 조회할때 사용합니다.

	SupplierDeferredSecurityContext(Supplier<SecurityContext> supplier, SecurityContextHolderStrategy strategy) {
		this.supplier = supplier;
		this.strategy = strategy;
	}

이 클래스를 직접 보시면 아시겠지만, 이러한 메서드가 존재합니다.

	@Override
	public SecurityContext get() {
		init();
		return this.securityContext;
	}
    
    private void init() {
		if (this.securityContext != null) {
			return;
		}

		this.securityContext = this.supplier.get();
		this.missingContext = (this.securityContext == null);
		if (this.missingContext) {
			this.securityContext = this.strategy.createEmptyContext();
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("Created %s", this.securityContext));
			}
		}
	}

Security 를 어느정도 디버깅을 해보신 분들이라면 익숙한 코드인데요,
이전버전에서의 SecurityContext.get() 과 같은 코드가 됩니다.

즉, init() 을 통해서 DelegatingSecurityContextRepository의 메서드에 의해 전달한 () -> readSecurityContextFromSession(...)() -> getContext(..) 메서드를 호출하여 결과를 받고 그 결과에 따라서 SecurityContext 객체가 반환됩니다.

최종적으로는 ThreadLocalSecurityContextHolderStrategysetDeferredContext 메서드를 통해 ThreadLocal 타입 변수에 저장이 됩니다.

그리고 전체 인증프로세스가 끝나는 과정에서 ThreadLocal 에 저장해둔 Supplier들을 사용해서 인증객체를 구해오고 그 객체들에 따라 익명의 사용자 인지 또는 인증 사용자 인지를 판단합니다.


로그인 요청 디버깅 해보기

사실 로그인 요청을 하게되면 먼저 위의 코드들이 설정이 미리 됩니다.
그래서 우리는 이미 설정되어 있는 Security 코드를 따라서 요청이 수행됩니다.

간단하게 요청을 살펴보자.

6.1 버전의 동작과정

그래도 어느정도 아신다고 가정하고 간단히 설명하면 ProviderManagerauthenticate 메서드를 통해서 인증이 수행될것입니다.

인증이 성공적으로 완료되면 AbstractAuthenticationProcessingFilter 에서 successfulAuthentication 이 수행됩니다.

이 구간 로직을 요약해봅시다.

1) SecurityContext 객체를 생성
2) SecurityContext 에 인증객체를 저장
3) SecurityContext 객체를 ThreadLocal에 설정한다.
4) SecurityContext를 생성 레포지토리에 저장한다.

아래의 사진은 3번 로직의 동작과정입니다.
기존의 Security 로직과 마찬가지로 contextHolder 에 인증객체를 저장하는것은 동일합니다.
이곳까지는 문제가 없습니다.

문제는 4번입니다.

위에서 작성한 DelegatingSecurityContextRepository 를 보시면 HttpSessionSecurityContextRepositoryRequestAttributeSecurityContextRepository 의 생성자를 가지고 있으며, 두 클래스는 서로 다른 역할을 하고 있는데요.

현재 간단하게 구현한 설정만으로 동작을 시켰을 경우 saveContext(..) 메서드는 RequestAttributeSecurityContextRepository 의 클래스에서만 동작을 하고 있습니다.


하지만.. 5.7 버전에서는?

Security 5.7 버전에서는 의외로 AbstractAuthenticationProcessingFiller 에서 securityContextRepository 의 구현체는 기본적으로 NullSecurityContextRepository 를 사용하고 있습니다.

// 5.7버전의 AbstractAuthenticationProcessingFilter
private SecurityContextRepository securityContextRepository = new NullSecurityContextRepository();

// 6.1.2버전의 AbstractAuthenticationProcessingFilter
private SecurityContextRepository securityContextRepository = new RequestAttributeSecurityContextRepository();

여담으로 SupplierDeferredSecurityContext 클래스는 5.8버전부터 사용하게 되었으므로 AbstractAuthenticationProcessingFiltersecurityContextRepository 는 현재 6.1버전에서 나타나는 것 처럼 RequestAttributeSecurityContextRepository 를 쓰고 있을 것으로 추측됩니다.

onAuthentcationSuccess(..) 메서드를 들여다 보면 handle()clearAuthenticationAttributes() 메서드를 사용하는데,

좀 더 내부적으로 살펴보게 되면 다음과 같습니다.

SaveContextOnUpdateOrErrorResponseWrapper 라는 추상 클래스에서 default 메서드로서 saveContext(..) 를 사용하고 있으며
이 메서드는 구현 클래스인 SaveToSessionResponseWrapper 클래스의 메서드를 사용합니다.

이때, 이 클래스는 사실 HttpSessionSecurityContextRepository 의 내부 클래스로서 정의되어 있으며 아래의 로직이 수행되고 HttpSession 에 인증 객체를 저장하는 로직이 수행됩니다.

@Override
		protected void saveContext(SecurityContext context) {
			if (isTransient(context)) {
				return;
			}
			final Authentication authentication = context.getAuthentication();
			if (isTransient(authentication)) {
				return;
			}
			HttpSession httpSession = this.request.getSession(false);
			String springSecurityContextKey = HttpSessionSecurityContextRepository.this.springSecurityContextKey;
			// See SEC-776
			if (authentication == null
					|| HttpSessionSecurityContextRepository.this.trustResolver.isAnonymous(authentication)) {
				if (httpSession != null && this.authBeforeExecution != null) {
					// SEC-1587 A non-anonymous context may still be in the session
					// SEC-1735 remove if the contextBeforeExecution was not anonymous
					httpSession.removeAttribute(springSecurityContextKey);
					this.isSaveContextInvoked = true;
				}
				if (this.logger.isDebugEnabled()) {
					if (authentication == null) {
						this.logger.debug("Did not store empty SecurityContext");
					}
					else {
						this.logger.debug("Did not store anonymous SecurityContext");
					}
				}
				return;
			}
			httpSession = (httpSession != null) ? httpSession : createNewSessionIfAllowed(context);
			// If HttpSession exists, store current SecurityContext but only if it has
			// actually changed in this thread (see SEC-37, SEC-1307, SEC-1528)
			if (httpSession != null) {
				// We may have a new session, so check also whether the context attribute
				// is set SEC-1561
				if (contextChanged(context) || httpSession.getAttribute(springSecurityContextKey) == null) {
					HttpSessionSecurityContextRepository.this.saveContextInHttpSession(context, this.request);
					this.isSaveContextInvoked = true;
				}
			}
		}

그리고 private 메서드인 saveContextInHttpSession(..) 에서 저장이 이루어집니다.

	private void saveContextInHttpSession(SecurityContext context, HttpServletRequest request) {
		if (isTransient(context) || isTransient(context.getAuthentication())) {
			return;
		}
		SecurityContext emptyContext = generateNewContext();
		if (emptyContext.equals(context)) {
			HttpSession session = request.getSession(false);
			removeContextFromSession(context, session);
		}
		else {
			boolean createSession = this.allowSessionCreation;
			HttpSession session = request.getSession(createSession);
			setContextInSession(context, session);
		}
	}

정리를 해보면 다음과 같습니다.

  • 5.7 버전에서는 HttpSessionSecurtiyContextRepository 를 통해서 HttpSession 에 인증객체를 저장한다
  • 6.1 버전에서는 추상 인증필터 (AbstractAuthenticationProcessingFilter ) 가 기본적으로 RequestAttributeSecurityContextRepository 를 사용한다.

버전에 따른 차이가 발생한 이유는?

이전에 제가 작성해둔 LoginAuthenticationFtilerUserDetailsService 구현 클래스만으로는 인증성공에 대한 결과를 얻을 수 없습니다.

최초로 SecurityFilterChain 을 등록할 떄 아래의 메서드를 수행하는데요, 여기서 분명히 등록을 하고 있습니다. SessionManagementConfigurer 클래스에서 세션 관리전략에 따라서 기본적으로 사용하는 SecurityContextRepository 가 달라집니다.

	@Override
	public void init(H http) {
		SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
		boolean stateless = isStateless();
		if (securityContextRepository == null) {
			if (stateless) {
				http.setSharedObject(SecurityContextRepository.class, new RequestAttributeSecurityContextRepository());
				this.sessionManagementSecurityContextRepository = new NullSecurityContextRepository();
			}
			else {
				HttpSessionSecurityContextRepository httpSecurityRepository = new HttpSessionSecurityContextRepository();
				httpSecurityRepository.setDisableUrlRewriting(!this.enableSessionUrlRewriting);
				httpSecurityRepository.setAllowSessionCreation(isAllowSessionCreation());
				AuthenticationTrustResolver trustResolver = http.getSharedObject(AuthenticationTrustResolver.class);
				if (trustResolver != null) {
					httpSecurityRepository.setTrustResolver(trustResolver);
				}
				this.sessionManagementSecurityContextRepository = httpSecurityRepository;
				DelegatingSecurityContextRepository defaultRepository = new DelegatingSecurityContextRepository(
						httpSecurityRepository, new RequestAttributeSecurityContextRepository());
				http.setSharedObject(SecurityContextRepository.class, defaultRepository);
			}
		}
		else {
			this.sessionManagementSecurityContextRepository = securityContextRepository;
		}
		RequestCache requestCache = http.getSharedObject(RequestCache.class);
		if (requestCache == null) {
			if (stateless) {
				http.setSharedObject(RequestCache.class, new NullRequestCache());
			}
		}
		http.setSharedObject(SessionAuthenticationStrategy.class, getSessionAuthenticationStrategy(http));
		http.setSharedObject(InvalidSessionStrategy.class, getInvalidSessionStrategy());
	}

즉, STATELESS 전략이라면 RequestAttributeSecurityContextRepository 그 외의 세션 생성전략이라면 DelegatingSecurityContextRepository 를 공통으로 등록합니다.

우리는 왜 STATELESS 가 아님에도 불구하고 인증시 공통으로 등록된 객체를 쓰지 못하는 걸까요?


여기서 부터는 사실 코드에 근거한 추측입니다.

그 이유를 이해하기 위해서 순수 FormLogin 설정을 통한 Security 동작과정을 보고 비교해봐야 할 거라고 판단했습니다.

기본적으로 formLogin 설정을 하게 될 경우 다음과 같은 로직으로 설정하게 될 것 입니다.

http
      .csrf(AbstractHttpConfigurer::disable)
      .formLogin(Customizer.withDefaults())

위 설정을 했을 때 동작과정을 봅시다.

생성자를 통해서 super 생성자를 호출을 하는데요,
여기서 보아야할 부분은 new UsernamePasswordAuthenticationFilter() 라고 생각합니다.

제 예상으로는 new UsernamePasswordAuthenticationFilter() 를 통해 만든 객체에 기본적으로 생성되는 Security 설정을 추가하고 있다고 보고있습니다.

윗 사진 (AbstractAuthenticationFilterConfigurer) 에서 this.authFilterSecurityContextRepository 를 등록합니다.

이 과정은 밑 사진(SecurityContextConfigurer) 에서 getSecurityContextRepository() 를 통해
일전에 등록한 SecurityContextRepository 의 공통객체를 뽑아서
new UsernamePasswordAuthenticationFilter() 객체에 등록하는 것이죠.


결과적으로 보면..

1) formLogin 은 새로운 UsernamePasswordAuthenticationFilter 를 만들어서 여태까지의 모든 Security 설정을 붙입니다.

2) 우리는 addFilterAt 메서드를 통해서 새롭게 우리가 구현한 AuthenticationFilter를 사용합니다.

즉, 자동설정에 의해 동작해야하는 UsernamePassowrdAuthenticationFilter 는 소멸되고 우리가 새롭게 커스텀한 AuthenticationFilter 를 따로 등록했기 때문에,
AbstractAuthenticationProcessingFilter 클래스의 기본 속성(변수)인 SecurityContextRepository 인터페이스 -> RequestAttributeSecurityContextRepository 클래스가 동작하게 되었고,

ServletRequest 객체에만 인증 객체가 들어갔기 때문에,
새롭게 변화된 ThreadLocalSecurityContextHolderStrategy 클래스에서 변수로 등록된 ThreadLocal 타입의 변수에 저장된 Supplier 를 뽑아 냈을 때,

최초로 들어간 () -> request.getSession(false) 의 값이 null 을 반환하게 되어 5.7버전에서의 동작과 다르게 AnonymousAuthenticaiton(익명사용자 객체)을 반환 받게 된 것이라고 볼 수 있습니다.

AnonymousAuthentication 이 발급되는 과정은 AnonymousAuthenticationFilter에서 확인이 가능합니다.


사실 그냥 봤을때는 이해하기가 쉽지는 않다고 생각합니다.
왜냐하면 5.76.1.2 버전은 다른 위치에서 동작과정의 이루어지게 변화했기 때문이죠,

하지만 기본 인증필터의 앞단인 AbstractAuthenticationProcessingFilter 에서부터 SecurityContextRepository의 구현체를 보고 즉시 발견을 할 수 있었기 때문에 디버깅을 잘 하시는 분들이라면 금방 즉시 해결할 수 있는 방안을 모색할 수 있었을 거라고 생각합니다..


최종 코드

LoginAuthenticationFilterDelegatingSecurityContextRepository 를 추가함으로써 일단락 하였습니다.

 public LoginAuthenticationFilter(final String defaultFilterProcessesUrl,
                                     final AuthenticationManager authenticationManager) {
        super(defaultFilterProcessesUrl, authenticationManager);
        // 로그인 이후 Context 생성 전략 설정
        setSecurityContextRepository(
                new DelegatingSecurityContextRepository(
                        new HttpSessionSecurityContextRepository(),
                        new RequestAttributeSecurityContextRepository()
                )
        );
    }

HttpSessionSecurityContextRepository 를 단일로 등록해도 문제는 없지만,
현재 Security 에서 왜 RequestAttributeSecurityContextRepository 를 사용했을지에 대해서는 SessionManagementConfigurerinit() 코드가 설명해 주고 있다고 생각합니다.

@Override
	public void init(H http) {
		SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
		boolean stateless = isStateless();
		if (securityContextRepository == null) {
			if (stateless) {
				http.setSharedObject(SecurityContextRepository.class, new RequestAttributeSecurityContextRepository());
				this.sessionManagementSecurityContextRepository = new NullSecurityContextRepository();
			}
			else {
				HttpSessionSecurityContextRepository httpSecurityRepository = new HttpSessionSecurityContextRepository();
				httpSecurityRepository.setDisableUrlRewriting(!this.enableSessionUrlRewriting);
				httpSecurityRepository.setAllowSessionCreation(isAllowSessionCreation());
				AuthenticationTrustResolver trustResolver = http.getSharedObject(AuthenticationTrustResolver.class);
				if (trustResolver != null) {
					httpSecurityRepository.setTrustResolver(trustResolver);
				}
				this.sessionManagementSecurityContextRepository = httpSecurityRepository;
				DelegatingSecurityContextRepository defaultRepository = new DelegatingSecurityContextRepository(
						httpSecurityRepository, new RequestAttributeSecurityContextRepository());
				http.setSharedObject(SecurityContextRepository.class, defaultRepository);
			}
		}
		else {
			this.sessionManagementSecurityContextRepository = securityContextRepository;
		}
		RequestCache requestCache = http.getSharedObject(RequestCache.class);
		if (requestCache == null) {
			if (stateless) {
				http.setSharedObject(RequestCache.class, new NullRequestCache());
			}
		}
		http.setSharedObject(SessionAuthenticationStrategy.class, getSessionAuthenticationStrategy(http));
		http.setSharedObject(InvalidSessionStrategy.class, getInvalidSessionStrategy());
	}

간단하게 말하자면,,

최근에는 SSR 방식의 구현을 잘 하지 않는 추세이기도 하고 Token 방식의 인증이 많아지다보니 stateless 방식을 많이쓰게되고, 이런 부분에 대해 따라가고자 몇몇 Filter 에서 기본적으로 RequestAttributeSecurityContextRepository 를 쓰게끔 설정한게 아닌가 라는 생각이 들었습니다.


마무리

6.1.x 버전 Security가 Release 된지 좀 됐지만, 해당 부분에 대한 글이 없기도 하고, 저 또한 해결을 했어야 했기때문에 해당 글을 작성하게 됐습니다.

제가 직접 볼떄는 좀 더 이해를 해보고자 봤던 코드들이 있지만 너무 많아서 그나마 이해에 도움이 되도록 추려서 올려봤습니다.

읽는분은 얼마 없으시겠지만 도움이 됐으면 좋겠습니다.

1개의 댓글

comment-user-thumbnail
2024년 9월 11일

세션 저장소가 적용이 안되는 이유 AbstractHttpConfigurer로 설정된 객체들은 정보들을shareObject라는 공유 변수로 일괄적으로 적용해줍니다. 그런데 커스텀한 필터만 적용하면 공유 변수로 넣어 줄 수 없습니다.
AbstractHttpConfigurer 클래스를 상속받아서 구현해서 httpBuilder에 등록하면 적용을 받을 수 있습니다.

답글 달기