Security——权限配置(数据库相关)

简单配置

如果不想做大的改动,使用 Security已有的功能进行实现。

 

数据库设计如下:

 

package cn.seaboot.admin.security.manager;

import cn.seaboot.admin.security.bean.entity.SecurityChain;
import cn.seaboot.admin.security.service.SecurityChainService;
import cn.seaboot.common.core.CommonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.List;

/**
 * Security configuration
 *
 * @author Mr.css
 * @date 2020-05-07 23:38
 */
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    private Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);

    @Resource
    private SecurityChainService securityChainService;

    /**
     * HttpSecurity相关配置
     *
     * @param http HttpSecurity
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //鉴权配置,将数据库的权限配置加到系统中去
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
        List<SecurityChain> chains = securityChainService.queryList();
        if (CommonUtils.isNotEmpty(chains)) {
            for (SecurityChain chain : chains) {
                if (!chain.getIsDisable() && !"anon".equals(chain.getExps())) {
                    if (chain.getMethod() == null) {
                        registry.antMatchers(chain.getUrl()).access(chain.getExps());
                    } else {
                        registry.antMatchers(HttpMethod.resolve(chain.getMethod()), chain.getUrl()).access(chain.getExps());
                    }
                    logger.info("[security] matcher: {} -> {}", chain.getUrl(), chain.getExps());
                }
            }
        }
        //以下省略http其它配置
    }

    /**
     * 白名单配置,因为默认的投票决策方式,是1票通过的方式,只要满足1个条件,就算满足权限要求,白名单配置的所有 url,都是可以直接访问的
     *
     * @param web -
     */
    @Override
    public void configure(WebSecurity web) {
        WebSecurity.IgnoredRequestConfigurer ignoredRequestConfigurer = web.ignoring();
        List<SecurityChain> chains = securityChainService.queryList();
        if (CommonUtils.isNotEmpty(chains)) {
            for (SecurityChain chain : chains) {
                if (!chain.getIsDisable() && "anon".equals(chain.getExps())) {
                    ignoredRequestConfigurer.antMatchers(chain.getUrl());
                    logger.info("[security] ignore: {}", chain.getUrl());
                }
            }
        }
    }
}

复杂配置

自定义权限匹配规则,这主要涉及到3个对象的使用。

AccessDecisionManager:投票决策,与数据库种的权限配置进行比较,判断用户是不是有操作权限;

FilterInvocationSecurityMetadataSource:元数据查找,相当于DAO,根据URL查找权限配置信息;

ObjectPostProcessor:一个代码切面,帮你将代码植入特定鉴权环节的接口。

具体配置如下:

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.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;

/**
 * Security configuration
 *
 * @author Mr.css
 * @date 2020-05-07 23:38
 */
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    private Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);

    /**
     * HttpSecurity相关配置
     *
     * @param http HttpSecurity
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        AuthorityPostProcessor urlPostProcessor = new AuthorityPostProcessor();
        urlPostProcessor.setAuthorityAccessDecisionManager(new AccessDecisionManager());
        urlPostProcessor.setAuthorityMetadataSource(new FilterInvocationSecurityMetadataSource());

        http.authorizeRequests()
                .antMatchers("/login").permitAll()
                .withObjectPostProcessor(urlPostProcessor)
                .anyRequest().authenticated();

        //下面省略其它配置
    }
}

AccessDecisionManager

系统默认实现有下面这么几个,Security是以投票的方式实现的,根据满足条件的数量,确定是否有权限进入系统(比如默认的,只要1个满足配置,就能直接进入系统)。

如何切换默认的3种决策方式?不知道,我也没找到配置方式,推荐查找AbstractSecurityInterceptor相关的代码。

* {@link AffirmativeBased}   只需有一个投票赞成即可通过;
* {@link ConsensusBased} 需要大多数投票赞成即可通过;
* {@link UnanimousBased} 需要所有的投票赞成才能通过。
import cn.seaboot.common.exception.ServiceException;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.ConsensusBased;
import org.springframework.security.access.vote.UnanimousBased;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import java.util.Collection;

/**
 * 投票决策
 * <p>
 * 默认实现:
 * - {@link AffirmativeBased}   只需有一个投票赞成即可通过;
 * - {@link ConsensusBased}     需要大多数投票赞成即可通过;
 * - {@link UnanimousBased}     需要所有的投票赞成才能通过。
 *
 * @author Mr.css
 * @date 2022-01-06 19:32
 */
@Component
public class AuthorityAccessDecisionManager implements AccessDecisionManager {
    /**
     * 与角色对比,查看是否满足权限
     *
     * @param authentication   用户凭证
     * @param object           当前请求路径
     * @param configAttributes 权限集合
     * @throws AccessDeniedException -
     */
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        System.out.println("通过认证:" + authentication);
//        throw new AccessDeniedException("access denied!");
    }

    /**
     * 因为我们的特殊写法,这些函数不应该被调用,直接抛出异常,方便我们跟踪意外调用的场景
     *
     * @param attribute -
     * @return -
     */
    @Override
    public boolean supports(ConfigAttribute attribute) {
        throw new ServiceException("UrlAccessDecisionManager's supports(ConfigAttribute attribute) method can not be used");
    }

    /**
     * 因为我们的特殊写法,这些函数不应该被调用,直接抛出异常,方便我们跟踪意外调用的场景
     *
     * @param clazz -
     * @return -
     */
    @Override
    public boolean supports(Class<?> clazz) {
        throw new ServiceException("UrlAccessDecisionManager's supports(Class<?> clazz) method can not be used");
    }
}

FilterInvocationSecurityMetadataSource

import cn.seaboot.common.exception.ServiceException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import java.util.Collection;

/**
 * 权限数据源管理
 *
 * @author Mr.css
 * @date 2022-01-06 19:29
 */
@Component
public class AuthorityMetadataSource implements FilterInvocationSecurityMetadataSource {

    /**
     * 路径匹配工具
     */
    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    /**
     * 根据Url查找匹配的路径列表
     *
     * @param object url
     * @return 所需的权限集合
     * @throws IllegalArgumentException -
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        System.out.println("获取URL所需的权限列表");
        String requestUrl = ((FilterInvocation) object).getRequestUrl();
        return SecurityConfig.createList("ROLE_LOGIN");
    }

    /**
     * 因为我们的特殊写法,这些函数不应该被调用,直接抛出异常,方便我们跟踪意外调用的场景
     *
     * @return -
     */
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        throw new ServiceException("SecurityMetadataSource's getAllConfigAttributes() method can not be used");
    }

    /**
     * 因为我们的特殊写法,这些函数不应该被调用,直接抛出异常,方便我们跟踪意外调用的场景
     *
     * @param clazz -
     * @return -
     */
    @Override
    public boolean supports(Class<?> clazz) {
        throw new ServiceException("SecurityMetadataSource's supports(Class<?> clazz) method can not be used");
    }
}

 

posted on 2022-01-07 11:53  疯狂的妞妞  阅读(301)  评论(0编辑  收藏  举报

导航