Spring Security用户登录认证流程

Spring Security的认证流程

Spring Security的登录认证流程始于AbstractAuthenticationProcessingFilter,默认使用类为UsernamePasswordAuthenticationFilter,当然也可以自己去自定义。

public abstract class AbstractAuthenticationProcessingFilter {
    ...
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {

		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
        
		// 1. 第一步:判断登录路径
		if (!requiresAuthentication(request, response)) {
			chain.doFilter(request, response);
			return;
		}
        
		Authentication authResult;
        
		try {
            // 2. 第二步:尝试认证
			authResult = attemptAuthentication(request, response);
			if (authResult == null) {
				return;
			}
			sessionStrategy.onAuthentication(authResult, request, response);
		}
		catch (...Exception failed) {{
			// 3. 第三步:认证失败后处理
			unsuccessfulAuthentication(request, response, failed);

			return;
		}
		// 4. 第四步:认证成功后处理
		successfulAuthentication(request, response, chain, authResult);
	}
    ...
}

doFilter()方法中共有4个主要步骤,一个一个来看:

1、判断登录路径

AbstractAuthenticationProcessingFilter类存在一个RequestMatcher引用来保存实现类,在UsernamePasswordAuthenticationFilter类中存在构造器给RequestMather进行赋值,只有请求的请求路径为"/login",请求方式为"POST"时才会进入下一步认证。

public abstract class AbstractAuthenticationProcessingFilter{
	private RequestMatcher requiresAuthenticationRequestMatcher;
    ...
}

public class UsernamePasswordAuthenticationFilter{
    public UsernamePasswordAuthenticationFilter() {
		super(new AntPathRequestMatcher("/login", "POST"));
	}
}

那如果我们想要自己去设置登录路径,那怎么办?

  1. 自定义类继承AbstractAuthenticationProcessingFilter抽象类,手动给它赋值
public class RestLoginAuthenticationFilter 
    		extends AbstractAuthenticationProcessingFilter {

    public RestLoginAuthenticationFilter() {
        super(new AntPathRequestMatcher("/api/user/login", "POST"));
    }
    ...
}

public  class SecurityConfigurer extends WebSecurityConfigurerAdapter {
    
    @override
    protected void configure(HttpSecurity http) throws Exception {

       http.addFilterAt(new RestLoginAuthenticationFilter(), 
                        UsernamePasswordAuthenticationFilter.class)
       ...
    }
}
  1. 在SecurityConfigurer中配置loginProcessingUrl选项

在这种情况下,会给AbstractAuthenticationProcessingFilterRequestMatcher字段赋值一个AntPathRequestMatcher实例,路径为传入的"loginProcessingUrl",方法为"POST"。

    protected void configure(HttpSecurity http) throws Exception {
        http
                .formLogin().loginProcessingUrl("/api/login")
                .and().authorizeRequests().anyRequest().permitAll();
    }	
    
	public T loginProcessingUrl(String loginProcessingUrl) {
		this.loginProcessingUrl = loginProcessingUrl;
        // authFilter 默认为 UsernamePasswordAuthenticationFilter
		this.authFilter.setRequiresAuthenticationRequestMatcher(
                  createLoginProcessingUrlMatcher(loginProcessingUrl));
		return getSelf();
	}

	// FormLoginConfigurer,返回一个AntPathRequestMatcher
	protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
		return new AntPathRequestMatcher(loginProcessingUrl, "POST");
	}

以上两种方法是同样的效果。

2、尝试认证

认证的开始于attemptAuthentication()方法。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain){
    ...
	authResult = attemptAuthentication(request, response);
    ...
}

默认情况下,我们会走向UsernamePasswordAuthenticationFilter的attemptAuthentication()方法。

	public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
        // 1. 从request中取出key为username和password的value,具体可以在配置文件中配置
		String username = obtainUsername(request);
		String password = obtainPassword(request);
		...
		// 2. 根据username和password创建一个Token,
		UsernamePasswordAuthenticationToken authRequest = new 
                        UsernamePasswordAuthenticationToken(username, password);
		setDetails(request, authRequest);

        // 3. 向下调用authenticate()方法
		return this.getAuthenticationManager().authenticate(authRequest);
	}

Token,我们根据其继承结构,我们可以看成它是有如下5个属性的UsernamePasswordAuthenticationToken类,如下:

public class UsernamePasswordAuthenticationToken{
    // 1. 主要信息
	private final Object principal;
    // 2. 凭证
	private Object credentials;
    // 3. 权限
	private final Collection<GrantedAuthority> authorities;
    // 4. 用户细节,这里是包含remoteAddress和sessionId的WebAuthenticationDetails
	private Object details;
    // 5. 是否认证
	private boolean authenticated = false;
}

接着往下走,AuthenticationManager默认的实现类为ProviderManager,在ProviderManager类中存在一个集合引用,保存数据为AuthenticationProvider实例。调用AuthenticationManagerauthenticate() 方法,实质上就是遍历这个集合,逐个调用集合中实例的 authenticate() 方法。

