【Spring Security】 注解源码分析

Metadata

title: 【Spring Security】 注解源码分析
date: 2023-02-02 14:28
tags:
  - 行动阶段/完成
  - 主题场景/组件
  - 笔记空间/KnowladgeSpace/ProgramSpace/ModuleSpace
  - 细化主题/Module/SpringSecurity
categories:
  - SpringSecurity
keywords:
  - SpringSecurity
description: 【Spring Security】 注解源码分析

【Spring Security】 注解源码分析

我们通过添加注解的方式,就实现了权限控制。接下来分析下源码,了解他的执行流程。

首先以下面代码问控制入口。

@PreAuthorize("hasRole('ROLE_ROOT')")
    public Object test() {
        SecurityContext context = SecurityContextHolder.getContext();
        Authentication authentication = context.getAuthentication();
        MyUser principal = (MyUser) authentication.getPrincipal();
        return principal;
    }

授权流程图:

源码分析

核心类

FilterSecurityInterceptor

FilterSecurityInterceptor 作为 Spring Security Filter Chain 的最后一个 Filter,承担着非常重要的作用。如获取当前 request 对应的权限配置,调用访问控制器进行鉴权操作等,都是核心功能。进行访问控制的处理就在这个过滤器中。

ConfigAttribute

ConfigAttribute 是一个接口,其作用为存储相关访问控制的规则。

public interface ConfigAttribute extends Serializable {
    String getAttribute();
}

而这些访问规则可以通过配置类或者注解方式来配置。

比如 HttpSecurity 的默认配置为所有的请求都需要被认证,也可以添加自定义的访问控制规则

protected void configure(HttpSecurity http) throws Exception {
        this.logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
        http.authorizeRequests((requests) -> {
            ((AuthorizedUrl)requests.anyRequest()).authenticated();
        });
        http.formLogin();
        http.httpBasic();
    }

比如可以通过注解方式进行访问控制:

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")

WebExpressionConfigAttribute 是 ConfigAttribute 的常用实现类,并实现了 EvaluationContextPostProcessor 接口,他的主要作用是存储我们配置的表达式及其处理器。

/**
 * Simple expression configuration attribute for use in web request authorizations.
 *
 * @author Luke Taylor
 * @since 3.0
 * 用于 Web 请求授权的简单表达式配置属性。
 */
class WebExpressionConfigAttribute implements ConfigAttribute, EvaluationContextPostProcessor<FilterInvocation> {

    // 表达式 比如
    private final Expression authorizeExpression;
    // el表达式内容处理处理器
    private final EvaluationContextPostProcessor<FilterInvocation> postProcessor;

    WebExpressionConfigAttribute(Expression authorizeExpression,
            EvaluationContextPostProcessor<FilterInvocation> postProcessor) {
        this.authorizeExpression = authorizeExpression;
        this.postProcessor = postProcessor;
    }

FilterInvocation

FilterInvocation 类,主要是保存与 HTTP 过滤器关联的对象,包括请求、响应、连接器链。

public class FilterInvocation {

    static final FilterChain DUMMY_CHAIN = (req, res) -> {
        throw new UnsupportedOperationException("Dummy filter chain");
    };

    private FilterChain chain;

    private HttpServletRequest request;

    private HttpServletResponse response;

    public FilterInvocation(ServletRequest request, ServletResponse response, FilterChain chain) {
        Assert.isTrue(request != null && response != null && chain != null, "Cannot pass null values to constructor");
        this.request = (HttpServletRequest) request;
        this.response = (HttpServletResponse) response;
        this.chain = chain;
    }
}

InterceptorStatusToken

InterceptorStatusToken 是 FilterSecurityInterceptor 拦截器 beforeInvocation 处理返回的对象,主要包含了反映了安全拦截的状态,最终 afterInvocation 方法会对他进行最终的处理。

public class InterceptorStatusToken {

    private SecurityContext securityContext;

    private Collection<ConfigAttribute> attr;

    private Object secureObject;

    private boolean contextHolderRefreshRequired;

