[데브코스] Spring Security - 3

Updated: Categories:

W16D2 - Session 관련 필터 / 인가처리에 대해 알아보자

SecurityContextPersistenceFilter

  • 로그인 후 세션을 통해 SecurityContext를 관리해주는 필터.
  • SecurityContextRepository 인터페이스 구현체를 통해 사용자의 SecurityContext를 가져오거나 갱신함
    • 인증 관련 필터 중 최상단에 위치 — 이미 인증된 사용자는 다시 로그인할 필요가 없음
    • SecurityContext가 존재하지 않는다면, empty SecurityContext를 생성함
  • SecurityContextRepository의 default 구현체는 세션을 이용하는 HttpSessionSecurityContextRepository 클래스


SessionManagementFilter

  • 사용자의 세션을 관리하는 필터

Session-fixation attack

image

  • 악성 유저가 어떤 사용자의 로그인 전 세션을 탈취하여 가지고 있다면?
  • 해당 사용자가 로그인 했다면 악성 사용자는 해당 세션을 가지고 똑같이 인증이 가능함..!

Session-fixation protection

  • 위와 같은 세션 하이재킹을 막기 위해선 로그인 후에 세션을 새로 발급하면 된다.
  • 로그인 전 세션은 쓸모가 없어지므로 해당 공격에 효과적으로 대응이 가능하다.

image

필터 사용법

http.sessionManagement()
    .invalidSessionUrl(URL) // 유효하지 않은 세션 감지 시 지정된 URL로 리다이렉트
    .sessionFixation().보호전략()
    .sessionCreationPolicy(SessionCreationPolicy.생성전략)
    .maximumSessions(세션 )  // 동일 사용자의 최대 세션 수
        .maxSessionsPreventsLogin(bool값)   // 최대 세션 개수 도달시 로그인 가능 여부
        .and()
  • 세션 보호 전략 4가지 옵션
    • .none() : 아무것도 하지 않음 (세션을 그대로 유지함)
    • .newSession() : 새로운 세션을 만들고, 기존 데이터는 복제하지 않음
    • .migrateSession() : 새로운 세션을 만들고, 데이터를 모두 복제함
    • .changeSessionId() : 새로운 세션을 만들지 않지만, session-fixation 공격을 방어함 (단, servlet 3.1 이상에서만 지원)
  • 세션 생성 전략 4가지 옵션
    • IF_REQUIRED : 필요시 생성함 (기본값)
    • NEVER : Spring Security에서는 세션을 생성하지 않지만, 세션이 존재하면 사용함
    • STATELESS : 세션을 완전히 사용하지 않음 (JWT 인증이 사용되는 REST API 서비스에 적합)
    • ALWAYS : 항상 세션을 사용함


Authorization

  • 인증된 사용자와 권한을 매핑해야 함
    • Spring Security에서는 보통 ‘역할’이라고 함 (예: ROLE_USER, ROLE_ADMIN, ROLE_ANONYMOUS)
  • 보호되는 리소스에 대한 권한 확인
    • 관리자 권한을 가진 사용자만 관리자 페이지에 접근 가능

image

FilterSecurityInterceptor

  • 필터체인 가장 마지막에 위치하며 사용자의 권한과 요청한 리소스의 권한을 비교, 접근가능 여부를 판단함
    • 실질적으로 접근 허용 여부 판단은 AccessDecisionManager 인터페이스 구현체에서 이루어짐
  • 해당 필터가 호출된다면 사용자는 이미 인증이 완료되어 있으므로 권한 목록을 가져올 수 있다!
    • 익명 사용자도 인증이 완료된 것으로 간주하며, ROLE_ANONYMOUS 권한을 가짐
  • 보호되는 리소스에서 요구하는 권한 정보는 SecurityMetadataSource 인터페이스를 통해 ConfigAttribute 타입으로 가져옴

image

AccessDecisionManager

  • 사용자가 갖고 있는 권한과 리소스에서 요구하는 권한을 확인하고, 사용자가 적절한 권한을 갖고 있지 않다면 접근 거부 처리함
  • AccessDecisionVoter 목록을 갖고 있음
  • AccessDecisionVoter들의 투표(vote)결과를 취합하고, 접근 승인 여부를 결정하는 3가지 구현체를 제공함
    • AffirmativeBased : AccessDecisionVoter가 승인하면 이전에 거부된 내용과 관계없이 접근이 승인됨 (기본값)
    • ConsensusBased : 다수의 AccessDecisionVoter가 승인하면 접근이 승인됨
    • UnanimousBased : 모든 AccessDecisionVoter가 만장일치로 승인해야 접근이 승인됨

