Spring Security | 인증 | Authentication

Spring Security를 구성하는 중요한 클래스 중에 하나에 Authentication이 존재한다. 이 클래스는 인증된 사용자 정보(사용자 이름과 부여된 권한 목록)이 포함되어 있다.

AuthenticationProvider 인증이 성공하면, AuthenticationProvider는 인증된 Authentication 객체를 생성하여 반환한다(isAuthenticated()는 true를 돌려 준다). 이 후에 인증된 Authentication 객체가 권한 체크 등의 후속 처리에서 참조된다.

Authentication의 얻는 방법과 참조 가능한 정보

SecurityContextHolderSampleServlet.java

package sample.spring.security.servlet;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetails;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import static java.util.stream.Collectors.*;

@WebServlet("/authentication")
public class SecurityContextHolderSampleServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        System.out.println("[authorities]");
        System.out.println("  " + auth.getAuthorities().stream()
                                    .map(GrantedAuthority::getAuthority)
                                    .collect(joining("\n  ")));

        WebAuthenticationDetails details = (WebAuthenticationDetails) auth.getDetails();
        System.out.println("[details]");
        System.out.println("  IP Address : " + details.getRemoteAddress());
        System.out.println("  Session ID : " + details.getSessionId());

        UserDetails principal = (UserDetails) auth.getPrincipal();
        System.out.println("[principal]");
        System.out.println("  username : " + principal.getUsername());
        System.out.println("  password : " + principal.getPassword());

        System.out.println("[credentials]");
        System.out.println("  " + auth.getCredentials());
    }
}

실행 결과

[authorities]
  USER
[details]
  IP Address : 0:0:0:0:0:0:0:1
  Session ID : 0225051BCDFE2C34D55DF4FA9D9685C2
[principal]
  username : hoge
  password : null
[credentials]
  null
  • 현재 요청에 연결된 Authentication을 얻으려면 SecurityContextHolder.getContext(). getAuthentication()으로 얻는다.
    • SecurityContextHolder.getContext()는 현재 요청에 연결된 SecurityContext를 반환한다.
  • Authentication.getAuthorities()으로 현재 로그인 한 사용자에게 부여된 권한(GrantedAuthority 컬렉션)을 얻을 수 있다.
  • Authentication.getDetails()는 기본적으로 WebAuthenticationDetails을 반환한다.
    • 이 클래스에서 IP 주소 및 세션 ID를 얻을 수 있다.
  • Authentication.getPrincipal ()에서 로그인 사용자 UserDetails를 얻을 수있다.
  • Authentication.getCredentials()는 사용자 인증에 이용하는 정보(일반적이라면 로그인 비밀번호)를 반환한다.

안전을 위해 패스워드 정보는 로그인 성공 후에 AuthenticationManager(ProviderManager)에 의해 명시적으로 삭제(null 설정)되어 있다. 옵션(eraseCredentialsAfterAuthentication)으로 삭제되지 않도록 하는 것도 가능하다.

Authentication 저장 장소

Authentication을 보유하고 있는 SecurityContext는 인증 후에 기본적으로는 HttpSession에 저장된다.

다음에 같은 세션으로 접근이 되면 HttpSession에 저장되어 있는 SecurityContext가 얻어 지고 SecurityContextHolder에 저장된다.

이 SecurityContextHolder는 기본적으로 ThreadLocal을 사용하여 static 필드에 SecurityContext를 저장한다. 따라서 현재의 요청에 연결된 SecurityContext를 글로벌로 얻을 수 있도록 되어있다.

엄밀하게는 SecurityContextHolder가 SecurityContextHolderStrategy의 인스턴스를 보유하고 있으며,이 인터페이스의 구현으로 SecurityContext를 ThreadLocal에 저장한다. ThreadLocalSecurityContextHolderStrategy이 사용되고 있다.

덧붙여서, SecurityContextHolder에 SecurityContext의 출력이나 입력은 요청별 ThreadLocal의 클리어는 SecurityContextPersistenceFilter가 하고 있다.

ThreadLocal 이외의 저장 방법

SecurityContextHolderStrategy의 구현에는 ThreadLocalSecurityContextHolderStrategy 이외에도 나머지 2개의 클래스가 존재한다.

GlobalSecurityContextHolderStrategy

응용 프로그램에서 단 하나의 SecurityContext를 저장한다.

이 클래스는 예를 들어 독립 실행형(stand-alone) 응용 프로그램 등에서 사용한다. 독립 실행형 응용 프로그램의 경우는 실행중인 사용자가 유일한 사용자가 될지도 모른다. 이 경우는 스레드를 여러 개를 만들었다 해도, 그 모두 동일한 인증 정보를 공유해야 할지도 모른다.

InheritableThreadLocalSecurityContextHolderStrategy

새로운 스레드가 생성된 경우는 부모 스레드 SecurityContext를 공유한다.

이 클래스는 여러 사용자가 이용할 수 있지만, 각 사용자의 처리에서 새로 스레드를 생성하고 백그라운드 프로세스를 실행시키는 경우에 사용한다. 이 경우, 스레드는 나뉘어져 있지만, 인증 정보는 부모 스레드의 사용자 정보를 인계하여야 한다.

어떤 저장 방법을 사용하거나 다음 중 하나의 방법으로 변경할 수있다.

시스템 속성으로 지정

시스템 속성 spring.security.strategy를 JVM을 시작 옵션에 전달하는 방식으로 지정할 수 있다.

Tomcat 시작시 지정하는 경우

$ set JAVA_OPTS=-Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL

$ cd %TOMCAT_HOME%\bin

$ startup.bat

static 메소드로 지정

SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);

SecurityContextHolder에 setStrategyName(String)라는 메소드가 준비되어 있다. 인수에는 같은 SecurityContextHolder에 정의되어 있는 상수를 지정하면 된다.