SpringSecurity身份认证原理解析

 更新时间:2023年09月18日 09:17:27   作者:还没秃的小菜鸡  
这篇文章主要介绍了SpringSecurity身份认证原理解析,身份认证时用户名和密码被过滤器获取到,封装成 Authentication ,通常情况下是 UsernamePasswordAuthenticationToken 这个实现类,需要的朋友可以参考下

Spring Security身份认证

  1. 用户名和密码被过滤器获取到,封装成 Authentication ,通常情况下是 UsernamePasswordAuthenticationToken 这个实现类。
  2. AuthenticationManager 身份管理器负责验证这个 Authentication
  3. 认证成功后, AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的 权限信息,身份信息,细节信息,但密码通常会被移除) Authentication 实例。
  4. SecurityContextHolder 安全上下文容器将第3步填充了信息的 Authentication ,通过 SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。
public class AuthenticationExample {
    private static AuthenticationManager am = new SampleAuthenticationManager();
    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        while (true) {
            System.out.println("please enter your username:");
            String name = in.readLine();
            System.out.println("please enter your password:");
            String password = null;
            password = in.readLine();
            try {
                // 封装认证信息,未认证通过
                UsernamePasswordAuthenticationToken request = new UsernamePasswordAuthenticationToken(name, password);
                // 认证逻辑
                Authentication result = am.authenticate(request);
                //当前线程绑定认证信息
                SecurityContextHolder.getContext().setAuthentication(result);
                break;
            } catch (AuthenticationException e) {
                System.out.println("Authentication failed: " + e.getMessage());
            }
        }
    }
    static class SampleAuthenticationManager implements AuthenticationManager {
        static final List<GrantedAuthority> AUTHORITIES = new ArrayList<GrantedAuthority>();
        static {
            AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));
        }
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            // 判断条件,用户名和密码是否相同
            if (authentication.getName().equals(authentication.getCredentials())){
                return new UsernamePasswordAuthenticationToken(authentication.getName(),authentication.getCredentials(),AUTHORITIES);
            }
            throw new BadCredentialsException("Bad Credentials");
        }
    }
}

测试:

在这里插入图片描述

认证流程

在这里插入图片描述

SecurityFilterChain 过滤器链

Spring Security采用的是filterChain的设计方式,主要的功能大都由过滤器实现,在启动项目的时候,可以在日志中看到已有的过滤器,可在类似下面的日志里找到 DefaultSecurityFilterChain ,这里面则是SecurityFilterChain

2021-01-07 11:27:30.410  INFO 13880 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@153cd6bb, org.springframework.security.web.context.SecurityContextPersistenceFilter@71f0b72e, org.springframework.security.web.header.HeaderWriterFilter@aa149ed, org.springframework.security.web.authentication.logout.LogoutFilter@2de50ee4, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@151ef57f, org.springframework.security.web.session.ConcurrentSessionFilter@5c73f672, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@2f508f3c, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@5eed2d86, org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter@36fc05ff, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@61d84e08, org.springframework.security.web.session.SessionManagementFilter@31ff6309, org.springframework.security.web.access.ExceptionTranslationFilter@10fbbdb, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@4e1459ea]

把各个过滤器抽取出来,我们可以看到是这样,这也是过滤器链的先后顺序。

  1. WebAsyncManagerIntegrationFilter
  2. SecurityContextPersistenceFilter
  3. HeaderWriterFilter
  4. LogoutFilter
  5. UsernamePasswordAuthenticationFilter
  6. JwtAuthorizationTokenFilter
  7. RequestCacheAwareFilter
  8. SecurityContextHolderAwareRequestFilter
  9. SessionManagementFilter
  10. ExceptionTranslationFilter
  11. FilterSecurityInterceptor

