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


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

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

    认证

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

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

    1. public interface Authentication extends Principal, Serializable {
    2. Collectionextends GrantedAuthority> getAuthorities();
    3. Object getCredentials();
    4. Object getDetails();
    5. Object getPrincipal();
    6. boolean isAuthenticated();
    7. void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
    8. }
    • getAuthorities()方法:用来获取用户权限
    • getCredentials()方法:用来获取用户凭证,一般来说是用户的密码
    • getDetails()方法:用来获取用户携带的详细信息,可能是当前请求之类等
    • getPrincipal()方法:用来获取当前用户,例如一个用户名或者一个用户对象
    • isAuthenticated()方法:当前用户是否认证成功

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

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

    1. public interface AuthenticationManager {
    2. Authentication authenticate(Authentication authentication) throws AuthenticationException;
    3. }

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

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

    1. public interface AuthenticationProvider {
    2. Authentication authenticate(Authentication authentication) throws AuthenticationException;
    3. boolean supports(Class authentication);
    4. }

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

    1. public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
    2. @Override
    3. public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    4. Classextends Authentication> toTest = authentication.getClass();
    5. AuthenticationException lastException = null;
    6. AuthenticationException parentException = null;
    7. Authentication result = null;
    8. Authentication parentResult = null;
    9. int currentPosition = 0;
    10. int size = this.providers.size();
    11. //这里会遍历所有的AuthenticationProvider判断当前的Authentication是否支持
    12. for (AuthenticationProvider provider : getProviders()) {
    13. if (!provider.supports(toTest)) {
    14. continue;
    15. }
    16. if (logger.isTraceEnabled()) {
    17. logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
    18. provider.getClass().getSimpleName(), ++currentPosition, size));
    19. }
    20. try {
    21. //如果支持的话,就调用AuthenticationProvider的认证方法
    22. result = provider.authenticate(authentication);
    23. if (result != null) {
    24. copyDetails(authentication, result);
    25. break;
    26. }
    27. }
    28. catch (AccountStatusException | InternalAuthenticationServiceException ex) {
    29. prepareException(ex, authentication);
    30. throw ex;
    31. }
    32. catch (AuthenticationException ex) {
    33. lastException = ex;
    34. }
    35. }
    36. if (result == null && this.parent != null) {
    37. try {
    38. //没有AuthenticationProvider处理的话就就调用ProviderManager在构造函数中传进来的一个名字叫parent的AuthenticationProvider来认证
    39. parentResult = this.parent.authenticate(authentication);
    40. result = parentResult;
    41. }
    42. catch (ProviderNotFoundException ex) {
    43. }
    44. catch (AuthenticationException ex) {
    45. parentException = ex;
    46. lastException = ex;
    47. }
    48. }
    49. if (result != null) {
    50. if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
    51. ((CredentialsContainer) result).eraseCredentials();
    52. }
    53. if (parentResult == null) {
    54. this.eventPublisher.publishAuthenticationSuccess(result);
    55. }
    56. return result;
    57. }
    58. if (lastException == null) {
    59. lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
    60. new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
    61. }
    62. if (parentException == null) {
    63. prepareException(lastException, authentication);
    64. }
    65. throw lastException;
    66. }
    67. }

    授权

            当人成认证之后接下来就是授权了。在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它们之间的关系。

  • 相关阅读:
    智慧法院档案数字化解决方案
    springboot嵌入式数据库H2初探
    深入分析APK文件格式
    C++ —— 二叉搜索树
    Android recyclerview 浮动头部
    java对象的内存布局
    手把手教你如何自制目标检测框架(从理论到实现)
    MAC 创建crontab任务一直不执行的原因
    享元模式模式简介
    NLP 开源形近字算法之相似字列表(番外篇)
  • 原文地址:https://blog.csdn.net/qq_27062249/article/details/127814530