首页 > 其他分享 >SpringSecurity过滤器之UsernamePasswordAuthenticationFilter

SpringSecurity过滤器之UsernamePasswordAuthenticationFilter

时间:2023-05-03 12:33:54浏览次数:43  
标签:request SpringSecurity UsernamePasswordAuthenticationFilter authentication ex 过滤

UsernamePasswordAuthenticationFilter处理表单登录及认证。

AbstractAuthenticationProcessingFilter#doFilter

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
		throws IOException, ServletException {
	if (!requiresAuthentication(request, response)) {
		chain.doFilter(request, response);
		return;
	}
	try {
		Authentication authenticationResult = attemptAuthentication(request, response);
		if (authenticationResult == null) {
			// return immediately as subclass has indicated that it hasn't completed
			return;
		}
		this.sessionStrategy.onAuthentication(authenticationResult, request, response);
		// Authentication success
		if (this.continueChainBeforeSuccessfulAuthentication) {
			chain.doFilter(request, response);
		}
		successfulAuthentication(request, response, chain, authenticationResult);
	}
	catch (InternalAuthenticationServiceException failed) {
		this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
		unsuccessfulAuthentication(request, response, failed);
	}
	catch (AuthenticationException ex) {
		// Authentication failed
		unsuccessfulAuthentication(request, response, ex);
	}
}

1、requiresAuthentication判断是否需要处理请求(默认是处理请求路径是/login且是POST方式访问的请求),如果不需要处理则放行。
2、attemptAuthentication去身份认证,判断continueChainBeforeSuccessfulAuthentication,如果为true就放行不在处理。默认是false。
3、successfulAuthentication处理认证成功后的操作,即设置SecurityContextHolder并保存SecurityContext,调用rememberMeServices.loginSuccess处理rememberMe认证成功后的操作(默认不处理),调用successHandler.onAuthenticationSuccess处理认证成功后的操作(默认是SavedRequestAwareAuthenticationSuccessHandler,重定向要访问的地址)
4、unsuccessfulAuthentication处理登录失败。即清空SecurityContextHolder,调用rememberMeServices.loginFail(默认无操作),failureHandler.onAuthenticationFailure(将异常设置到Session,重定向到/login?error)

 
 
UsernamePasswordAuthenticationFilter#attemptAuthentication

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.trim() : "";
	String password = obtainPassword(request);
	password = (password != null) ? password : "";
	UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
			password);
	// Allow subclasses to set the "details" property
	setDetails(request, authRequest);
	return this.getAuthenticationManager().authenticate(authRequest);
}

1、obtainUsername获取用户名,是request.getParameter(this.usernameParameter)从请求中获取,usernameParameter默认是username
2、obtainPassword获取密码,是request.getParameter(this.passwordParameter)从请求中获取,passwordParameter默认是password
3、将用户名和密码组装成UsernamePasswordAuthenticationToken
4、调用setDetails设置RemoteAddr和SessionId
5、getAuthenticationManager().authenticate进行身份认证。getAuthenticationManager()默认是ProviderManager。

 
 

