1. 소개
대부분의 경우 Spring 웹 애플리케이션 또는 REST API를 보호할 때 Spring Security에서 제공하는 도구로 충분하지만 때로는 보다 구체적인 동작을 찾고 있습니다.
이 사용방법(예제)에서는 사용자 지정 AccessDecisionVoter 를 작성하고 이를 사용하여 웹 애플리케이션의 권한 부여 논리를 추상화하고 애플리케이션의 비즈니스 논리에서 분리하는 방법을 보여줍니다.
2. 시나리오
AccessDecisionVoter 가 어떻게 작동하는지 보여주기 위해 USER 와 ADMIN 이라는 두 가지 사용자 유형으로 시나리오를 구현합니다 . 여기서 USER 는 짝수 분에만 시스템에 액세스할 수 있고 ADMIN 은 항상 액세스 권한이 부여됩니다.
3. AccessDecisionVoter 구현
먼저 권한 부여에 대한 최종 결정을 내릴 때 사용자 지정 투표자와 함께 참여할 Spring에서 제공하는 몇 가지 구현을 설명합니다. 그런 다음 사용자 지정 유권자를 구현하는 방법을 살펴보겠습니다.
3.1. 기본 AccessDecisionVoter 구현
Spring Security는 여러 AccessDecisionVoter 구현을 제공합니다. 여기에서 Security 솔루션의 일부로 그 중 몇 가지를 사용할 것입니다.
이러한 기본 유권자 구현이 투표하는 방법과 시기를 살펴보겠습니다.
AuthenticatedVoter 는 Authentication 객체의 인증 수준 에 따라 투표를 합니다. 특히 완전히 인증된 pricipal, remember-me로 인증된 pricipal 또는 마지막으로 익명을 찾습니다.
RoleVoter 는 구성 속성 중 하나라도 문자열 "ROLE_"로 시작하는 경우 투표합니다. 그렇다면 인증 개체 의 GrantedAuthority List 에서 역할을 검색합니다.
WebExpressionVoter 를 사용 하면 SpEL(Spring Expression Language)을 사용하여 @PreAuthorize 어노테이션을 사용하여 요청을 승인할 수 있습니다.
예를 들어 Java 구성을 사용하는 경우:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
...
.antMatchers("/").hasAnyAuthority("ROLE_USER")
...
}
또는 XML 구성을 사용 하여 http 태그의 intercept-url 태그 내에서 SpEL을 사용할 수 있습니다 .
<http use-expressions="true">
<intercept-url pattern="/"
access="hasAuthority('ROLE_USER')"/>
...
</http>
3.2. 사용자 지정 AccessDecisionVoter 구현
이제 AccessDecisionVoter 인터페이스를 구현하여 사용자 지정 유권자를 만들어 보겠습니다.
public class MinuteBasedVoter implements AccessDecisionVoter {
...
}
우리가 제공해야 할 세 가지 방법 중 첫 번째는 투표 방법입니다. 투표 방법은 사용자 지정 유권자 의 가장 중요한 부분이며 인증 논리가 사용되는 곳입니다.
투표 방법 은 세 가지 가능한 값을 반환할 수 있습니다.
- ACCESS_GRANTED – 유권자가 긍정적인 답변을 제공합니다.
- ACCESS_DENIED – 유권자가 부정적인 답변을 함
- ACCESS_ABSTAIN – 유권자가 투표를 기권함
이제 투표 방법을 구현해 보겠습니다.
@Override
public int vote(
Authentication authentication, Object object, Collection collection) {
return authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.filter(r -> "ROLE_USER".equals(r)
&& LocalDateTime.now().getMinute() % 2 != 0)
.findAny()
.map(s -> ACCESS_DENIED)
.orElseGet(() -> ACCESS_ABSTAIN);
}
투표 방법 에서 요청이 USER 에서 오는지 확인합니다 . 그렇다면 짝수 분이면 ACCESS_GRANTED 를 반환하고 그렇지 않으면 ACCESS_DENIED를 반환합니다. USER 로부터 요청이 오지 않으면 투표를 기권하고 ACCESS_ABSTAIN 을 반환 합니다.
두 번째 메서드는 유권자가 특정 구성 속성을 지원하는지 여부를 반환합니다. 이 예에서 투표자는 사용자 정의 구성 속성이 필요하지 않으므로 true 를 반환합니다 .
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
세 번째 메서드는 유권자가 Security 개체 유형에 투표할 수 있는지 여부를 반환합니다. 투표자는 Security 개체 유형에 관심이 없으므로 true 를 반환합니다 .
@Override
public boolean supports(Class clazz) {
return true;
}
4. AccessDecisionManager
최종 승인 결정은 AccessDecisionManager 에 의해 처리됩니다 .
AbstractAccessDecisionManager 에는 AccessDecisionVoter List 이 포함되어 있습니다. 이들은 서로 독립적으로 투표를 담당합니다.
가장 일반적인 사용 사례를 다루기 위해 투표를 처리하기 위한 세 가지 구현이 있습니다.
- AffirmativeBased – AccessDecisionVoter 중 하나라도 찬성표를 반환하면 액세스 권한을 부여합니다.
- ConsensusBased – 반대보다 찬성 투표가 더 많은 경우 액세스 권한을 부여합니다(기권한 사용자 무시).
- UnanimousBased – 모든 유권자가 기권하거나 찬성표를 반환하는 경우 액세스 권한을 부여합니다.
물론 사용자 지정 의사 결정 논리를 사용 하여 고유한 AccessDecisionManager 를 구현할 수 있습니다.
5. 구성
사용방법(예제)의 이 부분에서는 AccessDecisionManager 로 사용자 지정 AccessDecisionVoter 를 구성하기 위한 Java 기반 및 XML 기반 메서드를 살펴보겠습니다 .
5.1. 자바 구성
Spring Web Security에 대한 구성 클래스를 생성해 보겠습니다.
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
...
}
그리고 사용자 정의된 투표자 List과 함께 UnanimousBased 관리자 를 사용하는 AccessDecisionManager 빈을 정의해 보겠습니다.
@Bean
public AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<? extends Object>> decisionVoters
= Arrays.asList(
new WebExpressionVoter(),
new RoleVoter(),
new AuthenticatedVoter(),
new MinuteBasedVoter());
return new UnanimousBased(decisionVoters);
}
마지막으로 이전에 정의된 빈을 기본 AccessDecisionManager 로 사용하도록 Spring Security를 구성해 보겠습니다 .
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
...
.anyRequest()
.authenticated()
.accessDecisionManager(accessDecisionManager());
}
5.2. XML 구성
XML 구성을 사용하는 경우 spring-security.xml 파일(또는 Security 설정이 포함된 파일)을 수정해야 합니다.
먼저 <http> 태그를 수정해야 합니다.
<http access-decision-manager-ref="accessDecisionManager">
<intercept-url
pattern="/**"
access="hasAnyRole('ROLE_ADMIN', 'ROLE_USER')"/>
...
</http>
다음으로 사용자 지정 투표자에 대한 빈을 추가합니다.
<beans:bean
id="minuteBasedVoter"
class="com.baeldung.voter.MinuteBasedVoter"/>
그런 다음 AccessDecisionManager 에 대한 bean을 추가하십시오 .
<beans:bean
id="accessDecisionManager"
class="org.springframework.security.access.vote.UnanimousBased">
<beans:constructor-arg>
<beans:list>
<beans:bean class=
"org.springframework.security.web.access.expression.WebExpressionVoter"/>
<beans:bean class=
"org.springframework.security.access.vote.AuthenticatedVoter"/>
<beans:bean class=
"org.springframework.security.access.vote.RoleVoter"/>
<beans:bean class=
"com.baeldung.voter.MinuteBasedVoter"/>
</beans:list>
</beans:constructor-arg>
</beans:bean>
시나리오를 지원 하는 샘플 <authentication-manager> 태그 는 다음과 같습니다.
<authentication-manager>
<authentication-provider>
<user-service>
<user name="user" password="pass" authorities="ROLE_USER"/>
<user name="admin" password="pass" authorities="ROLE_ADMIN"/>
</user-service>
</authentication-provider>
</authentication-manager>
Java 및 XML 구성의 조합을 사용하는 경우 XML을 구성 클래스로 가져올 수 있습니다.
@Configuration
@ImportResource({"classpath:spring-security.xml"})
public class XmlSecurityConfig {
public XmlSecurityConfig() {
super();
}
}
6. 결론
이 예제에서는 AccessDecisionVoter 를 사용하여 Spring 웹 애플리케이션의 Security을 사용자 정의하는 방법을 살펴보았습니다 . 우리는 솔루션에 기여한 Spring Security에서 제공한 일부 유권자를 보았습니다. 그런 다음 사용자 지정 AccessDecisionVoter 를 구현하는 방법에 대해 논의했습니다 .
그런 다음 AccessDecisionManager 가 최종 승인 결정을 내리는 방법에 대해 논의했으며 모든 투표자가 투표한 후 이 결정을 내리기 위해 Spring에서 제공하는 구현을 사용하는 방법을 보여주었습니다.
그런 다음 Java 및 XML을 통해 AccessDecisionManager 로 AccessDecisionVoters List을 구성했습니다 .
구현은 Github 프로젝트 에서 찾을 수 있습니다 .
프로젝트가 로컬에서 실행되면 다음에서 로그인 페이지에 액세스할 수 있습니다.
http://localhost:8082/login
USER 에 대한 자격 증명 은 "user" 및 "pass"이고 ADMIN 에 대한 자격 증명 은 "admin" 및 "pass"입니다.