public class ProviderManager {
  private List<AuthenticationProvider> providers = Collections.emptyList();
  public Authentication authenticate(Authentication authentication) {
      	// 1. 第一步,遍历集合,看是否支持当前Token
        Class<? extends Authentication> toTest = authentication.getClass();
		...
		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}
			try {
				result = provider.authenticate(authentication);
				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (XXXException e) {
				// 捕获异常操作
			}
		}
		// 2. 第二步,当前情况下,继续走parent
		if (result == null && parent != null) {
			try {
				result = parentResult = parent.authenticate(authentication);
			}
			catch (XXXException e) {
				// 捕获异常操作
			}
		}
		// 3. 第三步,进行简单验证返回Result
		if (result != null) {
			if (eraseCredentialsAfterAuthentication
					&& (result instanceof CredentialsContainer)) {
				((CredentialsContainer) result).eraseCredentials();
			}
			if (parentResult == null) {
				eventPublisher.publishAuthenticationSuccess(result);
			}
			return result;
		}
		...
	}

}

ProviderManagerauthenticate() 方法大致分为三步:

  • 获取当前Token的class,看看provider们能不能支持,支持就调用他们的authenticate()方法,否则跳过。

  • 遍历完集合provider集合之后,如果没有可以认证当前Token的情况(Result = null),看看parent是否有值,有值则调用。

  • 如果Result不为null,在简单处理后就返回Result。

  • 在默认情况下,集合内的provider会有两个,AnonymousAuthenticationProviderDaoAuthenticationProvider,这里进行一次debug会更清晰。

3、认证失败
private void doFilter(HttpServletRequest request, HttpServletResponse response,                                  FilterChain chain)throws IOException, ServletException {
    ...
	unsuccessfulAuthentication(request, response, failed);
	...
}

protected void unsuccessfulAuthentication(HttpServletRequest request,
                                          HttpServletResponse response, 
               AuthenticationException failed)throws IOException, ServletException {
    	// 1. 清空上下文中的数据
		SecurityContextHolder.clearContext();
		// 2. rememberMe
		rememberMeServices.loginFail(request, response);
		// 3. 调用失败处理器
		failureHandler.onAuthenticationFailure(request, response, failed);
	}

failureHandler默认的实现类SimpleUrlAuthenticationFailureHandler,来看下onAuthenticationFailure() 方法,我们可以很容易看明白。

  • 先看下defaultFailureUrl有没有配置,没有直接返回错误,可以通过http.formLogin.failureUrl(…) 来进行设置。
  • 再看forwardToDestination的值,决定是采用重定向还是转发的形式到defaultFailureUrl。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {	
    ...
	successfulAuthentication(request, response, chain, authResult);
    ...  
}
public class SimpleUrlAuthenticationFailureHandler implements
		AuthenticationFailureHandler {
    private String defaultFailureUrl;
	private boolean forwardToDestination = false;
    ...
	public void onAuthenticationFailure(HttpServletRequest request,
			HttpServletResponse response, AuthenticationException exception)
			throws IOException, ServletException {
		// defaultFailureUrl 配置类可以设置
		if (defaultFailureUrl == null) {
			response.sendError(HttpStatus.UNAUTHORIZED.value(),
				HttpStatus.UNAUTHORIZED.getReasonPhrase());
		}
		else {
			saveException(request, exception);
            // forwardToDestination默认为false
			if (forwardToDestination) {
				request.getRequestDispatcher(defaultFailureUrl)
						.forward(request, response);
			}
			else {
				redirectStrategy.sendRedirect(request, response, defaultFailureUrl);
			}
		}
	}
    ...
}
4、认证成功
	private AuthenticationSuccessHandler successHandler = 
        					new SavedRequestAwareAuthenticationSuccessHandler();

	protected void successfulAuthentication(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain, Authentication authResult)
			throws IOException, ServletException {
		// 1. 在上下文中保存Token
		SecurityContextHolder.getContext().setAuthentication(authResult);
		// 2. RememberMe操作
		rememberMeServices.loginSuccess(request, response, authResult);
		// 3. 发布事件
		if (this.eventPublisher != null) {
			eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
					authResult, this.getClass()));
		}
		// 4. 调用成功处理器
		successHandler.onAuthenticationSuccess(request, response, authResult);
	}

successHandler默认实现为SavedRequestAwareAuthenticationSuccessHandler

public class SavedRequestAwareAuthenticationSuccessHandler {
	...
	public void onAuthenticationSuccess(HttpServletRequest request,
			HttpServletResponse response, Authentication authentication)
			throws ServletException, IOException {
        // 1. 取出存储的request
		SavedRequest savedRequest = requestCache.getRequest(request, response);
		...    
		// 2. 取出路径并重定向
		String targetUrl = savedRequest.getRedirectUrl();
		getRedirectStrategy().sendRedirect(request, response, targetUrl);
	}
    ...
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Security的登录认证流程如下: 1. 用户输入用户名和密码,生成一个AuthenticationToken对象。 2. 这个Token对象被传递给一个实现了AuthenticationManager接口的对象进行验证。 3. AuthenticationManager对Token对象进行验证,验证成功后返回一个Authentication对象。 4. 在验证成功后,可以调用AuthenticationSuccessHandler成功处理器进行跳转。 5. 在createSuccessAuthentication方法中,会重新创建一个UsernamePasswordAuthenticationToken对象,并将已认证状态标志注明。 6. 在认证流程中,我们会执行authenticationManager的authenticate方法,该方法实际上是一个接口,里面只有一个空方法。 总结一下,Spring Security的登录认证流程包括用户输入凭证信息,验证凭证信息,并返回认证结果的过程。123 #### 引用[.reference_title] - *1* [spring-security-用户登陆验证流程](https://blog.csdn.net/a1396537376/article/details/90706729)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}} ] [.reference_item] - *2* *3* [SpringSecurity登录认证流程](https://blog.csdn.net/weixin_52353216/article/details/127359420)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值