Spring/Spring Security

[Spring Security] 4-1. Authorization: Architecture

noahkim_ 2025. 7. 3. 00:49

1. Authorities

GrantedAuthority

  • 인증된 사용자가 가지고 있는 권한을 나타내는 객체
  • 단순 문자열로 권한을 표현함
  • Authentication 객체 안에 리스트 형태로 저장됨

 

복잡한 권한 구조

  • 복잡한 권한 구조일 경우, 단순 문자열로 표현하기 어려움
  • 이런 경우, AuthorizationManager가 그 객체의 권한 부여 책임을 담당함
    • Authorities의 getAuthority()는 null을 반환함

 

예시) 특정 계좌에만 접근 가능한 권한

더보기

권한 객체

public class AccountAccessAuthority implements GrantedAuthority {

    private final Long accountId;

    public AccountAccessAuthority(Long accountId) {
        this.accountId = accountId;
    }

    public Long getAccountId() {
        return accountId;
    }

    @Override
    public String getAuthority() {
        return null; // 문자열 권한이 아닌, 객체 기반이므로 null
    }
}

 

AuthorizationManager

@Component
public class AccountAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
        Authentication auth = authentication.get();
        HttpServletRequest request = context.getRequest();

        String accountIdParam = request.getParameter("accountId");
        if (accountIdParam == null) return new AuthorizationDecision(false);

        Long requestedId = Long.valueOf(accountIdParam);

        return new AuthorizationDecision(
            auth.getAuthorities().stream()
                .filter(a -> a instanceof AccountAccessAuthority)
                .map(a -> ((AccountAccessAuthority) a).getAccountId())
                .anyMatch(id -> id.equals(requestedId))
        );
    }
}

 

SecurityFilterChain에 적용

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http, AccountAuthorizationManager accountAuthz) throws Exception {
    http
        .authorizeHttpRequests(authz -> authz
            .requestMatchers("/account/view").access(accountAuthz)
            .anyRequest().authenticated()
        )
        .formLogin(withDefaults());

    return http.build();
}

 

ROLE_접두사

  • Spring Security는 기본적으로 모든 "역할 기반 권한"에 ROLE_이라는 접두사를 붙여서 인식함

 

예시) USER

더보기
@PreAuthorize("hasRole('USER')")
  • "ROLE_USER"라는 GrantedAuthority가 있어야 통과됨

 

  • 접두사 커스터마이징 지원

 

예) GrantedAuthorityDefaults 빈 등록

더보기
@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
    return new GrantedAuthorityDefaults("MYPREFIX_");
}
  • "ROLE_"대신 "MYPREFIX_"로 변경됨
  • static으로 선언되어야 적용됨 (Spring Security 보다 먼저 등록되어야 하므로)

 

2. Invocation Handling

  • 보안 객체에 대해 접근 제어를 수행하는 방식 (메서드 호출 or 웹 요청 등)
  • 인터셉터 방식으로 동작함

 

The AuthorizationManager

  • 접근 허용 여부를 결정하는 구성요소
  • 기존의 AccessDecisionManager와 AccessDecisionVoter를 대체함

 

코드) AuthorizationManager

더보기
@FunctionalInterface
public interface AuthorizationManager<T> {

    default void verify(Supplier<Authentication> authentication, T object) {
        AuthorizationDecision decision = check(authentication, object);
        if (decision != null && !decision.isGranted()) {
            throw new AccessDeniedException("Access Denied");
        }
    }

    @Nullable
    AuthorizationDecision check(Supplier<Authentication> authentication, T object);

}
  • verify(): 내부적으로 check() 호출. 부정 결정이면 AccessDeniedException 예외 발생
  • check(): 인증 정보와 보안 객체를 받아 AuthorizationDecision 객체 반환
  • secureObject가 MethodInvocation이라면, 메서드 파라미터에 있는 객체에 대해 작업할 권한이 있는지 확인하는 보안 로직을 구현할 수 있음

 

Delegate-based AuthorizationManager Implementations

  • 여러개의 AuthorizationManager를 조합하거나, 특정 요청에 따라 위임할 수 있음

 

