【问题标题】:Dynamic Spring Security using SQL Query使用 SQL 查询的动态 Spring Security
【发布时间】:2012-01-12 23:37:45
【问题描述】:

您好,我想在 Spring Security 中使用 sql 查询创建一个拦截 url 模式并动态访问。

通常我们在 XML 中使用这种类型的符号,我想从数据库中获取这些值(/add-role 和 ROLE_ADMIN)。

<intercept-url pattern="/add-role*" access="ROLE_ADMIN" />

是否可以动态执行此操作?

【问题讨论】:

    标签: spring spring-mvc spring-security


    【解决方案1】:

    免责声明

    正如 Spring Security FAQ 所提到的,您应该做的第一件事是问我真的应该这样做吗?安全性很复杂,应该对配置进行广泛的测试。允许配置动态更改只会使事情变得更加复杂,从而使应用程序更容易受到攻击。如果您真的想这样做,常见问题解答概述了完成此操作的基本方法。我已经扩展了下面的常见问题解答。

    实现自定义 FilterInvocationSecurityMetadataSource

    要动态获取安全 URL 映射,您可以实现自己的 FilterInvocationSecurityMetadataSource。下面给出一个示例实现。

    注意:请记住,对于 Spring Security 拦截的每个请求都会调用 getAttributes,因此您很可能需要某种缓存。

    public class JdbcFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    
        public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
            FilterInvocation fi = (FilterInvocation) object;
    
            String url = fi.getRequestUrl();
            HttpServletRequest request = fi.getHttpRequest();
    
            // Instead of hard coding the roles lookup the roles from the database using the url and/or HttpServletRequest
            // Do not forget to add caching of the lookup   
            String[] roles = new String[] { "ROLE_ADMIN", "ROLE_USER" };
            return SecurityConfig.createList(roles);
        }
    
        public Collection<ConfigAttribute> getAllConfigAttributes() {
            return null;
        }
    
        public boolean supports(Class<?> clazz) {
            return FilterInvocation.class.isAssignableFrom(clazz);
        }
    }
    

    创建一个 BeanPostProcessor

    你不能使用命名空间来连接它,所以使用另一个 tip from the FAQ 你可以使用一个 BeanPostProcessor,它可能看起来像:

    public class FilterInvocationSecurityMetadataSourcePostProcessor implements BeanPostProcessor, InitializingBean {
        private FilterInvocationSecurityMetadataSource securityMetadataSource;
    
        public Object postProcessAfterInitialization(Object bean, String name) {
            if (bean instanceof FilterSecurityInterceptor) {
                ((FilterSecurityInterceptor)bean).setSecurityMetadataSource(securityMetadataSource);
            }
            return bean;
        }
    
        public Object postProcessBeforeInitialization(Object bean, String name) {
            return bean;
        }
    
        public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource securityMetadataSource) {
            this.securityMetadataSource = securityMetadataSource;
        }
    
        public void afterPropertiesSet() throws Exception {
            Assert.notNull(securityMetadataSource,"securityMetadataSource cannot be null");
        }
    }
    

    XML 配置

    然后,假设上述两个 bean 都在包示例中,您将添加以下配置

    <bean class="sample.FilterInvocationSecurityMetadataSourcePostProcessor">
        <property name="securityMetadataSource">
            <bean class="sample.JdbcFilterInvocationSecurityMetadataSource"/>
        </property>
    </bean>
    

    可能的问题

    如果您最终收到 ClassCastException,您可能会遇到 SEC-1957,该问题已在 Spring Security 3.1.1+ 中修复,请尝试更新到最新版本来解决此问题。

    【讨论】:

    • 为了将来参考,请注意这不适用于use-expressions="true"。如果您需要使用数据库角色映射 + 带有表达式的静态映射,则必须在初始 WebExpressionVoteruse-expressions 设置为 true 时使用)之外定义一个 RoleVoter
    【解决方案2】:

    您无法真正从数据库中获取这些值,但您可以编写一个名为 DecisionManager 的自定义代码,用于评估是否允许执行资源。使用该代码,您甚至可以从数据库中读取数据。

    <bean id="MyDecisionManagerBean" class="org.springframework.security.vote.UnanimousBased">
        <property name="decisionVoters">
            <list>
                <!-- <bean class="org.springframework.security.vote.RoleVoter"/>  -->
                <bean class="org.springframework.security.vote.RoleHierarchyVoter" >
                     <constructor-arg>
                      <bean class="org.springframework.security.userdetails.hierarchicalroles.RoleHierarchyImpl" factory-bean="roleHierarchyImplFactory" factory-method="createRoleHierarchyImpl"/>
                    </constructor-arg>  
                </bean>
                <bean class="com.mycompany.RoleDenyVoter"/>
                <bean class="com.mycompany.RoleAllowVoter"/>
            </list>
        </property>
    </bean> 
    

    你的班级会是这样的:

    public class RoleDenyVoter implements AccessDecisionVoter {
    
        public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) {
             //read from the DB and decide if access is granted
    

    这里记录了这个过程:

    http://static.springsource.org/spring-security/site/docs/3.0.x/reference/authz-arch.html#authz-voting-based

    【讨论】:

    • 你错了角色可以从数据库中动态加载!在我的项目中做了这个,唯一的问题是这样做没有映射的 url 默认保持打开状态!但你可以解决它!!!!
    【解决方案3】:

    我已创建此条目以用于更新目的

    实现自定义 FilterInvocationSecurityMetadataSource

    这个类只在每个请求中获取 URL 并从数据库或第三方应用程序中查找他们的权限

    public class CommonFilterSecurityMetaDataSource implements FilterInvocationSecurityMetadataSource {
    
    private final Map<String, UrlRequestModel> permissions;
    
    @Autowired
    private UrlRequestDao urlRequestDao;
    
    public CommonFilterSecurityMetaDataSource() {
        permissions = new Hashtable<>();
    }
    
    public List<ConfigAttribute> getAttributes(Object object) {
        final FilterInvocation fi = (FilterInvocation) object;
        final String url = fi.getRequestUrl();
        final String httpMethod = fi.getRequest().getMethod();
        final String key = String.format("%s %s", httpMethod, url);
        final UrlRequestModel urlRequestModel;
    
        List<ConfigAttribute> attributes = null;
    
        // Lookup your database (or other source) using this information and populate the
        // list of attributes
        if(permissions.containsKey(key)) {
            urlRequestModel= permissions.get(key);
        } else {
            urlRequestModel= catRequestDao.findByUrl(url);
            if(catRequestMapModel != null) {
                permissions.put(key, urlRequestModel);
            }
        }
    
    
        if (catRequestMapModel != null) {
            List<RoleModel> roles = ulrRequestModel.getRoleList();
            if(!roles.isEmpty()) {
                attributes = new ArrayList<>(roles.size());
                for (RoleModel role : roles) {
                    attributes.add(new SecurityConfig(role.getDescription()));
                }
            }
        }
    
    
    
        return attributes;
    }
    
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }
    
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
    }
    

    Java 配置

    对于 java 配置,仅将其添加到从 WebSecurityConfigurerAdapter 扩展的类中

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.headers().frameOptions().disable();
        http.authorizeRequests().
                antMatchers( "/javax.faces.resource/**").permitAll().
                and()
                .exceptionHandling().accessDeniedPage("/accessDenied.jsf").
                and().formLogin().
                loginPage("/login.jsf").
                loginProcessingUrl("/loginAction").
                usernameParameter("app_username").
                passwordParameter("app_password").
                defaultSuccessUrl("/secure/index.jsf").
                and().logout().
                logoutUrl("/appLogout").
                logoutSuccessUrl("/login.jsf").logoutRequestMatcher(new AntPathRequestMatcher("/appLogout")).
                and().addFilterAfter(filterSecurityInterceptor(), FilterSecurityInterceptor.class);
        http.csrf().disable();
    
    }
    @Bean
    public FilterSecurityInterceptor filterSecurityInterceptor() throws Exception {
        FilterSecurityInterceptor filterSecurityInterceptor = new FilterSecurityInterceptor();
        filterSecurityInterceptor.setSecurityMetadataSource(securityMetadataSource());
        filterSecurityInterceptor.setAuthenticationManager(authenticationManager());
        filterSecurityInterceptor.setAccessDecisionManager(accessDecisionManager());
        filterSecurityInterceptor.setPublishAuthorizationSuccess(true);
        return filterSecurityInterceptor;
    }
    
    
    @Bean
    public AccessDecisionManager accessDecisionManager() {
        AuthenticatedVoter authenticatedVoter = new AuthenticatedVoter();
        RoleVoter roleVoter = new RoleVoter();
        List<AccessDecisionVoter<? extends Object>> voters = new ArrayList<>();
        voters.add(authenticatedVoter);
        voters.add(roleVoter);
        return new AffirmativeBased(voters);
    }
    
    @Bean
    public FilterInvocationSecurityMetadataSource securityMetadataSource() {
        return new CommonFilterSecurityMetaDataSource();
    }
    

    我使用 Spring security 5.0.8 对其进行了测试

    【讨论】:

      猜你喜欢
      • 2023-04-11
      • 2015-10-02
      • 1970-01-01
      • 1970-01-01
      • 2012-12-21
      • 2021-10-10
      • 1970-01-01
      • 2020-01-19
      相关资源
      最近更新 更多