본문 바로가기
관리자

Programming-[Backend]/Spring Security

[스프링 시큐리티] [작성중] 5. 익명 사용자 인증 필터, 세션 제어 필터

728x90
반응형

 

1. AnonymousAuthenticationFilter

 

 

익명사용자도 Authentication 객체는 null이 아니다

 

앞서 대략적으로만 다뤘지만, 스프링 시큐리티는 인증이 됬다면 기본적으로 SecurityContextHolder 내부의 SecurityContext에 인증 정보가 담긴 Authentication 객체의 값이 존재해야하는 구조이다. 익명 사용자를 처리하는 필터는 AnonymousAuthenticationFilter이다. 이 필터는 Authentication의 값을 검사하고, 만약 사용자의 Authentication 정보가 아니라면, AnonymousAuthenticationToken을 생성하고 해당 토큰을 SecurityContext에 넣는다. 익명 사용자라 할지라도 Authentication 객체가 null이 아니라는 것을 기억하자

 

AbstractSecurityInterceptor는 보안 필터 중 맨 마지막에 위치하는 필터로 최종적으로 접근 가능 여부를 판단하는 필터이다. 여기서도 Authentication 객체가 없다면 AuthenticationCredentialsNotFoundException을 발생시킨다. 즉 처음에 살펴본 바와 같이 스프링 시큐리티 곳곳에서 Authentication이 null인지 검사한다.

protected InterceptorStatusToken beforeInvocation(Object object) {
    Assert.notNull(object, "Object was null");
    if (!this.getSecureObjectClass().isAssignableFrom(object.getClass())) {
        throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName() + " but AbstractSecurityInterceptor only configured to support secure objects of type: " + this.getSecureObjectClass());
    } else {
        Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
        if (CollectionUtils.isEmpty(attributes)) {
            Assert.isTrue(!this.rejectPublicInvocations, () -> {
                return "Secure object invocation " + object + " was denied as public invocations are not allowed via this interceptor. This indicates a configuration error because the rejectPublicInvocations property is set to 'true'";
            });
            if (this.logger.isDebugEnabled()) {
                this.logger.debug(LogMessage.format("Authorized public object %s", object));
            }

            this.publishEvent(new PublicInvocationEvent(object));
            return null;
        } else { //SecurityContextHolder에 Authentication이 null이라면 예외발생
            if (SecurityContextHolder.getContext().getAuthentication() == null) {
                this.credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes);
            }
            
            ....

 

 

 

AnonymousAuthenticationFilter 동작방식

 

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
    if (SecurityContextHolder.getContext().getAuthentication() == null) { //디버깅 포인트
        Authentication authentication = this.createAuthentication((HttpServletRequest)req);
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        context.setAuthentication(authentication);
        SecurityContextHolder.setContext(context);
        if (this.logger.isTraceEnabled()) {
            this.logger.trace(LogMessage.of(() -> {
                return "Set SecurityContextHolder to " + SecurityContextHolder.getContext().getAuthentication();
            }));
        } else { //정보가 없을때
            this.logger.debug("Set SecurityContextHolder to anonymous SecurityContext");
        }
    } else if (this.logger.isTraceEnabled()) {
        this.logger.trace(LogMessage.of(() -> {
            return "Did not set SecurityContextHolder since already authenticated " + SecurityContextHolder.getContext().getAuthentication();
        }));
    }

    chain.doFilter(req, res);
}

 

AnonymousAuthenticationFilter의 doFilter 부분을 살펴본다. 로그인 화면에 접속한 뒤, 해당 메서드에 디버깅 포인트를 잡고 루트 페이지로 새로고침을 통해 다시 접속해본다. 디버깅 포인트부터 한줄 씩 실행해나가면, createEmptyContext() 메서드를 통해 Authentication 객체가 없는 경우 빈 context를 만들고 거기에 AnonymousAuthenticationToken을 Authentication 객체로 넣어준다. 다음 그림의 AnonymousAuthenticationFilter의 선언부를 살펴보면, request에서 key, principal, authorities를 받아와서 생성하는 것을 확인할 수 있다. 이값들이 Authentication 객체로 생성되는 것이다.

 