介绍几个主要的作用

  • SecurityContextPersistenceFilter
  • Filter的入口和出口,它是用来将SecurityContext(认证的上下文,里面有登录成功后的认证授权信息)对象持久到Session的Filter,同时会把SecurityContext设置给SecurityContextHolder方便我们获取用户认证授权信息
  • UsernamePasswordAuthenticationFilter
  • 默认拦截“/login”登录请求,处理表单提交的登录认证,将请求中的认证信息包括username,password等封装成UsernamePasswordAuthenticationToken,然后调用AuthenticationManager的认证方法进行认证
  • BasicAuthenticationFilter
  • 基本认证,支持httpBasic认证方式的Filter
  • RememberAuthenticationFilter
  • 记住我功能实现的Filter
  • AnonymousAuthenticationFilter
  • 匿名Filter,用来处理匿名访问的资源,如果用户未登录,SecurityContext中没有Authentication,就会创建匿名的Token(AnonymousAuthenticationToken),然后通过SecurityContextHodler设置到SecurityContext中。
  • ExceptionTranslationFilter
  • 用来捕获FilterChain所有的异常,进行处理,但是只会处理 AuthenticationException和AccessDeniedException,异常,其他的异常 会继续抛出。
  • FilterSecurityInterceptor

用来做授权的Filter,通过父类(AbstractSecurityInterceptor.beforeInvocation)调用AccessDecisionManager.decide方法对用户进行授权。

UsernamePasswordAuthenticationFilter

UsernamePasswordAuthenticationFilter ,顾名思义,是用来处理用户名密码登录的过滤器。所有的Filter核心方法都是 doFilter ,该过滤器的doFilter在其父抽象类中,过滤器只需实现 attemptAuthentication 方法即可。

public class UsernamePasswordAuthenticationFilter extends
		AbstractAuthenticationProcessingFilter {
	// ~ Static fields/initializers
	// =====================================================================================
	//从登录请求中获取参数:username,password的名字
	public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
	public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
	private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
	private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
	//默认支持POST登录
	private boolean postOnly = true;
	//默认拦截/login请求,Post方式
	public UsernamePasswordAuthenticationFilter() {
		super(new AntPathRequestMatcher("/login", "POST"));
	}
	// ~ Methods
	// ========================================================================================================
	public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
			//判断请求是否是POST
		if (postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException(
					"Authentication method not supported: " + request.getMethod());
		}
		//获取到用户名和密码
		String username = obtainUsername(request);
		String password = obtainPassword(request);
		if (username == null) {
			username = "";
		}
		if (password == null) {
			password = "";
		}
		username = username.trim();
		//用户名和密码封装Token
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);
		//设置details属性
		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);
		//调用AuthenticationManager().authenticate进行认证,参数就是Token对象
		return this.getAuthenticationManager().authenticate(authRequest);
	}

AuthenticationManager