    public InterceptorStatusToken(SecurityContext securityContext, boolean contextHolderRefreshRequired,
            Collection<ConfigAttribute> attributes, Object secureObject) {
        this.securityContext = securityContext;
        this.contextHolderRefreshRequired = contextHolderRefreshRequired;
        this.attr = attributes;
        this.secureObject = secureObject;
    }
}

RunAsManager

RunAsManager 是一个接口,主要定义了对 Authentication 进行替换的方法。在极少数情况下,用户可以使用不同的 Authentication 替换 SecurityContext 中的 Authentication。

public interface RunAsManager {
    Authentication buildRunAs(Authentication authentication, Object object, Collection<ConfigAttribute> attributes);
    boolean supports(ConfigAttribute attribute);
    boolean supports(Class<?> clazz);
}

AccessDecisionManager

AccessDecisionManager 是访问决策管理器,做出最终的访问控制(授权)决定,他是一个接口,有众多的实现类。每一个具体的实现类可以表示为一种决策。

public interface AccessDecisionManager {

    /**
     * Resolves an access control decision for the passed parameters.
     * @param authentication the caller invoking the method (not null)
     * @param object the secured object being called
     * @param configAttributes the configuration attributes associated with the secured
     * object being invoked
     * @throws AccessDeniedException if access is denied as the authentication does not
     * hold a required authority or ACL privilege
     * @throws InsufficientAuthenticationException if access is denied as the
     * authentication does not provide a sufficient level of trust
     * 为传递的参数解析访问控制决策 
     */
    void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException, InsufficientAuthenticationException;

    /**
     * Indicates whether this <code>AccessDecisionManager</code> is able to process
     * authorization requests presented with the passed <code>ConfigAttribute</code>.
     * <p>
     * This allows the <code>AbstractSecurityInterceptor</code> to check every
     * configuration attribute can be consumed by the configured
     * <code>AccessDecisionManager</code> and/or <code>RunAsManager</code> and/or
     * <code>AfterInvocationManager</code>.
     * </p>
     * @param attribute a configuration attribute that has been configured against the
     * <code>AbstractSecurityInterceptor</code>
     * @return true if this <code>AccessDecisionManager</code> can support the passed
     * configuration attribute
     * 指示此 <code>AccessDecisionManager</code> 是否能够处理通过传递的 <code>ConfigAttribute</code> 呈现的授权请求
     */
    boolean supports(ConfigAttribute attribute);

    /**
     * Indicates whether the <code>AccessDecisionManager</code> implementation is able to
     * provide access control decisions for the indicated secured object type.
     * @param clazz the class that is being queried
     * @return <code>true</code> if the implementation can process the indicated class
     * 指示 <code>AccessDecisionManager</code> 实现是否能够为指示的安全对象类型提供访问控制决策。
     */
    boolean supports(Class<?> clazz);

}

AffirmativeBased

AffirmativeBased 是 AccessDecisionManager 默认的实现类,AffirmativeBased 的逻辑是这样的:

(1)只要有AccessDecisionVoter的投票为ACCESS_GRANTED则同意用户进行访问;

(2)如果全部弃权也表示通过;

(3)如果没有一个人投赞成票,但是有人投反对票,则将抛出AccessDeniedException。
public class AffirmativeBased extends AbstractAccessDecisionManager {

    public AffirmativeBased(List<AccessDecisionVoter<?>> decisionVoters) {
        super(decisionVoters);
    }
    @Override
    @SuppressWarnings({"rawtypes", "unchecked"})
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException {
        int deny = 0; // 否决票
        // 1. 循环WEB 投票器
        for (AccessDecisionVoter voter : getDecisionVoters()) {
            // 2. 投票,返回结果
            int result = voter.vote(authentication, object, configAttributes);
            //
            switch (result) {
                // 为1 ,表示通过,有一个通过,则直接通过,return退出函数,不抛出异常
                case AccessDecisionVoter.ACCESS_GRANTED:
                    return;
                // -1 ,拒绝访问,否决票+1
                case AccessDecisionVoter.ACCESS_DENIED:
                    deny++;
                    break;
                default:
                    break;
            }
        }
        // 如果否决票》0 抛出AccessDeniedException
        if (deny > 0) {
            throw new AccessDeniedException(
                    this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
        }
        // To get this far, every AccessDecisionVoter abstained
        // 3. 没有否决票,也没有赞成票,都弃权了,则检查是否允许如果所有弃权决定。
        checkAllowIfAllAbstainDecisions();
    }
}

AccessDecisionVoter

AccessDecisionVoter 就表示为投票决策者,也就是一个投票器了。AccessDecisionVoter 是一个接口,定义了三个常量,表示投票结果。

/** 
 *  访问决策选民, 表示一个类负责对授权决定进行投票
 */
public interface AccessDecisionVoter<S> {

    int ACCESS_GRANTED = 1; // 授予访问权限