클래스
클래스명 설명
AuthorityAuthorizationManager
특정 권한을 보유한 인증 객체만 허용
AuthenticatedAuthorizationManager
익명, Remember-Me, 완전 인증 사용자를 구분해서 권한 부여
RequestMatcherDelegatingAuthorizationManager
요청 URL/패턴에 따라 다른 AuthorizationManager에게 위임함.

 

 

인터셉터
인터셉터명 설명
AuthorizationManagerBeforeMethodInterceptor
메서드 실행 이전에 권한 체크
AuthorizationManagerAfterMethodInterceptor
메서드 실행 이후 반환값까지 체크

 

3. Adapting AccessDecisionManager and AccessDecisionVoters

  • 기존의 AccessDecisionManager와 AccessDecisionVoter 방식을 점진적으로 AuthorizationManager로 대체할 수 있음
  • SecurityFilterChain 구성 시 이 어댑터들을 등록하면 바로 사용할 수 있음

 

예시) AccessDecisionManagerAuthorizationManagerAdapter

더보기
@Component
public class AccessDecisionManagerAuthorizationManagerAdapter implements AuthorizationManager {
    private final AccessDecisionManager accessDecisionManager;
    private final SecurityMetadataSource securityMetadataSource;

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, Object object) {
        try {
            Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
            this.accessDecisionManager.decide(authentication.get(), object, attributes);
            return new AuthorizationDecision(true); // 접근 허용
        } catch (AccessDeniedException ex) {
            return new AuthorizationDecision(false); // 접근 거부
        }
    }

    @Override
    public void verify(Supplier<Authentication> authentication, Object object) {
        Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
        this.accessDecisionManager.decide(authentication.get(), object, attributes);
    }
}

 

예시) AccessDecisionVoterAuthorizationManagerAdapter

더보기
@Component
public class AccessDecisionVoterAuthorizationManagerAdapter implements AuthorizationManager {
    private final AccessDecisionVoter accessDecisionVoter;
    private final SecurityMetadataSource securityMetadataSource;

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, Object object) {
        Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
        int decision = this.accessDecisionVoter.vote(authentication.get(), object, attributes);

        switch (decision) {
            case ACCESS_GRANTED:
                return new AuthorizationDecision(true); // 허용
            case ACCESS_DENIED:
                return new AuthorizationDecision(false); // 거부
            default:
                return null; // 중립
        }
    }
}

 

4. Hierarchical Roles

  • 상위 역할이 하위 역할의 권한을 자동으로 포함하도록 만드는 기능
  • 복잡한 권한 매핑을 단순화할 수 있음

 

예시) 관리자 & 일반 사용자

더보기
  • 관리자는 일반 사용자보다 더 많은 권한을 가짐
  • 즉, 관리자는 일반 사용자의 권한을 기본으로 가지고 있음
  • 모든 관리자에게 ROLE_USER를 추가하거나, 모든 접근 제어 조건에 ROLE_USER를 확인하게 될 경우, 복잡하고 유지보수가 힘듬

 

Role Hierarchy

  • 역할 간 포함 관계를 설정하도록 도와주는 클래스
  • 메서드 기반 보안에 반영시 설정이 필요함

 

예시) RoleHierarchyImpl

더보기
@Bean
static RoleHierarchy roleHierarchy() {
    return RoleHierarchyImpl.withDefaultRolePrefix()
        .role("ADMIN").implies("STAFF")
        .role("STAFF").implies("USER")
        .role("USER").implies("GUEST")
        .build();
}
  • ROLE_ADMIN > ROLE_STAFF > ROLE_USER > ROLE_GUEST
  • ROLE_ADMIN 사용자는 ROLE_STAFF, ROLE_USER, ROLE_GUEST 모두 포함하게 됨

 

예시) 메서드 보안 적용

더보기
@Bean
static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
    DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
    expressionHandler.setRoleHierarchy(roleHierarchy);
    return expressionHandler;
}
  • @PreAuthorize, @RolesAllowed, @Secured와 같은 메서드 기반 보안에 적용됨

 

5. Legacy Authorization Components

The AccessDecisionManager

Voting-Based AccessDecisionManager Implementations

 

 

출처