여기서 key는 기본값이 "anonymousUser", authorities는 "ROLE_ANONYMOUS"라는 1개 값이 들어간다.

public class AnonymousAuthenticationFilter extends GenericFilterBean implements InitializingBean {
    private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource;
    private String key;
    private Object principal;
    private List<GrantedAuthority> authorities;

    public AnonymousAuthenticationFilter(String key) {
        this(key, "anonymousUser", AuthorityUtils.createAuthorityList(new String[]{"ROLE_ANONYMOUS"}));
    }

    public AnonymousAuthenticationFilter(String key, Object principal, List<GrantedAuthority> authorities) {
        this.authenticationDetailsSource = new WebAuthenticationDetailsSource();
        Assert.hasLength(key, "key cannot be null or empty");
        Assert.notNull(principal, "Anonymous authentication principal must be set");
        Assert.notNull(authorities, "Anonymous authorities must be set");
        this.key = key;
        this.principal = principal;
        this.authorities = authorities;
    }

 

 

AnonymousAuthenticationFilter 이전의 동작부터 추적해보려고 했으나, request에서 어떤 방식을 거쳐서 AnonymousAuthenticationFilter로 오게되는 것인지 자세히는 아직 모르겠다. 다만 앞에서 배운 것처럼 FilterChainProxy에 의해서 차례대로 필터를 타는데, 익명의 사용자인 경우 AnonymousAuthenticationFilter의 동작에서 처리된다는 것은 알겠다.

 

 


 

2. 세션 제어

 

사용자가 서버에서 설정해둔 웹 url로 접속하면 Session이 생성된다. 이번 파트에서는 이 세션과 관련한 제어 방식을 공부한다.

 

 

동시 세션 제어

 

최대 세션 허용 개수라는 개념이 있다. 같은 계정으로 동시에 로그인 시, 몇 개까지 접속을 허용할 것인지를 말한다. 만약 최대 세션 허용 개수가 1개일 때, 같은 계정으로 두 명의 사용자가 접속했을 때, 서버는 아래 2가지 방법으로 처리를 한다.

 

  • 기존 사용자 로그인 만료
  • 새로운 사용자 로그인 실패

 

 

세션 고정 보호

 

공격자가 자신의 SessionID를 다른 사용자에게 넘기고, 사용자가 서버에 접속 시 해당 세션을 이용하여 인증 성공 시, 공격자가 같은 세션으로 로그인하면 사용자의 이용정보가 공격자에게 공유되는 형태이다. 이런 세션 고정 보호를 위해서 서버에서는 인증 시마다 계속 세션을 새롭게 발행한다. 따라서 사용자가 공격자의 SessionId를 갖고 인증을 하더라도 새롭게 세션이 생성되므로 세션이 고정됨으로써 발생하는 정보 탈취의 문제를 방지할 수 있다.

 

.none, .changeSessionId, .migrateSession, newSession API

2개의 브라우저를 띄워놓고, A의 sessionId를 복사하여 B에 붙여넣는다. 그리고 B가 로그인 하면, A는 로그인 없이 루트 페이지로 이동만해도 인증 처리가 되는 것을 확인할 수 있다.

 

 

세션 생성 정책

세션 생싱 및 사용여부에 따라 사용할 수 있는 API가 정해져있다. 이중 특히 stateless 옵션은 세션을 사용하지 않는 JWT 방식 등에서 적용한다.

 

 

 

세션 관련 필터 소스 코드 분석 내용 정리하기

 


 

참조

 

1) 인프런 - 스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 정수원님 강의

https://www.inflearn.com/course/%EC%BD%94%EC%96%B4-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0

728x90
반응형