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
출처