请求通过UsernamePasswordAuthenticationFilter调用AuthenticationManager,默认走的实现类是ProviderManager,它会找到能支持当前认证的AuthenticationProvider实现类调用器authenticate方法执行认证,认证成功后会清除密码,然后抛出AuthenticationSuccessEvent事件

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
		InitializingBean {
		...省略...
		//这里authentication 是封装了登录请求的认证参数,
		//即:UsernamePasswordAuthenticationFilter传入的Token对象
	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;
		boolean debug = logger.isDebugEnabled();
		//找到所有的AuthenticationProvider ,选择合适的进行认证
		for (AuthenticationProvider provider : getProviders()) {
			//是否支持当前认证
			if (!provider.supports(toTest)) {
				continue;
			}
```java
		if (debug) {
			logger.debug("Authentication attempt using "
					+ provider.getClass().getName());
		}
		try {
			//调用provider执行认证
			result = provider.authenticate(authentication);
			if (result != null) {
				copyDetails(authentication, result);
				break;
			}
		}
			...省略...
	}
	...省略...
	//result就是Authentication ,使用的实现类依然是UsernamepasswordAuthenticationToken,
	//封装了认证成功后的用户的认证信息和授权信息
	if (result != null) {
		if (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 than it will publish an AuthenticationSuccessEvent
	// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
	if (parentResult == null) {
		//发布事件
		eventPublisher.publishAuthenticationSuccess(result);
	}
	return result;
}

DaoAuthenticationProvider

请求到达AuthenticationProvider,默认实现是DaoAuthenticationProvider,它的作用是根据传入的Token中的username调用UserDetailService加载数据库中的认证授权信息(UserDetails),然后使用PasswordEncoder对比用户登录密码是否正确

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
		//密码编码器
		private PasswordEncoder passwordEncoder;
		//UserDetailsService ,根据用户名加载UserDetails对象,从数据库加载的认证授权信息
		private UserDetailsService userDetailsService;
		//认证检查方法
		protected void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		if (authentication.getCredentials() == null) {
			logger.debug("Authentication failed: no credentials provided");
```java
		throw new BadCredentialsException(messages.getMessage(
				"AbstractUserDetailsAuthenticationProvider.badCredentials",
				"Bad credentials"));
	}
	//获取密码
	String presentedPassword = authentication.getCredentials().toString();
	//通过passwordEncoder比较密码,presentedPassword是用户传入的密码,userDetails.getPassword()是从数据库加载到的密码
	//passwordEncoder编码器不一样比较密码的方式也不一样
	if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
		logger.debug("Authentication failed: password does not match stored value");
		throw new BadCredentialsException(messages.getMessage(
				"AbstractUserDetailsAuthenticationProvider.badCredentials",
				"Bad credentials"));
	}
}
//检索用户,参数为用户名和Token对象
protected final UserDetails retrieveUser(String username,
		UsernamePasswordAuthenticationToken authentication)
		throws AuthenticationException {
	prepareTimingAttackProtection();
	try {
		//调用UserDetailsService的loadUserByUsername方法,
		//根据用户名检索数据库中的用户,封装成UserDetails 
		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);
	}
}
//创建认证成功的认证对象Authentication,使用的实现是UsernamepasswordAuthenticationToken,
//封装了认证成功后的认证信息和授权信息,以及账户的状态等
@Override
protected Authentication createSuccessAuthentication(Object principal,
		Authentication authentication, UserDetails user) {
	boolean upgradeEncoding = this.userDetailsPasswordService != null
			&& this.passwordEncoder.upgradeEncoding(user.getPassword());
	if (upgradeEncoding) {
		String presentedPassword = authentication.getCredentials().toString();
		String newPassword = this.passwordEncoder.encode(presentedPassword);
		user = this.userDetailsPasswordService.updatePassword(user, newPassword);
	}
	return super.createSuccessAuthentication(principal, authentication, user);
}
...省略...

这里提供了三个方法

  • additionalAuthenticationChecks:通过passwordEncoder比对密码
  • retrieveUser:根据用户名调用UserDetailsService加载用户认证授权信息
  • createSuccessAuthentication:登录成功,创建认证对象Authentication

然而你发现 DaoAuthenticationProvider 中并没有authenticate认证方法,真正的认证逻辑是通过父类AbstractUserDetailsAuthenticationProvider.authenticate方法完成的

AbstractUserDetailsAuthenticationProvider

public abstract class AbstractUserDetailsAuthenticationProvider implements
		AuthenticationProvider, InitializingBean, MessageSourceAware {
		//认证逻辑
		public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
			//得到传入的用户名
			String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
				: authentication.getName();
				//从缓存中得到UserDetails
			boolean cacheWasUsed = true;
			UserDetails user = this.userCache.getUserFromCache(username);
			if (user == null) {
			cacheWasUsed = false;
```java
		try {
			//检索用户,底层会调用UserDetailsService加载数据库中的UserDetails对象,保护认证信息和授权信息
			user = retrieveUser(username,
					(UsernamePasswordAuthenticationToken) authentication);
		}
		catch (UsernameNotFoundException notFound) {
			...省略...
		}
		try {
			//前置检查,主要检查账户是否锁定,账户是否过期等
			preAuthenticationChecks.check(user);
			//比对密码在这个方法里面比对的
			additionalAuthenticationChecks(user,
				(UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException exception) {
		...省略...
		}
		//后置检查
		postAuthenticationChecks.check(user);
		if (!cacheWasUsed) {
			//设置UserDetails缓存
			this.userCache.putUserInCache(user);
		}
		Object principalToReturn = user;
		if (forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}
		//认证成功,创建Auhentication认证对象
		return createSuccessAuthentication(principalToReturn, authentication, user);
}

SecurityContextHolder

认证成功,请求会重新回到UsernamePasswordAuthenticationFilter,然后会通过其父类AbstractAuthenticationProcessingFilter.successfulAuthentication方法将认证对象封装成SecurityContext设置到SecurityContextHolder中
protected void successfulAuthentication(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain, Authentication authResult)
			throws IOException, ServletException {
```java
	if (logger.isDebugEnabled()) {
		logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
				+ authResult);
	}
	//认证成功,吧Authentication 设置到SecurityContextHolder
	SecurityContextHolder.getContext().setAuthentication(authResult);
	//处理记住我业务逻辑
	rememberMeServices.loginSuccess(request, response, authResult);
	// Fire event
	if (this.eventPublisher != null) {
		eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
				authResult, this.getClass()));
	}
	//重定向登录成功地址
	successHandler.onAuthenticationSuccess(request, response, authResult);
}

