• SpringSecurity系列一:03 SpringSecurity 的默认认证数据源是什么?


    1. 查看 SpringBootWebSecurityConfiguration 类的 defaultSecurityFilterChain 方法

    在这里插入图片描述

    2. 进入 HttpSecurity 类的 formLogin 方法

    在这里插入图片描述

    3. 进入 FormLoginConfigurer类的 FormLoginConfigurer 方法

    在这里插入图片描述

    4. 进入 UsernamePasswordAuthenticationFilter 类的 attempAuthentication 方法

    在这里插入图片描述

    1. attemptAuthentication 方法
    public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
        
        private AuthenticationManager authenticationManager;
    
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {
            // 1、判断是否是 post 请求方式;
            if (this.postOnly && !request.getMethod().equals("POST")) {
                throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
            }
            // 2、从请求中根据表单name属性的值获取用户名 username
            String username = obtainUsername(request);
            username = (username != null) ? username : "";
            username = username.trim();
            // 3、从请求中根据表单name属性的值获取密码 password
            String password = obtainPassword(request);
            password = (password != null) ? password : "";
            // 4、将 username 和 password 封装成 UsernamePasswordAuthenticationToken
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            setDetails(request, authRequest);
            // 5、调用 AuthenticationManager 中的 authenticate 方法完成身份认证
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    
        @Nullable
        protected String obtainPassword(HttpServletRequest request) {
            return request.getParameter(this.passwordParameter);
        }
    
        @Nullable
        protected String obtainUsername(HttpServletRequest request) {
            return requesta.getParameter(this.usernameParameter);
        }
    
        protected AuthenticationManager getAuthenticationManager() {
            return this.authenticationManager;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    具体流程:

    1. 判断是否是 post 请求方式;

    2. 从请求中根据表单 name 属性的值获取用户名 username;

    3. 从请求中根据表单 name 属性的值获取密码 password;

    4. 将 username 和 password 封装成 UsernamePasswordAuthenticationToken authRequest;

    5. 调用 AuthenticationManager 接口中的 authenticate(authRequest) 方法完成身份认证;

    6. AuthenticationManager 接口中的 authenticate 方法完成身份认证

    2. AuthenticationManager 接口

    在上述方法中通过 AuthenticationManager 接口中的 authenticate(authRequest) 方法完成身份认证,事实上,AuthenticationManager 是认证相关的顶层接口,该接口中只有一个 authenticate 方法,请求体是一个包含认证信息的不完整的 Authentication 对象,响应体是一个包含凭据的完整 的Authentication 对象 。

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

    AuthenticationManager 的实现l类如下:

    在这里插入图片描述
    当我们使用debug模式按F7进入 attemptAuthentication 方法内部时,发现真正实现认证的是AuthenticationManager 接口的实现类 ProvideManager,调用实现类中的 authenticate 方法完成认证,AuthenticationManager 接口一般不直接认证,而是通过子类去认证。

    下面我们使用debug模式按F7进入authenticate 方法,将进入ProvideManager的 authenticate 方法。

    5. 进入 ProvideManager 类的 authenticate 方法

    public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
    
        // AuthenticationManager 接口
        private AuthenticationManager parent;
    
        // 存储AuthenticationProvider接口实现类的List集合
        private List<AuthenticationProvider> providers = Collections.emptyList();
    
        /**
         * @param authentication 待认证的对象
         * @return Authentication 认证成功后填充的对象
        */
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            Class<? extends Authentication> toTest = authentication.getClass();
            AuthenticationException lastException = null;
            AuthenticationException parentException = null;
            Authentication result = null;
            Authentication parentResult = null;
            int currentPosition = 0;
            int size = this.providers.size();
    		// 遍历providers集合
            // 如果该providers中如果有一个AuthenticationProvider 的 supports 方法返回 true
            // 那么就会调用该 AuthenticationProvider 的 authenticate 方法认证
            // 如果认证成功则整个认证过程结束。
            // 如果不成功,则继续使用下一个合适的AuthenticationProvider进行认证
            // 只要有一个认证成功则为认证成功。
            for (AuthenticationProvider provider : getProviders()) {
                if (!provider.supports(toTest)) {
                    continue;
                }
                try {
                    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;
                }
            }
            // 如果上述过程没有认证成功,result==null。
            // 并且 AuthenticationManager parent != null,那么会使用该 parent 继续认证。
            if (result == null && this.parent != null) {
                // Allow the parent to try.
                try {
                    // 回调AuthenticationManager接口实现类ProviderManager中的该方法
                    parentResult = this.parent.authenticate(authentication);
                    result = parentResult;
                }catch (ProviderNotFoundException ex) {
                }catch (AuthenticationException ex) {
                    parentException = ex;
                    lastException = ex;
                }
            }
            // 省略...
        }
        
        public List<AuthenticationProvider> getProviders() {
            return this.providers;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    整个方法的认证流程:

    认证是通过 AuthenticationManager 实现类 ProviderManage 的 authenticate 方法完成的,该方法会遍历 List providers 集合获取 AuthenticationProvider 的实现类完成认证,如果该 providers 中如果有一个 AuthenticationProvider 的 supports 方法返回 true,那么就会调用该 AuthenticationProvider 的 authenticate 方法认证,如果认证成功则整个认证过程结束。如果不成功,则继续使用下一个合适的 AuthenticationProvider 进行认证,只要有一个认证成功则为认证成功。

    如果上述过程没有认证成功,且该ProviderManager的成员变量AuthenticationManager parent不为null,那么会使用该 parent 继续认证。

    1. ProviderManager类

    ProviderManager 是AuthenticationManager 接口的具体实现类,对请求认证链进行管理。

    AuthenticationManager 接口,ProviderManager类 ,AuthenticationProvider接口 的关系?
    在这里插入图片描述
    AuthenticationManager 是认证相关的核心接口,在实际需求中,我们可能会允许用户使用用户名+密码登录,同时允许用户使用邮箱+密码,手机号码+密码登录,所以说 AuthenticationManager一般不直接认证。

    AuthenticationManager 接口的常用实现类 ProviderManager 内部会维护一个 List providers 列表,存放多种认证方式,实际上这是委托者模式的应用。也就是说,核心的认证入口始终只有一个:AuthenticationManager。

    不同的认证方式:用户名+密码(UsernamePasswordAuthenticationToken),邮箱+密码,手机号码+密码登录则对应了三个不同的 AuthenticationProvider。

    2. AuthenticationProvider 接口

    在 ProviderManager 类的 authenticate 方法中,遍历List providers 列表,AuthenticationProvider 将按照认证请求链顺序去执行,如果 result 非null 表示验证通过,不再尝试验证其它的 provider 进行认证;如果后续提供的 AuthenticationProvider 成功地对请求进行身份认证,则忽略先前的身份验证异常及null响应,并将使用成功的身份验证。

    AuthenticationProvider接口和AuthenticationManager接口很相似,只多了一个supports方法,该接口通常是提供给开发人员实现,可以实现自定义的安全认证方式:

    public interface AuthenticationProvider {
       // 认证方法
       Authentication authenticate(Authentication authentication) throws AuthenticationException;
       // 验证是否支持某种身份认证方式;
       boolean supports(Class<?> authentication);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    ProviderManager中有一个List用来存储定义的 AuthenticationProvider 认证实现类,也可以认为是一个认证处理器链来支持同一个应用中的多个不同身份认证机制,ProviderManager 将会根据多个实现类的顺序来进行验证 。

    AuthenticationProvider 接口的实现类:
    在这里插入图片描述

    3. 流程解析

    authenticate 方法的核心流程:

    for (AuthenticationProvider provider : getProviders()) {
        if (!provider.supports(toTest)) {
            continue;
        }
        try {
            // 调用AuthenticationProvider实现类的 authenticate 方法完成认证
            result = provider.authenticate(authentication);
        }
    }
    // 如果上述过程没有认证成功,result==null。
    if (result == null && this.parent != null) {
        try {
            // 回调AuthenticationManager接口实现类ProviderManager中的该方法
            parentResult = this.parent.authenticate(authentication);
            result = parentResult;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    1. Authentication 待认证对象

    在这里插入图片描述

    2. AnonymousAuthenticationProvider 类的 supports 方法

    在这里插入图片描述

    调用 AnonymousAuthenticationProvider类中的 supports 方法,判断是否支持UsernamePasswordAuthenticationToken 的认证:

    // 匿名身份验证提供程序
    public class AnonymousAuthenticationProvider implements AuthenticationProvider, MessageSourceAware {
       // ... 省略代码
        
       @Override
       public boolean supports(Class<?> authentication) {
          // 判断AnonymousAuthenticationToken对象是否是authentication对象的父类或接口
          // 判断AnonymousAuthenticationToken对象和authentication对象是否是同一个类或者同一个接口
          return (AnonymousAuthenticationToken.class.isAssignableFrom(authentication));
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    AnonymousAuthenticationProvider 不支持 UsernamePasswordAuthenticationToken 认证方式,因为跳出循环,将回调 AuthenticationManager 实现类 ProviderManager 中的 authenticate 方法进行认证:

    在这里插入图片描述

    3. DaoAuthenticationProvider 类 supports 方法

    回调 ProviderManager 类中的 authenticate 方法,此时 AuthenticationProvider的实现类为DaoAuthenticationProvider :

    在这里插入图片描述

    DaoAuthenticationProvider 继承抽象类 AbstractUserDetailsAuthenticationProvider:

    public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
    }
    
    • 1
    • 2

    执行 AbstractUserDetailsAuthenticationProvider 类中的 supports() 方法,判断是否支持UsernamePasswordAuthenticationToken 的认证:

    public abstract class AbstractUserDetailsAuthenticationProvider
        implements AuthenticationProvider, InitializingBean, MessageSourceAware {
        
        @Override
        public boolean supports(Class<?> authentication) {
            return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    supports 方法将返回 true,开始调用 DaoAuthenticationProvider类中的 authenticate 方法执行 UsernamePasswordAuthenticationToken 的认证。

    4. AbstractUserDetailsAuthenticationProvider 类的 authenticate 方法

    因为DaoAuthenticationProvider继承自抽象类 AbstractUserDetailsAuthenticationProvider ,因此实际调用的是 AbstractUserDetailsAuthenticationProvider 类的 authenticate 方法:

    public abstract class AbstractUserDetailsAuthenticationProvider
        implements AuthenticationProvider, InitializingBean, MessageSourceAware {
    
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            String username = determineUsername(authentication);
            boolean cacheWasUsed = true;
            // 先从缓存中获取认证成功的UserDeails对象
            UserDetails user = this.userCache.getUserFromCache(username);
            // 如果缓存中不存在
            if (user == null) {
                cacheWasUsed = false;
                try {
                    // 从数据源中获取 
                    user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
                } // ...
            }
            // ...
            // 将认证成功的UserDetails对象放入缓存中
            if (!cacheWasUsed) {
                this.userCache.putUserInCache(user);
            }
            // ...
        }     
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    看到这儿,我们的问题就快解决了,默认的数据源是什么?

    5. DaoAuthenticationProvider 类的 retrieveUser 方法

    进入 retrieveUser 方法的内部,调用了DaoAuthenticationProvider 中的 retrieveUser 方法:

    public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
        
        @Override
        protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
            prepareTimingAttackProtection();
            try {
                // 通过username获取 UserDetails 对象
                UserDetails loadedUser 
                    = this.getUserDetailsService().loadUserByUsername(username);
                if (loadedUser == null) {
                    throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
                }
                return loadedUser;
            } // ...
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    调用 UserDeatailService 接口中的 loadUserByUsername 方法完成认证,UserDeatailService 的实现类:
    在这里插入图片描述

    public interface UserDetailsService {
    	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
    }
    
    • 1
    • 2
    • 3

    debug模式下F7进入 loadUserByUsername 方法的内部,发现调用的是UserDetailsService 接口的实现类 InMemoryUserDetailsManager 中的 loadUserByUsername 方法。

    public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {
        
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            UserDetails user = this.users.get(username.toLowerCase());
            if (user == null) {
                throw new UsernameNotFoundException(username);
            }
            // 常见一个UserDetails对象并返回
            return new User(user.getUsername(), 
                            user.getPassword(), 
                            user.isEnabled(), 
                            user.isAccountNonExpired(),
                            user.isCredentialsNonExpired(), 
                            user.isAccountNonLocked(), 
                            user.getAuthorities());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    最终,我们终于找到了默认的数据源就是 InMemoryUserDetailsManager,基于内存的用户认证。

    UserDetailService 是顶层父接口,用来修改默认认证的数据源信息,接口中 loadUserByUserName 方法是用来在认证时进行用户名认证方法,默认实现使用是内存实现,如果想要修改数据库实现我们只需要自定义 UserDetailService 实现,最终返回 UserDetails 实例即可。

  • 相关阅读:
    水平基准和垂直基准
    【编译原理】-- 递归下降语法分析设计原理与实现(C语言实现)
    3712. 根能抵达的点
    爆肝!阿里最新版的Spring Security源码手册,强行霸占GitHub榜首
    用 DataGridView 控件显示数据
    会议论文和期刊论文在写作上有什么区别?有什么侧重点?
    HFSS笔记——优化设计optimetrics
    YOLOv8-seg改进:注意力系列篇 | 一种简单有效的可变形的自注意力模块DAT | CVPR 2022
    MATLAB | 如何绘制三维曲线、曲面、多边形投影(三视图)?
    数据库——表结构相关SQL
  • 原文地址:https://blog.csdn.net/qq_42764468/article/details/126795387