Spring Security基本框架之认证和授权

本文内容来自王松老师的《深入浅出Spring Security》,自己在学习的时候为了加深理解顺手抄录的,有时候还会写一些自己的想法。

在具体学习Spring Security各种方法用法之前,我们先介绍下Spring Security中常见的概念,以及认证、授权的思路,方便读者整体把握Spring Security架构,这里涉及的所有组件在后面的章节中还会详细介绍。

Spring Security在架构设计中认证(Authentication)和授权(Authorization)是分开的后面我们会学习到。无论采用什么样的认证方式都不会影响到授权,这是两个独立的存在。这种独立带来的好处之一就是Spring Security可以非常方便的整合一些外部的认证方案。

在Spring Security中,用户的认证信息主要由Authentication的实现类来保存的。Authentication接口定义如下:

public interface Authentication extends Principal, Serializable {

    Collection getAuthorities();

    Object getCredentials();

    Object getDetails();

    Object getPrincipal();

    boolean isAuthenticated();

    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
  • getAuthorities()方法:用来获取用户权限
  • getCredentials()方法:用来获取用户凭证,一般来说是用户的密码
  • getDetails()方法:用来获取用户携带的详细信息,可能是当前请求之类等
  • getPrincipal()方法:用来获取当前用户,例如一个用户名或者一个用户对象
  • isAuthenticated()方法:当前用户是否认证成功

当用户使用用户名和密码登录或者采用Remember-me登录时,都会对应一个不同的Authentication对象实例。

Spring Security中的认证工作主要由AuthenticationManager接口来负责,我们看看该接口的定义:

public interface AuthenticationManager {

    Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

AuthenticationManager就只有一个认证方法,返回一个认证对象Authentication。

AuthenticationManager的主要实现类是ProviderManager。ProviderManager管理了很多的AuthenticationProvider实例,AuthenticationProvider有点类似AuthenticationManager,但是它多了一个supports方法来判断是否支持给定的Authentication类型。由于Authentication拥有众多不同的实现类型,这些不同的实现类又由不同的AuthenticationProvider来处理,所以这里需要一个supports是方法来判断当前的AuthenticationProvider是否支持对应的Authentication。再一次完成认证流程中,可能会同时存在多个AuthenticationProvider,例如项目中同时支持表单登录和短信验证码登录。多个AuthenticationProvider统一由ProviderManager来管理。同时,ProviderManager具有一个可选的parent,如果所有的AuthenticationProvider都认证失败的话就会调用parent进行认证。parent相当于一个备用的认证方式。

public interface AuthenticationProvider {

    Authentication authenticate(Authentication authentication) throws AuthenticationException;

    boolean supports(Class authentication);
}

我们来看看ProviderManager的认证方法:authenticate()

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Class toTest = authentication.getClass();
        AuthenticationException lastException = null;
        AuthenticationException parentException = null;
        Authentication result = null;
        Authentication parentResult = null;
        int currentPosition = 0;
        int size = this.providers.size();
        //这里会遍历所有的AuthenticationProvider判断当前的Authentication是否支持
        for (AuthenticationProvider provider : getProviders()) {
            if (!provider.supports(toTest)) {
                continue;
            }
            if (logger.isTraceEnabled()) {
                logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
                        provider.getClass().getSimpleName(), ++currentPosition, size));
            }
            try {
                //如果支持的话,就调用AuthenticationProvider的认证方法
                result = provider.authenticate(authentication);
                if (result != null) {
                    copyDetails(authentication, result);
                    break;
                }
            }
            catch (AccountStatusException | InternalAuthenticationServiceException ex) {
                prepareException(ex, authentication);
                throw ex;
            }
            catch (AuthenticationException ex) {
                lastException = ex;
            }
        }
        if (result == null && this.parent != null) {
            try {
                //没有AuthenticationProvider处理的话就就调用ProviderManager在构造函数中传进来的一个名字叫parent的AuthenticationProvider来认证
                parentResult = this.parent.authenticate(authentication);
                result = parentResult;
            }
            catch (ProviderNotFoundException ex) {

            }
            catch (AuthenticationException ex) {
                parentException = ex;
                lastException = ex;
            }
        }
        if (result != null) {
            if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
                ((CredentialsContainer) result).eraseCredentials();
            }

            if (parentResult == null) {
                this.eventPublisher.publishAuthenticationSuccess(result);
            }
            return result;
        }
        if (lastException == null) {
            lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
                    new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
        }
        if (parentException == null) {
            prepareException(lastException, authentication);
        }
        throw lastException;
    }
}

当人成认证之后接下来就是授权了。在Spring Security的授权体系中有两个关键的接口:

  • AccessDecisionMamager
  • AccessDecisionVoter

AccessDecisionVoter是一个投票器,投票器会检查用户是否具备应 有的角色,进而投出赞成、反对或者弃权票;AccessDecisionManager则 是一个决策器,来决定此次访问是否被允许。AccessDecisionVoter和 AccessDecisionManager都有众多的实现类,在 AccessDecisionManager 中会挨个遍历AccessDecisionVoter ,进而决定是否允许用户访问,因而 AccessDecisionVoter和 AccessDecisionManager 两者的关系类似于 AuthenticationProvider和 ProviderManager的关系。

在Spring Security中,用户请求一个资源(通常是一个网络接口或 者一个Java 方法)所需要的角色会被封装成一个 ConfigAttribute 对象,在 ConfigAttribute中只有一个 getAttribute 方法,该方法返回一个 String 字符 串,就是角色的名称。一般来说,角色名称都带有一个ROLE_ 前缀,投票器AccessDecisionVoter 所做的事情,其实就是比较用户所具备的角色 和请求某个资源所需的ConfigAttribute之间的关系。

看完的小伙伴可以在脑海里一边想一遍动手画一下Authentication、AuthenticationManager、AuthenticationProvider、ProviderManager、AccessDecisionMamager、AccessDecisionVoter它们之间的关系。

Original: https://blog.csdn.net/qq_27062249/article/details/127814530
Author: 大后生大大大
Title: Spring Security基本框架之认证和授权

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/660969/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球