ProviderManager#authenticate

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();
	for (AuthenticationProvider provider : getProviders()) {
		if (!provider.supports(toTest)) {
			continue;
		}
		if (logger.isTraceEnabled()) {
			logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
					provider.getClass().getSimpleName(), ++currentPosition, size));
		}
		try {
			result = provider.authenticate(authentication);
			if (result != null) {
				copyDetails(authentication, result);
				break;
			}
		}
		catch (AccountStatusException | InternalAuthenticationServiceException ex) {
			prepareException(ex, authentication);
			// SEC-546: Avoid polling additional providers if auth failure is due to
			// invalid account status
			throw ex;
		}
		catch (AuthenticationException ex) {
			lastException = ex;
		}
	}
	if (result == null && this.parent != null) {
		// Allow the parent to try.
		try {
			parentResult = this.parent.authenticate(authentication);
			result = parentResult;
		}
		catch (ProviderNotFoundException ex) {
			// ignore as we will throw below if no other exception occurred prior to
			// calling parent and the parent
			// may throw ProviderNotFound even though a provider in the child already
			// handled the request
		}
		catch (AuthenticationException ex) {
			parentException = ex;
			lastException = ex;
		}
	}
	if (result != null) {
		if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
			// Authentication is complete. Remove credentials and other secret data
			// from authentication
			((CredentialsContainer) result).eraseCredentials();
		}
		// If the parent AuthenticationManager was attempted and successful then it
		// will publish an AuthenticationSuccessEvent
		// This check prevents a duplicate AuthenticationSuccessEvent if the parent
		// AuthenticationManager already published it
		if (parentResult == null) {
			this.eventPublisher.publishAuthenticationSuccess(result);
		}

		return result;
	}

	// Parent was null, or didn't authenticate (or throw an exception).
	if (lastException == null) {
		lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
				new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
	}
	// If the parent AuthenticationManager was attempted and failed then it will
	// publish an AbstractAuthenticationFailureEvent
	// This check prevents a duplicate AbstractAuthenticationFailureEvent if the
	// parent AuthenticationManager already published it
	if (parentException == null) {
		prepareException(lastException, authentication);
	}
	throw lastException;
}

1、遍历providers(默认只有AnonymousAuthenticationProvider),调用provider.supports判断AuthenticationProvider是否支持处理Authentication,不支持进行下一轮。否则调用provider.authenticate身份认证。如果结果不为空调用copyDetails复制RemoteAddr和SessionId。如果出现AccountStatusException或InternalAuthenticationServiceException则调用prepareException发布事件并抛出异常。
2、如果鉴权结果为空且父AuthenticationManager存在则调用父parent.authenticate(providers默认只有DaoAuthenticationProvider,实际也是DaoAuthenticationProvider进行鉴权的)进行身份认证
3、认证成功完成后调用eraseCredentials擦除密码
4、发布AuthenticationSuccessEvent事件

 
 

DaoAuthenticationProvider父类AbstractUserDetailsAuthenticationProvider#authenticate

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;
	UserDetails user = this.userCache.getUserFromCache(username);
	if (user == null) {
		cacheWasUsed = false;
		try {
			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);
}

1、retrieveUser获取UserDetails,可以看到这里将UsernameNotFoundException异常转成了BadCredentialsException
2、preAuthenticationChecks.check检查账户是否锁定,是否启用,是否过期
3、additionalAuthenticationChecks进行密码对比
4、postAuthenticationChecks.check检查密码是否过期
5、forcePrincipalAsString表示是否将principalToReturn用户对象(UserDetails)转成用户名,默认是false

 
 
DaoAuthenticationProvider#retrieveUser

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);
	}
}

调用UserDetailsService的loadUserByUsername通过用户名加载用户。如果想通过数据库查询用户,可以重写UserDetailsService接口并注入到Spring中。

 
 

preAuthenticationChecks.check

private class DefaultPreAuthenticationChecks implements UserDetailsChecker {

	@Override
	public void check(UserDetails user) {
		if (!user.isAccountNonLocked()) {
			AbstractUserDetailsAuthenticationProvider.this.logger
					.debug("Failed to authenticate since user account is locked");
			throw new LockedException(AbstractUserDetailsAuthenticationProvider.this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
		}
		if (!user.isEnabled()) {
			AbstractUserDetailsAuthenticationProvider.this.logger
					.debug("Failed to authenticate since user account is disabled");
			throw new DisabledException(AbstractUserDetailsAuthenticationProvider.this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
		}
		if (!user.isAccountNonExpired()) {
			AbstractUserDetailsAuthenticationProvider.this.logger
					.debug("Failed to authenticate since user account has expired");
			throw new AccountExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
		}
	}

}

检查账户是否锁定,是否启用,是否过期

 
 

postAuthenticationChecks.check

private class DefaultPostAuthenticationChecks implements UserDetailsChecker {

	@Override
	public void check(UserDetails user) {
		if (!user.isCredentialsNonExpired()) {
			AbstractUserDetailsAuthenticationProvider.this.logger
					.debug("Failed to authenticate since user account credentials have expired");
			throw new CredentialsExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.credentialsExpired",
							"User credentials have expired"));
		}
	}

}

