1. 소개

대부분의 경우 Spring 웹 애플리케이션 또는 REST API를 보호할 때 Spring Security에서 제공하는 도구로 충분하지만 때로는 보다 구체적인 동작을 찾고 있습니다.

이 사용방법(예제)에서는 사용자 지정 AccessDecisionVoter 를 작성하고 이를 사용하여 웹 애플리케이션의 권한 부여 논리를 추상화하고 애플리케이션의 비즈니스 논리에서 분리하는 방법을 보여줍니다.

2. 시나리오

AccessDecisionVoter 가 어떻게 작동하는지 보여주기 위해 USERADMIN 이라는 두 가지 사용자 유형으로 시나리오를 구현합니다 . 여기서 USER 는 짝수 분에만 시스템에 액세스할 수 있고 ADMIN 은 항상 액세스 권한이 부여됩니다.

3. AccessDecisionVoter 구현

먼저 권한 부여에 대한 최종 결정을 내릴 때 사용자 지정 투표자와 함께 참여할 Spring에서 제공하는 몇 가지 구현을 설명합니다. 그런 다음 사용자 지정 유권자를 구현하는 방법을 살펴보겠습니다.

3.1. 기본 AccessDecisionVoter 구현

Spring Security는 여러 AccessDecisionVoter 구현을 제공합니다. 여기에서 Security 솔루션의 일부로 그 중 몇 가지를 사용할 것입니다.

이러한 기본 유권자 구현이 투표하는 방법과 시기를 살펴보겠습니다.

AuthenticatedVoterAuthentication 객체의 인증 수준 에 따라 투표를 합니다. 특히 완전히 인증된 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을 통해 AccessDecisionManagerAccessDecisionVoters List을 구성했습니다 .

구현은 Github 프로젝트 에서 찾을 수 있습니다 .

프로젝트가 로컬에서 실행되면 다음에서 로그인 페이지에 액세스할 수 있습니다.

http://localhost:8082/login

USER 에 대한 자격 증명 은 "user" 및 "pass"이고 ADMIN 에 대한 자격 증명 은 "admin" 및 "pass"입니다.

Security footer banner