Spring Security ‐ Authorization Architecture - taeyun-ham/andalos GitHub Wiki
Authorization Architecture
이 섹션에서는 인가에 적용되는 스프링 시큐리티 아키텍처를 설명합니다.
Authorities
인증은 모든 Authentication 구현이 부여된 권한을 나타내는 GrantedAuthority 객체 목록을 저장하는 방법에 대해 논의합니다. GrantedAuthority 객체는 AuthenticationManager에 의해 Authentication 객체에 삽입되며, 나중에 인가 결정을 내릴 때 AccessDecisionManager 인스턴스에 의해 읽힙니다.
GrantedAuthority 인터페이스는 단 하나의 메서드만을 가지고 있습니다:
String getAuthority();
이 메서드는 AuthorizationManager 인스턴스가 GrantedAuthority의 정확한 문자열 표현을 얻기 위해 사용됩니다. 문자열로 표현함으로써, GrantedAuthority는 대부분의 AuthorizationManager 구현에서 쉽게 "읽을" 수 있습니다. 만약 GrantedAuthority를 정확하게 문자열로 표현할 수 없다면, 그 GrantedAuthority는 "복잡한" 것으로 간주되며 getAuthority()는 null을 반환해야 합니다.
복잡한 GrantedAuthority의 예로는 다양한 고객 계좌 번호에 적용되는 작업 목록과 권한 임계값을 저장하는 구현이 있습니다. 이러한 복잡한 GrantedAuthority를 문자열로 표현하는 것은 상당히 어려울 수 있습니다. 결과적으로 getAuthority() 메서드는 null을 반환해야 합니다. 이는 AuthorizationManager가 특정 GrantedAuthority 구현의 내용을 이해하기 위해 지원해야 함을 나타냅니다.
스프링 시큐리티에는 하나의 구체적인 GrantedAuthority 구현인 SimpleGrantedAuthority가 포함되어 있습니다. 이 구현은 사용자가 지정한 어떤 문자열도 GrantedAuthority로 변환할 수 있게 합니다. 보안 아키텍처에 포함된 모든 AuthenticationProvider 인스턴스는 Authentication 객체를 채우기 위해 SimpleGrantedAuthority를 사용합니다.
기본적으로 역할 기반 인가 규칙에는 ROLE_ 접두사가 포함됩니다. 이는 "USER" 역할이 필요한 인가 규칙이 있다면 스프링 시큐리티는 기본적으로 "ROLE_USER"를 반환하는 GrantedAuthority#getAuthority를 찾게 됨을 의미합니다.
이것은 GrantedAuthorityDefaults를 사용하여 사용자 정의할 수 있습니다. GrantedAuthorityDefaults는 역할 기반 인가 규칙에 사용할 접두사를 사용자 정의할 수 있도록 합니다.
다른 접두사를 사용하도록 인가 규칙을 구성하려면 다음과 같이 GrantedAuthorityDefaults 빈을 노출할 수 있습니다: Custom MethodSecurityExpressionHandler
@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("MYPREFIX_");
}
GrantedAuthorityDefaults를 static 메소드를 사용하여 노출함으로써 스프링 시큐리티의 메소드 보안 @Configuration 클래스가 초기화되기 전에 스프링이 이를 게시하도록 보장할 수 있습니다.
Invocation Handling
스프링 시큐리티는 메소드 호출이나 웹 요청과 같은 보안 객체에 대한 접근을 제어하는 인터셉터를 제공합니다. 호출이 진행될 수 있는지에 대한 사전 호출 결정은 AuthorizationManager 인스턴스에 의해 이루어집니다. 또한 주어진 값이 반환될 수 있는지에 대한 사후 호출 결정도 AuthorizationManager 인스턴스에 의해 이루어집니다.
The AuthorizationManager
AuthorizationManager는 AccessDecisionManager와 AccessDecisionVoter를 대체합니다.
AccessDecisionManager나 AccessDecisionVoter를 커스텀한 애플리케이션은 AuthorizationManager 사용으로 변경하는 것이 권장됩니다.
AuthorizationManager는 스프링 시큐리티의 요청 기반, 메소드 기반, 메시지 기반 인가 컴포넌트에 의해 호출되며, 최종 접근 제어 결정을 내리는 책임을 가집니다. AuthorizationManager 인터페이스는 두 가지 메소드를 포함하고 있습니다:
AuthorizationDecision check(Supplier<Authentication> authentication, Object secureObject);
default AuthorizationDecision verify(Supplier<Authentication> authentication, Object secureObject)
throws AccessDeniedException {
// ...
}
AuthorizationManager의 check 메소드는 인가 결정을 내리기 위해 필요한 모든 관련 정보를 전달받습니다. 특히, 보안 객체를 전달함으로써 실제 보안 객체 호출에 포함된 인자들을 검사할 수 있습니다. 예를 들어, 보안 객체가 MethodInvocation이었다고 가정해 봅시다. MethodInvocation에서 어떤 Customer 인자를 쿼리하고, 그런 다음 AuthorizationManager에서 보안 로직을 구현하여 원칙적으로 그 고객에 대한 작업을 허용하는지 확인하는 것이 쉬울 것입니다. 구현체는 접근이 허용되면 긍정적인 AuthorizationDecision을 반환하고, 접근이 거부되면 부정적인 AuthorizationDecision을 반환하며, 결정을 유보할 때는 null AuthorizationDecision을 반환할 것으로 예상됩니다.
verify는 check를 호출한 후 부정적인 AuthorizationDecision의 경우 AccessDeniedException을 발생시킵니다.
Delegate-based AuthorizationManager Implementations
사용자는 모든 인가 측면을 제어하기 위해 자체 AuthorizationManager를 구현할 수 있지만, 스프링 시큐리티는 개별 AuthorizationManager와 협력할 수 있는 대리 AuthorizationManager를 제공합니다.
RequestMatcherDelegatingAuthorizationManager는 요청과 가장 적절한 대리 AuthorizationManager를 매칭합니다. 메소드 보안의 경우, AuthorizationManagerBeforeMethodInterceptor와 AuthorizationManagerAfterMethodInterceptor를 사용할 수 있습니다.
Authorization Manager Implementations는 관련 클래스를 보여줍니다.
이 접근 방식을 사용하면, AuthorizationManager 구현의 조합이 인가 결정에 대해 폴링될 수 있습니다.
AuthorityAuthorizationManager
스프링 시큐리티와 함께 제공되는 가장 일반적인 AuthorizationManager는 AuthorityAuthorizationManager입니다. 이는 현재 Authentication에서 찾을 권한의 주어진 집합으로 구성됩니다. Authentication이 구성된 권한 중 하나라도 포함하고 있으면 긍정적인 AuthorizationDecision을 반환합니다. 그렇지 않으면 부정적인 AuthorizationDecision을 반환합니다.
AuthenticatedAuthorizationManager
또 다른 매니저는 AuthenticatedAuthorizationManager입니다. 이는 익명, 완전 인증 및 '기억하기 인증(remember-me)'된 사용자를 구분하는 데 사용할 수 있습니다. 많은 사이트에서는 기억하기 인증 하에 일정한 제한된 접근을 허용하지만, 전체 접근을 위해 사용자가 로그인하여 자신의 신원을 확인하도록 요구합니다.
AuthorizationManagers
AuthorizationManagers에는 개별 AuthorizationManager를 더 복잡한 표현으로 구성하기 위한 유용한 정적 팩토리도 있습니다.
Custom Authorization Managers
당연히 사용자 지정 AuthorizationManager도 구현할 수 있으며, 원하는 거의 모든 접근 제어 로직을 포함시킬 수 있습니다. 이는 귀하의 애플리케이션(비즈니스 로직 관련)에 특정할 수도 있고, 일부 보안 관리 로직을 구현할 수도 있습니다. 예를 들어, Open Policy Agent나 자체 인가 데이터베이스를 조회할 수 있는 구현을 만들 수 있습니다.
스프링 웹사이트의 블로그 기사에서는 계정이 정지된 사용자에게 실시간으로 접근을 거부하는 방법을 설명하는 데 사용된 레거시 AccessDecisionVoter에 대해 알아볼 수 있습니다. AuthorizationManager를 구현함으로써 동일한 결과를 달성할 수 있습니다.
AccessDecisionManager와 AccessDecisionVoters 적용하기
AuthorizationManager 이전에, 스프링 시큐리티는 AccessDecisionManager와 AccessDecisionVoter를 제공했습니다.
예를 들어 이전 애플리케이션을 마이그레이션하는 경우, AccessDecisionManager나 AccessDecisionVoter를 호출하는 AuthorizationManager를 도입하는 것이 바람직할 수 있습니다.
기존의 AccessDecisionManager를 호출하려면 다음과 같이 할 수 있습니다:
AccessDecisionManager 적용하기
@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);
}
}
그리고 그것을 SecurityFilterChain에 연결하세요.
또는 AccessDecisionVoter만 호출하려면 다음과 같이 할 수 있습니다:
AccessDecisionVoter 적용하기
@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);
}
return null;
}
}
그리고 그것을 SecurityFilterChain에 연결하세요.
Hierarchical Roles
응용 프로그램의 특정 역할이 다른 역할을 자동으로 "포함"해야 한다는 것은 일반적인 요구 사항입니다. 예를 들어, "관리자"와 "사용자" 역할 개념이 있는 응용 프로그램에서 관리자가 일반 사용자가 할 수 있는 모든 것을 할 수 있게 하고 싶을 수 있습니다. 이를 달성하기 위해 모든 관리자 사용자에게 "사용자" 역할도 할당되도록 할 수 있습니다. 또는 "사용자" 역할을 요구하는 모든 접근 제약을 "관리자" 역할을 포함하도록 수정할 수 있습니다. 응용 프로그램에 다양한 역할이 많은 경우 이는 상당히 복잡해질 수 있습니다.
역할 계층 구조를 사용하면 어떤 역할(또는 권한)이 다른 것을 포함해야 하는지 구성할 수 있습니다. 스프링 시큐리티의 RoleVoter의 확장 버전인 RoleHierarchyVoter는 RoleHierarchy로 구성되며, 사용자에게 할당된 모든 "도달 가능한 권한"을 얻습니다. 전형적인 구성은 다음과 같을 수 있습니다:
계층적 역할 구성
@Bean
static RoleHierarchy roleHierarchy() {
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF\n" +
"ROLE_STAFF > ROLE_USER\n" +
"ROLE_USER > ROLE_GUEST");
return hierarchy;
}
// and, if using method security also add
@Bean
static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy);
return expressionHandler;
}
RoleHierarchy 빈 구성은 아직 @EnableMethodSecurity로 이전되지 않았습니다. 따라서 이 예제는 AccessDecisionVoter를 사용하고 있습니다. 메소드 보안에 대한 RoleHierarchy 지원이 필요한 경우, github.com/spring-projects/spring-security/issues/12783 이슈가 완료될 때까지 @EnableGlobalMethodSecurity를 계속 사용하십시오.
여기에는 ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST와 같이 네 가지 역할의 계층 구조가 있습니다. ROLE_ADMIN으로 인증된 사용자는 보안 제약 조건이 위의 RoleHierarchyVoter를 호출하도록 적응된 AuthorizationManager에 대해 평가될 때 네 가지 역할을 모두 가진 것처럼 행동할 것입니다. '>' 기호는 "포함한다"는 의미로 생각할 수 있습니다.
역할 계층은 애플리케이션의 접근 제어 구성 데이터를 간소화하고 사용자에게 할당해야 하는 권한 수를 줄이는 편리한 수단을 제공합니다. 더 복잡한 요구 사항의 경우, 애플리케이션에서 요구하는 특정 접근 권한과 사용자에게 할당된 역할 간의 논리적 매핑을 정의하고 사용자 정보를 로드할 때 두 가지를 변환하고자 할 수 있습니다.
Legacy Authorization Components
시큐리티에는 몇 가지 레거시 컴포넌트가 포함되어 있습니다. 아직 제거되지 않았기 때문에, 역사적인 목적을 위해 문서가 포함되어 있습니다. 권장되는 대체 제품은 위에 나열되어 있습니다.
The AccessDecisionManager
AccessDecisionManager는 AbstractSecurityInterceptor에 의해 호출되며 최종 접근 제어 결정을 내리는 책임을 집니다. AccessDecisionManager 인터페이스는 세 가지 메소드를 포함하고 있습니다:
void decide(Authentication authentication, Object secureObject,
Collection<ConfigAttribute> attrs) throws AccessDeniedException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class clazz);
AccessDecisionManager의 decide 메소드는 인가 결정을 내리기 위해 필요한 모든 관련 정보를 전달받습니다. 특히, 보안 객체를 전달함으로써 실제 보안 객체 호출에 포함된 인자들을 검사할 수 있습니다. 예를 들어, 보안 객체가 MethodInvocation이라고 가정해 보겠습니다. MethodInvocation에서 어떤 Customer 인자를 조회한 다음, AccessDecisionManager에서 어떤 보안 로직을 구현하여 그 고객에 대한 작업을 원칙적으로 허용할 수 있도록 할 수 있습니다. 접근이 거부되면 AccessDeniedException을 발생시킬 것으로 예상됩니다.
supports(ConfigAttribute) 메소드는 시작 시 AbstractSecurityInterceptor에 의해 호출되어 AccessDecisionManager가 전달된 ConfigAttribute를 처리할 수 있는지 결정합니다. supports(Class) 메소드는 보안 인터셉터 구현에 의해 호출되어 구성된 AccessDecisionManager가 보안 인터셉터가 제시하는 보안 객체 유형을 지원하는지 확인합니다.
Voting-Based AccessDecisionManager 구현
사용자는 모든 인가 측면을 제어하기 위해 자체 AccessDecisionManager를 구현할 수 있지만, 스프링 시큐리티는 투표에 기반한 여러 AccessDecisionManager 구현을 포함하고 있습니다. Voting Decision Manager는 관련 클래스를 설명합니다.
다음 이미지는 AccessDecisionManager 인터페이스를 보여줍니다:
이 접근 방식을 사용하면 일련의 AccessDecisionVoter 구현체가 인가 결정에 대해 폴링됩니다. 그런 다음 AccessDecisionManager는 투표의 평가를 바탕으로 AccessDeniedException을 발생시킬지 여부를 결정합니다.
AccessDecisionVoter 인터페이스에는 세 가지 메소드가 있습니다:
int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs);
boolean supports(ConfigAttribute attribute);
boolean supports(Class clazz);
구체적인 구현은 int를 반환하며, 가능한 값은 AccessDecisionVoter에 정의된 정적 필드인 ACCESS_ABSTAIN, ACCESS_DENIED, 그리고 ACCESS_GRANTED를 반영합니다. 투표 구현체는 인가 결정에 대한 의견이 없을 경우 ACCESS_ABSTAIN을 반환합니다. 의견이 있는 경우에는 ACCESS_DENIED 또는 ACCESS_GRANTED 중 하나를 반환해야 합니다.
스프링 시큐리티에서 제공하는 세 가지 구체적인 AccessDecisionManager 구현체는 투표를 집계합니다. ConsensusBased 구현체는 기권 투표가 아닌 투표의 합의에 따라 접근을 허용하거나 거부합니다. 투표가 동일하거나 모든 투표가 기권일 경우의 행동을 제어하는 속성이 제공됩니다. AffirmativeBased 구현체는 하나 이상의 ACCESS_GRANTED 투표가 있을 경우 접근을 허용합니다(즉, 최소한 하나의 허용 투표가 있는 경우 거부 투표는 무시됩니다). ConsensusBased 구현체와 마찬가지로, 모든 투표자가 기권할 경우의 행동을 제어하는 매개변수가 있습니다. UnanimousBased 제공자는 접근을 허용하기 위해 만장일치 ACCESS_GRANTED 투표를 기대하며, 기권은 무시합니다. ACCESS_DENIED 투표가 있으면 접근을 거부합니다. 다른 구현체와 마찬가지로 모든 투표자가 기권할 경우의 행동을 제어하는 매개변수가 있습니다.
투표를 다르게 집계하는 사용자 정의 AccessDecisionManager를 구현할 수도 있습니다. 예를 들어, 특정 AccessDecisionVoter의 투표에 추가 가중치를 부여하거나 특정 투표자의 거부 투표에 거부권 효과를 줄 수 있습니다.
RoleVoter
스프링 시큐리티와 함께 제공되는 가장 일반적으로 사용되는 AccessDecisionVoter는 RoleVoter로, 구성 속성을 역할 이름으로 처리하고 사용자가 해당 역할에 할당된 경우 접근을 허용하기 위해 투표합니다.
ConfigAttribute가 ROLE_ 접두사로 시작하는 경우에 투표합니다. ROLE_ 접두사로 시작하는 하나 이상의 ConfigAttributes와 정확히 일치하는 문자열 표현( getAuthority() 메소드에서 반환)을 반환하는 GrantedAuthority가 있으면 접근을 허용하기 위해 투표합니다. ROLE_로 시작하는 ConfigAttribute와 정확히 일치하지 않는 경우 RoleVoter는 접근을 거부하기 위해 투표합니다. ROLE_로 시작하는 ConfigAttribute가 없으면 기권합니다.
AuthenticatedVoter
또 다른 투표자는 우리가 암시적으로 본 AuthenticatedVoter로, 익명, 완전 인증, 기억하기 인증된 사용자를 구분하는 데 사용할 수 있습니다. 많은 사이트에서는 기억하기 인증 하에 일정한 제한된 접근을 허용하지만, 사용자가 로그인하여 자신의 신원을 확인함으로써 전체 접근을 요구합니다.
IS_AUTHENTICATED_ANONYMOUSLY 속성을 사용하여 익명 접근을 허용할 때 이 속성은 AuthenticatedVoter에 의해 처리되었습니다. 자세한 내용은 AuthenticatedVoter를 참조하십시구체적인 구현은 int를 반환하며, 가능한 값은 AccessDecisionVoter에 정의된 정적 필드인 ACCESS_ABSTAIN, ACCESS_DENIED, 그리고 ACCESS_GRANTED를 반영합니다. 투표 구현체는 인가 결정에 대한 의견이 없을 경우 ACCESS_ABSTAIN을 반환합니다. 의견이 있는 경우에는 ACCESS_DENIED 또는 ACCESS_GRANTED 중 하나를 반환해야 합니다.
스프링 시큐리티에서 제공하는 세 가지 구체적인 AccessDecisionManager 구현체는 투표를 집계합니다. ConsensusBased 구현체는 기권 투표가 아닌 투표의 합의에 따라 접근을 허용하거나 거부합니다. 투표가 동일하거나 모든 투표가 기권일 경우의 행동을 제어하는 속성이 제공됩니다. AffirmativeBased 구현체는 하나 이상의 ACCESS_GRANTED 투표가 있을 경우 접근을 허용합니다(즉, 최소한 하나의 허용 투표가 있는 경우 거부 투표는 무시됩니다). ConsensusBased 구현체와 마찬가지로, 모든 투표자가 기권할 경우의 행동을 제어하는 매개변수가 있습니다. UnanimousBased 제공자는 접근을 허용하기 위해 만장일치 ACCESS_GRANTED 투표를 기대하며, 기권은 무시합니다. ACCESS_DENIED 투표가 있으면 접근을 거부합니다. 다른 구현체와 마찬가지로 모든 투표자가 기권할 경우의 행동을 제어하는 매개변수가 있습니다.
투표를 다르게 집계하는 사용자 정의 AccessDecisionManager를 구현할 수도 있습니다. 예를 들어, 특정 AccessDecisionVoter의 투표에 추가 가중치를 부여하거나 특정 투표자의 거부 투표에 거부권 효과를 줄 수 있습니다.
RoleVoter
스프링 시큐리티와 함께 제공되는 가장 일반적으로 사용되는 AccessDecisionVoter는 RoleVoter로, 구성 속성을 역할 이름으로 처리하고 사용자가 해당 역할에 할당된 경우 접근을 허용하기 위해 투표합니다.
ConfigAttribute가 ROLE_ 접두사로 시작하는 경우에 투표합니다. ROLE_ 접두사로 시작하는 하나 이상의 ConfigAttributes와 정확히 일치하는 문자열 표현( getAuthority() 메소드에서 반환)을 반환하는 GrantedAuthority가 있으면 접근을 허용하기 위해 투표합니다. ROLE_로 시작하는 ConfigAttribute와 정확히 일치하지 않는 경우 RoleVoter는 접근을 거부하기 위해 투표합니다. ROLE_로 시작하는 ConfigAttribute가 없으면 기권합니다.
AuthenticatedVoter
또 다른 투표자는 우리가 암시적으로 본 AuthenticatedVoter로, 익명, 완전 인증, 기억하기 인증된 사용자를 구분하는 데 사용할 수 있습니다. 많은 사이트에서는 기억하기 인증 하에 일정한 제한된 접근을 허용하지만, 사용자가 로그인하여 자신의 신원을 확인함으로써 전체 접근을 요구합니다.
IS_AUTHENTICATED_ANONYMOUSLY 속성을 사용하여 익명 접근을 허용할 때 이 속성은 AuthenticatedVoter에 의해 처리되었습니다. 자세한 내용은 AuthenticatedVoter를 참조하십시오.
Custom Voters
또한 사용자 정의 AccessDecisionVoter를 구현하고 거의 모든 접근 제어 로직을 포함할 수 있습니다. 이는 애플리케이션에 특정할 수 있습니다(비즈니스 로직 관련) 또는 일부 보안 관리 로직을 구현할 수 있습니다. 예를 들어, 스프링 웹사이트에서는 계정이 정지된 사용자에게 실시간으로 접근을 거부하는 데 사용하는 투표자를 설명하는 블로그 기사를 찾을 수 있습니다.
Spring Security의 많은 다른 부분들처럼, AfterInvocationManager에는 AfterInvocationProviderManager라는 단일 구체적 구현이 있으며, 이는 AfterInvocationProviders 목록을 폴링합니다. 각 AfterInvocationProvider는 반환 객체를 수정하거나 AccessDeniedException을 발생시킬 수 있습니다. 실제로 여러 제공자가 객체를 수정할 수 있으며, 이전 제공자의 결과가 목록에서 다음 제공자에게 전달됩니다.
AfterInvocationManager를 사용하는 경우에도 MethodSecurityInterceptor의 AccessDecisionManager가 작업을 허용하도록 허용하는 구성 속성이 여전히 필요하다는 점을 유의해야 합니다. 일반적인 Spring Security에 포함된 AccessDecisionManager 구현을 사용하는 경우 특정 보안 메소드 호출에 대해 구성 속성이 정의되어 있지 않으면 각 AccessDecisionVoter는 투표에서 기권하게 됩니다. 이어서, AccessDecisionManager 속성 "allowIfAllAbstainDecisions"가 false인 경우 AccessDeniedException이 발생할 것입니다. 이 잠재적인 문제를 피할 수 있는 방법은 (i) "allowIfAllAbstainDecisions"를 true로 설정하는 것입니다(일반적으로 권장되지 않음) 또는 (ii) 적어도 하나의 구성 속성이 AccessDecisionVoter에 의해 접근을 허용하도록 투표하도록 하는 것입니다. 후자의 (권장되는) 접근 방식은 일반적으로 ROLE_USER 또는 ROLE_AUTHENTICATED 구성 속성을 통해 달성됩니다.