    int ACCESS_ABSTAIN = 0; // 访问弃权

    int ACCESS_DENIED = -1; // 拒绝访问
    boolean supports(ConfigAttribute attribute);
    boolean supports(Class<?> clazz);
    // 投票
    int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);

}

WebExpressionVoter

AccessDecisionManager 默认的投票器 WebExpressionVoter。其实,就是对使用 http.authorizeRequests() 基于 Spring-EL 进行控制权限的的授权决策类。

/**
 * Voter which handles web authorisation decisions.
 *
 * @author Luke Taylor
 * @since 3.0
 * 处理web授权决定的选民
 */
public class WebExpressionVoter implements AccessDecisionVoter<FilterInvocation> {

    private final Log logger = LogFactory.getLog(getClass());

    // 默认的WebSecurity授权表达式处理器
    private SecurityExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler();

    // 投票
    @Override
    public int vote(Authentication authentication, FilterInvocation filterInvocation,
            Collection<ConfigAttribute> attributes) {
        // 1. 校验参数
        Assert.notNull(authentication, "authentication must not be null");
        Assert.notNull(filterInvocation, "filterInvocation must not be null");
        Assert.notNull(attributes, "attributes must not be null");
        // 2. 获取http配置项
        WebExpressionConfigAttribute webExpressionConfigAttribute = findConfigAttribute(attributes);
        // 3. 没有配置规则,弃权
        if (webExpressionConfigAttribute == null) {
            this.logger
                    .trace("Abstained since did not find a config attribute of instance WebExpressionConfigAttribute");
            return ACCESS_ABSTAIN;
        }
        // 4. 对EL表达式进行处理
        EvaluationContext ctx = webExpressionConfigAttribute.postProcess(
                this.expressionHandler.createEvaluationContext(authentication, filterInvocation), filterInvocation);
        boolean granted = ExpressionUtils.evaluateAsBoolean(webExpressionConfigAttribute.getAuthorizeExpression(), ctx);
        if (granted) {
            // 5. 符合条件,赞成票
            return ACCESS_GRANTED;
        }
        this.logger.trace("Voted to deny authorization");
        // 6. 最后都没有则反对票
        return ACCESS_DENIED;
    }

    private WebExpressionConfigAttribute findConfigAttribute(Collection<ConfigAttribute> attributes) {
        for (ConfigAttribute attribute : attributes) {
            if (attribute instanceof WebExpressionConfigAttribute) {
                return (WebExpressionConfigAttribute) attribute;
            }
        }
        return null;
    }

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

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

    public void setExpressionHandler(SecurityExpressionHandler<FilterInvocation> expressionHandler) {
        this.expressionHandler = expressionHandler;
    }

}

SecurityExpressionHandler

Web 安全表达式处理器 (handler)。最后对 EL 访问控制表达式的处理,都是在此接口中的实现类进行。它的默认实现类为 DefaultWebSecurityExpressionHandler。这个类的主要作用是创建 SecurityExpressionRoot 对象。

public class DefaultWebSecurityExpressionHandler extends AbstractSecurityExpressionHandler<FilterInvocation>
        implements SecurityExpressionHandler<FilterInvocation> {

    private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();

    // 角色前缀
    private String defaultRolePrefix = "ROLE_";

    @Override
    protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,
            FilterInvocation fi) {
        WebSecurityExpressionRoot root = new WebSecurityExpressionRoot(authentication, fi);
        root.setPermissionEvaluator(getPermissionEvaluator());
        root.setTrustResolver(this.trustResolver);
        root.setRoleHierarchy(getRoleHierarchy());
        root.setDefaultRolePrefix(this.defaultRolePrefix);
        return root;
    }
}

WebSecurityExpressionRoot

WebSecurityExpressionRoot 实现了 SecurityExpressionRoot 接口,其顶级接口为 SecurityExpressionOperations。

SecurityExpressionOperations 接口中定义个很多方法,每个方法都关联着一个访问控制表达式。

/**
 * Standard interface for expression root objects used with expression-based security.
 *
 * @author Andrei Stefan
 * @author Luke Taylor
 * @since 3.1.1
 * 与基于表达式的安全性一起使用的表达式根对象的标准接口
 */
public interface SecurityExpressionOperations {

    /**
     * Gets the {@link Authentication} used for evaluating the expressions
     * @return the {@link Authentication} for evaluating the expressions
     */
    Authentication getAuthentication();