WebExpressionVoter

  • SpEL(Spring Expression Language) 표현식을 사용해 접근 승인 여부에 대한 규칙을 지정할 수 있음
  • SpEL 표현식 처리를 위해 DefaultWebSecurityExpressionHandler 그리고 WebSecurityExpressionRoot 구현에 의존함
  • WebSecurityExpressionRoot 클래스는 SpEL 표현식에서 사용할수 있는 다양한 메소드를 제공
표현식 설명 예시
hasIpAddress(ipAddress) 요청 IP 주소가 특정 IP 주소 또는 특정 대역에 해당하는지 확인 access=”hasRole(‘ADMIN’) and hasIpAddress(‘192.168.1.0/24’)”
hasRole(String role) 사용자가 특정 role을 갖고 있는지 확인 access=”hasRole(‘USER’)”
hasAnyRole(String… roles) 사용자가 주어진 role 목록 중 매칭되는 role을 갖고 있는지 확인 access=”hasAnyRole(‘USER’, ‘ADMIN’)”
hasAuthority(String authority) 사용자가 특정 권한을 갖고 있는지 확인 access=”hasAuthority(‘ROLE_USER’)”
hasAnyAuthority(String… authorities) 사용자가 주어진 권한 목록 중 매칭되는 권한을 갖고 있는지 확인 access=”hasAnyAuthority(‘ROLE_USER’, ‘ROLE_ADMIN’)”
permitAll 모든 사용자에 대해 접근 허용 access=”permitAll”
denyAll 모든 사용자에 대해 접근 거부 access=”denyAll”
isAnonymous() 사용자가 익명 사용자인지 확인 access=”isAnonymous()”
isRememberMe() 사용자가 remember-me 를 통해 인증되었는지 확인 access=”isRememberMe() or hasRole(‘USER’)”
isAuthenticated() 사용자가 인증되었는지 확인 access=”isAuthenticated()”
isFullyAuthenticated() 사용자가 익명 사용자가 아니고, remember-me 인증 사용자도 아닌지 확인 access=”isFullyAuthenticated() and hasRole(‘USER’)”
  • 기본적으로 제공하는 SpEL 표현식 외에 커스텀 표현식을 생성할 수 있다.

권한 규칙 커스텀

  • 1. 표현식 추가 : WebSecurityExpressionRoot를 상속받은 클래스를 생성하고 커스텀 규칙 메소드를 추가한다
    • 해당 메소드는 반환값이 bool값이어야 함.
public class CustomWebSecurityExpressionRoot extends WebSecurityExpressionRoot {
    ...
    커스텀 규칙 메소드
    ...
}
  • 2. 핸들러 추가 : AbstractSecurityExpressionHandler<FilterInvocation>를 상속받은 클래스를 생성한다
    • 표현식에 커스텀한 WebSecurityExpressionRoot를 지정해주기 위함.
    • DefaultWebSecurityExpressionHandler와 거의 유사하게 만들어줌
public class CustomWebSecurityExpressionHandler extends AbstractSecurityExpressionHandler<FilterInvocation> {

    private final AuthenticationTrustResolver trustResolver;
    private final String defaultRolePrefix;

    public CustomWebSecurityExpressionHandler(AuthenticationTrustResolver trustResolver, String defaultRolePrefix) {
        this.trustResolver = trustResolver;
        this.defaultRolePrefix = defaultRolePrefix;
    }

    @Override
    protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, FilterInvocation fi) {
        CustomWebSecurityExpressionRoot root = new CustomWebSecurityExpressionRoot(authentication, fi);
        root.setPermissionEvaluator(this.getPermissionEvaluator());
        root.setTrustResolver(this.trustResolver);
        root.setRoleHierarchy(this.getRoleHierarchy());
        root.setDefaultRolePrefix(this.defaultRolePrefix);
        return root;
    }
}
  • 3. 커스텀 핸들러 팩토리 메소드를 만들고 필터 체이닝에서 추가해준다.
public SecurityExpressionHandler<FilterInvocation> securityExpressionHandler() {
    return new CustomWebSecurityExpressionHandler(
            new AuthenticationTrustResolverImpl(),
            "ROLE_"
    );
}
http.authorizeRequests()
    ...
    .expressionHandler(securityExpressionHandler())
  • 원래는 자동으로 DefaultWebSecurityExpressionHandler로 지정된다.