检查密码是否过期

 
 

DaoAuthenticationProvider#additionalAuthenticationChecks

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"));
	}
}

判断密码是否为空和调用passwordEncoder.matches进行密码对比

标签:request,SpringSecurity,UsernamePasswordAuthenticationFilter,authentication,ex,过滤
From: https://www.cnblogs.com/shigongp/p/17368725.html

相关文章

  • SpringSecurity过滤器之LogoutFilter
    LogoutFilter用于注销登录。privatevoiddoFilter(HttpServletRequestrequest,HttpServletResponseresponse,FilterChainchain) throwsIOException,ServletException{ if(requiresLogout(request,response)){ Authenticationauth=SecurityContextHolder.getC......
  • SpringSecurity过滤器之DefaultLoginPageGeneratingFilter
    DefaultLoginPageGeneratingFilter用于生成默认登录页。privatevoiddoFilter(HttpServletRequestrequest,HttpServletResponseresponse,FilterChainchain) throwsIOException,ServletException{ booleanloginError=isErrorPage(request); booleanlogoutSuccess......
  • SpringSecurity过滤器之SecurityContextHolderAwareRequestFilter,RequestCacheAwareFi
    SecurityContextHolderAwareRequestFilterSecurityContextHolderAwareRequestFilter对Servelet3.0的api做了封装。publicvoiddoFilter(ServletRequestreq,ServletResponseres,FilterChainchain) throwsIOException,ServletException{ chain.doFilter(this.requestF......
  • 哈希表与布隆过滤器
    一、哈希的整体思想最简单的哈希表其实就是数组,从数组中取出一个数的时间复杂度是O(1)的。但是数组下标类型是整型的,万一我的下标类型不是整型了该怎么办呢?比如说字符串型,典型的就是我想查找某个单词存不存在。还有些更复杂的数据类型,比如自定义的类型。那么问题就来了,如何满足任......
  • springSecurity过滤器之AnonymousAuthenticationFilter
    SpringSecurity提供了匿名登录功能,让我们不登录也能访问。比如/anoy路径及子路径都能匿名访问,配置如下:@ConfigurationpublicclassMySecurityConfigextendsWebSecurityConfigurerAdapter{@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException......
  • SpringSecurity过滤器之SecurityContextPersistenceFilter
    SecurityContextPersistenceFilter在请求之前从配置的SecurityContextRepository获得的信息填充SecurityContextHolder,并在请求完成并清除上下文holder后将其存储回存储库。默认情况下,它使用HttpSessionSecurityContextRepository。privatevoiddoFilter(HttpServletRequestreq......
  • SpringSecurity过滤器之SessionManagementFilter
    SessionManagementFilter检测用户自请求开始以来是否已通过身份验证,如果已通过,则调用SessionAuthenticationStrategy以执行任何与会话相关的活动,例如激活会话固定保护机制或检查多个并发登录。配置如下:@ConfigurationpublicclassMySecurityConfigextendsWebSecurityConfigur......
  • 接口过期过滤器
    注解类importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;/***过期接口通知*/@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD,......
  • 接口重复调用限制过滤器
    注解类importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;/***重复请求过滤器*/@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD......
  • SpringBoot SpringSecurity 介绍(基于内存的验证)
    SpringBoot集成SpringSecurity+MySQL+JWT附源码,废话不多直接盘SpringBoot已经为用户采用默认配置,只需要引入pom依赖就能快速启动SpringSecurity。目的:验证请求用户的身份,提供安全访问优势:基于Spring,配置方便,减少大量代码内置访问控制方法permitAll()表示所匹配的......