    /**
     * Determines if the {@link #getAuthentication()} has a particular authority within
     * {@link Authentication#getAuthorities()}.
     * @param authority the authority to test (i.e. "ROLE_USER")
     * @return true if the authority is found, else false
     */
    boolean hasAuthority(String authority);

    /**
     * Determines if the {@link #getAuthentication()} has any of the specified authorities
     * within {@link Authentication#getAuthorities()}.
     * @param authorities the authorities to test (i.e. "ROLE_USER", "ROLE_ADMIN")
     * @return true if any of the authorities is found, else false
     */
    boolean hasAnyAuthority(String... authorities);

    /**
     * <p>
     * Determines if the {@link #getAuthentication()} has a particular authority within
     * {@link Authentication#getAuthorities()}.
     * </p>
     * <p>
     * This is similar to {@link #hasAuthority(String)} except that this method implies
     * that the String passed in is a role. For example, if "USER" is passed in the
     * implementation may convert it to use "ROLE_USER" instead. The way in which the role
     * is converted may depend on the implementation settings.
     * </p>
     * @param role the authority to test (i.e. "USER")
     * @return true if the authority is found, else false
     */
    boolean hasRole(String role);

    /**
     * <p>
     * Determines if the {@link #getAuthentication()} has any of the specified authorities
     * within {@link Authentication#getAuthorities()}.
     * </p>
     * <p>
     * This is a similar to hasAnyAuthority except that this method implies that the
     * String passed in is a role. For example, if "USER" is passed in the implementation
     * may convert it to use "ROLE_USER" instead. The way in which the role is converted
     * may depend on the implementation settings.
     * </p>
     * @param roles the authorities to test (i.e. "USER", "ADMIN")
     * @return true if any of the authorities is found, else false
     */
    boolean hasAnyRole(String... roles);

    /**
     * Always grants access.
     * @return true
     */
    boolean permitAll();

    /**
     * Always denies access
     * @return false
     */
    boolean denyAll();

    /**
     * Determines if the {@link #getAuthentication()} is anonymous
     * @return true if the user is anonymous, else false
     */
    boolean isAnonymous();

    /**
     * Determines ifthe {@link #getAuthentication()} is authenticated
     * @return true if the {@link #getAuthentication()} is authenticated, else false
     */
    boolean isAuthenticated();

    /**
     * Determines if the {@link #getAuthentication()} was authenticated using remember me
     * @return true if the {@link #getAuthentication()} authenticated using remember me,
     * else false
     */
    boolean isRememberMe();

    /**
     * Determines if the {@link #getAuthentication()} authenticated without the use of
     * remember me
     * @return true if the {@link #getAuthentication()} authenticated without the use of
     * remember me, else false
     */
    boolean isFullyAuthenticated();

    /**
     * Determines if the {@link #getAuthentication()} has permission to access the target
     * given the permission
     * @param target the target domain object to check permission on
     * @param permission the permission to check on the domain object (i.e. "read",
     * "write", etc).
     * @return true if permission is granted to the {@link #getAuthentication()}, else
     * false
     */
    boolean hasPermission(Object target, Object permission);

    /**
     * Determines if the {@link #getAuthentication()} has permission to access the domain
     * object with a given id, type, and permission.
     * @param targetId the identifier of the domain object to determine access
     * @param targetType the type (i.e. com.example.domain.Message)
     * @param permission the perission to check on the domain object (i.e. "read",
     * "write", etc)
     * @return true if permission is granted to the {@link #getAuthentication()}, else
     * false
     */
    boolean hasPermission(Object targetId, String targetType, Object permission);

}

WebSecurityExpressionRoot 可以理解为权限表达式对应的 JAVA 类,他除了接口中的表达式支持,还添加了一个 hasIpAddress 方法,可以对 IP 进行控制。

public class WebSecurityExpressionRoot extends SecurityExpressionRoot {

    /**
     * Allows direct access to the request object
     */
    public final HttpServletRequest request;

    public WebSecurityExpressionRoot(Authentication a, FilterInvocation fi) {
        super(a);
        this.request = fi.getRequest();
    }

    /**
     * Takes a specific IP address or a range using the IP/Netmask (e.g. 192.168.1.0/24 or
     * 202.24.0.0/14).
     * @param ipAddress the address or range of addresses from which the request must
     * come.
     * @return true if the IP address of the current request is in the required range.
     */
    public boolean hasIpAddress(String ipAddress) {
        IpAddressMatcher matcher = new IpAddressMatcher(ipAddress);
        return matcher.matches(this.request);
    }

}