SpringSecurity6从入门到实战之登录表单的提交(依旧源码级别讲解,耐心看完)

SpringSecurity6从入门到实战之登录表单的提交

文接上回,当SpringSecurity帮我们生成了一个默认对象.本文继续对登录流程进行探索,我们如何通过账号密码进行表单的提交,SpringSecurity在这过程中又帮助我们做了什么

登录表单的提交的源码分析

在之前了解了为什么所有的请求都会进行认证操作,我们也直接把目光放到源码中这个地方defaultSecurityFilterChain()

 

less

复制代码

@Configuration(    proxyBeanMethods = false ) @ConditionalOnWebApplication(    type = Type.SERVLET ) class SpringBootWebSecurityConfiguration {    SpringBootWebSecurityConfiguration() {   } ​    @Configuration(        proxyBeanMethods = false   )    @ConditionalOnMissingBean(        name = {"springSecurityFilterChain"}   )    @ConditionalOnClass({EnableWebSecurity.class})    @EnableWebSecurity    static class WebSecurityEnablerConfiguration {        WebSecurityEnablerConfiguration() {       }   } ​    @Configuration(        proxyBeanMethods = false   )    @ConditionalOnDefaultWebSecurity    static class SecurityFilterChainConfiguration {        SecurityFilterChainConfiguration() {       } ​        @Bean        @Order(2147483642)        SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {           ((AuthorizeHttpRequestsConfigurer.AuthorizedUrl)http.authorizeHttpRequests().anyRequest()).authenticated();            //这里就是进行表单登录的入口方法了            http.formLogin();            http.httpBasic();            return (SecurityFilterChain)http.build();       }   } }

我们进入formLogin()中继续看,可以看到这个方法formLogin().这里创建了一个FormLoginConfigurer,我们继续顺着这个构造方法进去看看

 

csharp

复制代码

public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception { return getOrApply(new FormLoginConfigurer<>()); }

 

scss

复制代码

public FormLoginConfigurer() { super(new UsernamePasswordAuthenticationFilter(), null); usernameParameter("username"); passwordParameter("password"); }

这里可以看到FormLoginConfigurer调用了父类构造并且传了一个UsernamePasswordAuthenticationFilter对象,之前有介绍过这个过滤器是专门做账号密码认证的,那我们继续看向这个过滤器new UsernamePasswordAuthenticationFilter()

 

java

复制代码

   public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {        //首先判断是否为POST请求        if (this.postOnly && !request.getMethod().equals("POST")) {            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());       } else {            //通过过去用户名和密码进行非空判断            String username = this.obtainUsername(request);            username = username != null ? username.trim() : "";            String password = this.obtainPassword(request);            password = password != null ? password : "";            //组装成一个UsernamePasswordAuthenticationToken令牌对象,方便传递            UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);            this.setDetails(request, authRequest);            //这里将令牌对象传入,继续看看authenticate()做了什么            return this.getAuthenticationManager().authenticate(authRequest);       }   }

最终发现这个authenticate()是一个接口下的抽象方法,实际执行的是 AuthenticationManager 接口实现类 ProviderManager 中的 authenticate() 方法,在该方法中调用 AuthenticationProvider 接口的authenticate() 方法:

我们继续看:

image.png

image.png

可以发现这里传入了authentication对象最终返回的还是authentication对象,说明这里肯定为这个对象的其他属性进行了操作,我们继续看到provider.authenticate().

image.png

实际执行的是 AuthenticationProvider 接口实现类 AbstractUserDetailsAuthenticationProvider 中的 authenticate() 方法,在该方法中调用 retrieveUser() 方法:

 

kotlin

复制代码

@Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported")); String username = determineUsername(authentication); boolean cacheWasUsed = true;        //第一次从缓存中获取user对象,肯定是找不到的 UserDetails user = this.userCache.getUserFromCache(username); if (user == null) { cacheWasUsed = false; try {                //将输入的用户名和token对象传入retrieveUser()方法,最终返回了UserDetails user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); } catch (UsernameNotFoundException ex) { this.logger.debug("Failed to find user '" + username + "'"); if (!this.hideUserNotFoundExceptions) { throw ex; } throw new BadCredentialsException(this.messages .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract"); } try { this.preAuthenticationChecks.check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } catch (AuthenticationException ex) { if (!cacheWasUsed) { throw ex; } // There was a problem, so try again after checking // we're using latest data (i.e. not from the cache) cacheWasUsed = false; user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); this.preAuthenticationChecks.check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } this.postAuthenticationChecks.check(user); if (!cacheWasUsed) { this.userCache.putUserInCache(user); } Object principalToReturn = user; if (this.forcePrincipalAsString) { principalToReturn = user.getUsername(); } return createSuccessAuthentication(principalToReturn, authentication, user); }

向下查询retrieveUser(),由于retrieveUser()是抽象方法而当前类有且只有一个子类所以直接看到AbstractUserDetailsAuthenticationProvider的子类DaoAuthenticationProvider中实现的retrieveUser()

image.png

通过username去加载用户,也是看到这个this.getUserDetailsService().loadUserByUsername().可以看到loadUserByUsername还是一个接口

 

java

复制代码

package org.springframework.security.core.userdetails; ​ /** * Core interface which loads user-specific data. * <p> * It is used throughout the framework as a user DAO and is the strategy used by the * {@link org.springframework.security.authentication.dao.DaoAuthenticationProvider * DaoAuthenticationProvider}. * * <p> * The interface requires only one read-only method, which simplifies support for new * data-access strategies. * * @author Ben Alex * @see org.springframework.security.authentication.dao.DaoAuthenticationProvider * @see UserDetails */ public interface UserDetailsService { ​ /** * Locates the user based on the username. In the actual implementation, the search * may possibly be case sensitive, or case insensitive depending on how the * implementation instance is configured. In this case, the <code>UserDetails</code> * object that comes back may have a username that is of a different case than what * was actually requested.. * @param username the username identifying the user whose data is required. * @return a fully populated user record (never <code>null</code>) * @throws UsernameNotFoundException if the user could not be found or the user has no * GrantedAuthority */ UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; ​ }

实际执行的是 UserDetailsService 接口实现类 InMemoryUserDetailsManager 中的 loadUserByUsername() 方法,在该方法中会在 users 集合变量中根据用户输入的帐号获取 UserDetails 信息:

 

scss

复制代码

@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserDetails user = this.users.get(username.toLowerCase()); if (user == null) { throw new UsernameNotFoundException(username); } return new User(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(), user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities()); }

类 InMemoryUserDetailsManager 是由内存 map 支持的接口实现类,基于内存存储,不需要后端数据库

image.png

最终结论

总结:1. 默认用户名 user 和 控制台的密码,是在 SpringSecurity 提供的 User 类中定义生成的;

           2.在表单认证时,基于 InMemoryUserDetailsManager 类具体进行实现,也就是基于内存的实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值