SpringSecurity 认证流程

前言

通过上文了解SpringSecurity核心组件后,就可以进一步了解其认证的实现流程了。

认证入口(过滤器)

在SpringSecurity中处理认证逻辑是在UsernamePasswordAuthenticationFilter这个过滤器中实现的。UsernamePasswordAuthenticationFilter继承于AbstractAuthenticationProcessingFilter这个父类。

image.png

而在UsernamePasswordAuthenticationFilter没有实现doFilter方法,所以认证的逻辑需要先看AbstractAuthenticationProcessingFilter中的doFilter方法。

image.png

上面的核心代码是

Authentication authenticationResult = attemptAuthentication(request, response);

attemptAuthentication方法的作用是获取Authentication对象对应的其实就是认证过程,进入到UsernamePasswordAuthenticationFilter中来查看具体的实现。

	@Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException {
    
    
		if (this.postOnly && !request.getMethod().equals("POST")) {
    
    
			throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
		}
		String username = obtainUsername(request);
		username = (username != null) ? username : "";
		username = username.trim();
		String password = obtainPassword(request);
		password = (password != null) ? password : "";
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);
		return this.getAuthenticationManager().authenticate(authRequest);
	}

上面代码的含义非常清晰

  1. 该方法只支持POST方式提交的请求
  2. 获取账号和密码
  3. 通过账号密码获取了UsernamePasswordAuthenticationToken对象
  4. 设置请求的详细信息
  5. 通过AuthenticationManager来完成认证操作

在上面的逻辑中出现了一个对象AuthenticationManager

扫描二维码关注公众号,回复: 16748351 查看本文章

认证管理器

AuthenticationManager接口中就定义了一个方法authenticate方法,处理认证的请求。

public interface AuthenticationManager {
    
    

	Authentication authenticate(Authentication authentication) throws AuthenticationException;

}

认证器说明

AuthenticationManager的默认实现是ProviderManager,而在ProviderManagerauthenticate方法中实现的操作是循环遍历成员变量List<AuthenticationProvider> providers。该providers中如果有一个AuthenticationProvidersupports函数返回true,那么就会调用该AuthenticationProviderauthenticate函数认证,如果认证成功则整个认证过程结束。如果不成功,则继续使用下一个合适的AuthenticationProvider进行认证,只要有一个认证成功则为认证成功。

image.png

在当前环境下默认的实现提供是

image.png

默认认证器的实现

进入到AbstractUserDetailsAuthenticationProvider中的认证方法

image.png

然后进入到retrieveUser方法中,具体的实现是DaoAuthenticationProvidergetUserDetailsService会获取到我们自定义的UserServiceImpl对象,也就是会走我们自定义的认证方法了。

	@Override
	protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
    
    
		prepareTimingAttackProtection();
		try {
    
    
     
			UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
			if (loadedUser == null) {
    
    
				throw new InternalAuthenticationServiceException(
						"UserDetailsService returned null, which is an interface contract violation");
			}
			return loadedUser;
		}
		catch (UsernameNotFoundException ex) {
    
    
			mitigateAgainstTimingAttack(authentication);
			throw ex;
		}
		catch (InternalAuthenticationServiceException ex) {
    
    
			throw ex;
		}
		catch (Exception ex) {
    
    
			throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
		}
	}

如果账号存在就会开始密码的验证,不过在密码验证前还是会完成一个检查

image.png

image.png

然后就是具体的密码验证

additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);

具体的验证的逻辑

	protected void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    
    
        // 密码为空
		if (authentication.getCredentials() == null) {
    
    
			this.logger.debug("Failed to authenticate since no credentials provided");
			throw new BadCredentialsException(this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
        // 获取表单提交的密码
		String presentedPassword = authentication.getCredentials().toString();
		// 表单提交的密码和数据库查询的密码 比较是否相对
		if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
    
    
			this.logger.debug("Failed to authenticate since password does not match stored value");
			throw new BadCredentialsException(this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
	}

上面的逻辑会通过对应的密码编码器来处理,如果是非加密的情况会通过NoOpPasswordEncoder来处理

    public boolean matches(CharSequence rawPassword, String encodedPassword) {
    
    
        return rawPassword.toString().equals(encodedPassword);
    }

image.png

如果有加密处理,就选择对应的加密对象来处理,比如SpringSecurity 入门使用的BCryptPasswordEncoder来处理

image.png

总结

Spring Security认证流程:

  1. 请求首先会进入过滤器,它的作用在于验证系统设置受限资源的过滤器。
  2. 请求被过滤器接收后,会将其传递给AuthenticationProvider。AuthenticationProvider需要用户的信息以及UserDetailsService的认证实现方式。
  3. UserDetailsService是被UserDetail继承的,UserDetail封装了User用户信息。在这个阶段,用户信息被封装进AuthenticationProvider。
  4. 接着,封装了用户信息的AuthenticationProvider会被传递给AuthenticationManager中的ProviderManager。
    ProviderManager会对AuthenticationProvider进行再次审核,最终返回过滤器。

通过Spring Security的认证实现,可以看到虽然代码简洁明了,但是其扩展性极强。Spring Security支持多种认证和授权机制,包括用户名密码认证、JWT令牌认证、OAuth2认证等等。同时,Spring Security还提供了多种默认配置,可以根据需要进行调整和扩展。

猜你喜欢

转载自blog.csdn.net/qq_28314431/article/details/132979838
今日推荐