SpringSecurityInterceptor for spring boot中的多个方法

时间:2014-07-27 16:34:09

标签: java spring spring-security spring-boot

这与Mickael Marrache关于如何使用授权逻辑来保护服务的this question答案有关。授权逻辑可以在不同方法之间有所不同。

我更喜欢Maciej Ziarko使用Method Security AccessDecisionManager 而不是接受的响应提供的响应,因为它使用带有不同自定义参数的相同注释@Secured

由于我使用没有XML配置的Spring-Boot,所以花了一些时间来弄清楚如何做到这一点。

所以,这是我的答案。

它只解释了如何使用Java Config配置替换xml配置。

1 个答案:

答案 0 :(得分:2)

(在我的更改之后,我将添加原始答案“以防万一”。)

为了替换xml配置:

<sec:global-method-security secured-annotations="enabled"
                            access-decision-manager-ref="methodSecurityAccessDecisionManager">
</sec:global-method-security>

发生注释@EnableGlobalMethodSecurity没有办法在那里指定accessDecisionManager。您必须扩展GlobalMethodSecurityConfiguration并覆盖AccessDecisionManager方法。

要实现和配置任意数量的策略,原始帖子有

<bean id="methodSecurityAccessDecisionManager"
      class="some.package.MethodSecurityAccessDecisionManager">

    <constructor-arg>
        <map>
            <entry key="GetByOwner">
                <bean class="some.package.GetByOwnerStrategy"/>
            </entry>

            <entry key="SomeOther">
                <bean class="some.package.SomeOtherStrategy"/>
            </entry>
        </map>
    </constructor-arg>

</bean>

这两个问题都可以使用Java Config完成:

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;


@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    public AccessDecisionManager accessDecisionManager() {

        logger.debug("accessDecisionManager config...");

        Map<String, AccessDecisionStrategy> strategyMap = new HashMap<String, AccessDecisionStrategy>();

        strategyMap.put("GetByOwner", new GetByOwnerStrategy());

        return new MethodSecurityAccessDecisionManager(strategyMap);
    }

}

最后,简单的Web Security Config。 请注意,我正在使用“RestWebSecurity ...”,无论如何都可以为其命名。

@Configuration
public class WebSecurityConfig {

    @Configuration
    @Order(1)                                                        
    public static class RestWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                    .antMatchers(HttpMethod.GET, "/**").authenticated()
            .and()
                .httpBasic()
            .and()
                .csrf().disable();    
        }
    }
}

为了完整起见,当用户不应该继续时,实施的策略必须返回AccessDeniedExceptionInsufficientAuthenticationException。以下是访问参数和所有参数的示例:

public class GetByOwnerStrategy implements AccessDecisionStrategy {
    @Override
    public void decide(Authentication authentication,
            MethodInvocation methodInvocation, ConfigAttribute configAttribute) {

        MethodInvocationExtractor<Object> extractor = new MethodInvocationExtractor<>(methodInvocation);
        Person person = (Person) extractor.getArg(0);
        String userId = (String) extractor.getArg(1);

        String username = authentication.getName();

        if (! ((userId.equals(username)) && (person.getSomeData().equals("SOMETHING") ) && ....) {
            throw new AccessDeniedException("Not enough privileges");
        }
    }
}

==================原始答案======================= < / b>

我通过实现自己的AccessDecisionManager实现了这一点,AccessDecisionManager将访问决策委托给我的特殊接口AccessDecisionStrategy:

public interface AccessDecisionStrategy {

    void decide(Authentication authentication, MethodInvocation methodInvocation, ConfigAttribute configAttribute);

}

每个访问决策策略都代表了不同的访问决策方式。

您可以轻松实现自己的策略(即使是其他语言 - 例如Scala):

公共类SomeStrategy实现了AccessDecisionStrategy {...

如您所见,我的AccessDecisionManager有一个策略图。经理使用的策略基于注释参数。

public class MethodSecurityAccessDecisionManager implements AccessDecisionManager {

    private Map<String, AccessDecisionStrategy> strategyMap;

    public MethodSecurityAccessDecisionManager(Map<String, AccessDecisionStrategy> strategyMap) {
        this.strategyMap = strategyMap;
    }

    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        ConfigAttribute configAttribute = getSingleConfigAttribute(configAttributes);
        AccessDecisionStrategy accessDecisionStrategy = strategyMap.get(configAttribute.getAttribute());
        if (accessDecisionStrategy == null) {
            throw new IllegalStateException("AccessDecisionStrategy with name "
                    + configAttribute.getAttribute() + " was not found!");
        }
        try {
            accessDecisionStrategy.decide(authentication, (MethodInvocation) object, configAttribute);
        } catch (ClassCastException e) {
            throw new IllegalStateException();
        }
    }

    private ConfigAttribute getSingleConfigAttribute(Collection<ConfigAttribute> configAttributes) {
        if (configAttributes == null || configAttributes.size() != 1) {
            throw new IllegalStateException("Invalid config attribute configuration");
        }
        return configAttributes.iterator().next();
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return clazz.equals(MethodInvocation.class);
    }
}

现在,当我想要保护我的方法时,我将@Secured注释与作为策略名称的参数放在一起:

@Secured("GetByOwner")
FlightSpotting getFlightSpotting(Long id);

您可以根据需要实施和配置多个策略:

<bean id="methodSecurityAccessDecisionManager"
      class="some.package.MethodSecurityAccessDecisionManager">

    <constructor-arg>
        <map>
            <entry key="GetByOwner">
                <bean class="some.package.GetByOwnerStrategy"/>
            </entry>

            <entry key="SomeOther">
                <bean class="some.package.SomeOtherStrategy"/>
            </entry>
        </map>
    </constructor-arg>

</bean>

要注入您输入的访问决策管理器:

<sec:global-method-security secured-annotations="enabled"
                            access-decision-manager-ref="methodSecurityAccessDecisionManager">
</sec:global-method-security>

我还实现了帮助类来处理MethodInvocation参数:

import org.aopalliance.intercept.MethodInvocation;

public class MethodInvocationExtractor<ArgumentType> {

    private MethodInvocation methodInvocation;

    public MethodInvocationExtractor(MethodInvocation methodInvocation) {
        this.methodInvocation = methodInvocation;
    }

    public ArgumentType getArg(int num) {
        try {
            Object[] arguments = methodInvocation.getArguments();
            return (ArgumentType) arguments[num];
        } catch (ClassCastException | ArrayIndexOutOfBoundsException e) {
            throw new IllegalStateException();
        }
    }
}

现在,您可以轻松地在策略代码中提取有趣的参数来做出决定:

假设我想得到类型为Long的参数0:

MethodInvocationExtractor<Long> extractor = new MethodInvocationExtractor<>(methodInvocation);
Long id = extractor.getArg(0);

    在12月14日14:40回答
    Maciej Ziarko