然后后续请求又会回到SecurityContextPersistenceFilter,它就可以从SecurityContextHolder获取到SecurityContext持久到SecurityContextRepository(默认实现是HttpSessionSecurityContextRepository基于Session存储)

到此这篇关于SpringSecurity身份认证原理解析的文章就介绍到这了,更多相关SpringSecurity身份认证内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • ThreadPoolExecutor参数含义及源码执行流程详解

    ThreadPoolExecutor参数含义及源码执行流程详解

    这篇文章主要为大家介绍了ThreadPoolExecutor参数含义及源码执行流程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • java使用poi读取ppt文件和poi读取excel、word示例

    java使用poi读取ppt文件和poi读取excel、word示例

    这篇文章主要介绍了java使用poi读取ppt文件和poi读取excel、word示例,需要的朋友可以参考下
    2014-03-03
  • 从概念到实践的深度解析Java 面向对象进阶之多态

    从概念到实践的深度解析Java 面向对象进阶之多态

    多态作为Java面向对象编程的重要特性,通过继承、方法重写和父类引用指向子类对象,实现了同一操作在不同对象上的多样化行为,本文给大家介绍从概念到实践的深度解析Java 面向对象进阶之多态,感兴趣的朋友一起看看吧
    2025-06-06
  • 通过Java实现RSA加密与验证的方法详解

    通过Java实现RSA加密与验证的方法详解

    RSA是一种非对称加密算法,是目前广泛应用于加密和数字签名领域的一种加密算法,本文主要讲述如何通过Java实现RSA加密与验证,应用场景为与其他平台对接接口时,通过RSA加密和解密验证请求的有效性,在对接时双方互换公钥,需要的朋友可以参考下
    2023-12-12
  • idea2020.2卡死在reading maven projects

    idea2020.2卡死在reading maven projects

    这篇文章主要介绍了idea2020.2卡死在reading maven projects,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • spring Security的自定义用户认证过程详解

    spring Security的自定义用户认证过程详解

    这篇文章主要介绍了spring Security的自定义用户认证过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • Java中实现Excel数字与文本转换的示例代码

    Java中实现Excel数字与文本转换的示例代码

    在数据处理过程中,经常会遇到需要将Excel单元格中的文本转换为数字,或将数字转换为文本的情况,下面就来详细的介绍一下,感兴趣的可以了解下
    2025-11-11
  • Java Vector实现班级信息管理系统

    Java Vector实现班级信息管理系统

    这篇文章主要为大家详细介绍了Java Vector实现班级信息管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • Mybatis单个参数的if判断报异常There is no getter for property named ''xxx'' in ''class java.lang.Integer''的解决方案

    Mybatis单个参数的if判断报异常There is no getter for property named ''x

    今天小编就为大家分享一篇关于Mybatis单个参数的if判断报异常There is no getter for property named 'xxx' in 'class java.lang.Integer'的解决方案,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12
  • MybatisGenerator文件生成不出对应文件的问题

    MybatisGenerator文件生成不出对应文件的问题

    本文介绍了使用MybatisGenerator生成文件时遇到的问题及解决方法,主要步骤包括检查目标表是否存在、是否能连接到数据库、配置生成器的路径等,通过在项目结构中引入相应的jar包,并在GeneratorSqlmap.java文件中运行,可以成功生成对应的文件
    2025-01-01

最新评论