본문 바로가기

Spring/Security

Spring Security 4: Security Filter #3

인가 처리

인가 -> 권한을 부여 특정 사용자만 특정 작업을 할 수 있게 함

인가 처리는 두 개의 작업으로 구분

  1. 인증된 사용자와 권한을 매핑해야 함
    • 예시) ROLE_USER, ROLE_ADMIN, ROLE_ANONYMOUS
  2. 보호되는 리소스에 대한 권한 확인
    • 관리자 권한을 가진 사용자만 관리자 페이지에 접근 가능

FilterSecurityInterceptor

  • 필터 체인 상에서 가장 마지막에 위치하며, 사용자가 갖고 있는 권한과 리소스에서 요구하는 권한을 취합하여 접근을 허용할지 결정함
    • 실질적으로 접근 허용 여부 판단은 AccessDecisionManager 인터페이스 구현체에서 이루어짐
  • 해당 필터가 호출되는 시점에서 사용자는 이미 AccessDecisionManager에서 인증이 완료된 상태며, Authentication 인터페이스의 getAuthorities() 메소드를 통해 인증된 사용자의 권한 목록을 가져올수 있음
    • 익명 사용자도 인증이 완료된 것으로 간주하며, ROLE_ANONYMOUS 권한을 갖음

AccessDecisionManager

  • 사용자가 갖고 있는 권한과 리소스에서 요구하는 권한을 확인하고, 사용자가 적절한 권한을 갖고 있지 않다면 접근 거부 처리함
  • AccessDecisionVoter 목록을 갖고 있으며 AccessDecisionManager의 구현체를 통해 접근 승인 여부를 결정
  • AccessDecisionVoter들의 투표(vote)결과를 취합하고, 접근 승인 여부를 결정하는 3가지 구현체를 제공함
  • 자세한 내요은 해당 클래스 내에 메소드를 확인하면 흐름을 이해할 수 있음

AuthorizationFilter

  • Spring Security 버전이 6.1로 올라감에 따라 새롭게 간소화된 FilterSecurityInterceptor와 AccessDecisionManager가 아닌 AuthorizationManager API와 AuthorizationFilter를 사용한다.
  • 마찬가지로 필터 체인 상에서 가장 마지막에 위치하며, FilterSecurityInterceptor와 역할은 동일하다.

AuthenticationManager

  • AccessDecisionManager와는 비교도 안되게 적용 방법이 간단하다.
  • 커스텀해서 인증을 필터링 하고 싶다면 AuthenticationManager의 구현체 중 하나인 WebExpressionAuthorizationManager의 코드를 보면 쉽게 작성 가능하다.
  • 권한을 계층적으로 적용 가능하다.

권한 계층 AuthenticationManager에 적용한 코드


.requestMatchers(antMatcher("/me")).access(new HierarchyBasedAuthorizationManager(roleHierarchy())) // 커스텀한 AuthenticationManager를 이런식으로 적용 가능


@Bean
    public RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
        return roleHierarchy;
    }/*
       계층형 권한 설정 스프링 시큐리티 버전 6.0에 추가된 내용.
       계층형 권한 설정을 통해 ROLE_ADMIN이 ROLE_USER보다 상위 권한이라는 것을 설정함.
     */


    static class HierarchyBasedAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {

        private final RoleHierarchy roleHierarchy;

        public HierarchyBasedAuthorizationManager(RoleHierarchy roleHierarchy) {
            this.roleHierarchy = roleHierarchy;
        }

        @Override
        public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext object) {
            var userAuthorities = authentication.get().getAuthorities();
            var reachableAuthorities = roleHierarchy.getReachableGrantedAuthorities(userAuthorities);

            var hasUserRole = reachableAuthorities.stream()
                    .anyMatch(auth -> "ROLE_USER".equals(auth.getAuthority()));

            return new AuthorizationDecision(hasUserRole);
        }
    }

아이디의 마지막 숫자가 짝수인 경우만 승인 접근 권한이 있도록 설정한 AuthenticationManager


.requestMatchers(antMatcher("/admin")).access(allOf(hasRole("ADMIN"), fullyAuthenticated(), new CustomAuthorizationManager())) // 이런식으로 여러개의 AuthenticationManager 설정 가능


static class CustomAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
        private static final Pattern PATTERN = Pattern.compile("[0-9]+$");

        @Override
        public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext object) {
            var user = (User) authentication.get().getPrincipal();
            var userName = user.getUsername();
            var matcher = PATTERN.matcher(userName);
            if (matcher.find()) {
                var num = Integer.parseInt(matcher.group());
                if (num % 2 == 0) {
                    return new AuthorizationDecision(true);
                }
            }
            return new AuthorizationDecision(false);
        }
    }