认证和授权学习5:spring security认证原理分析

认证和授权学习5:spring security认证原理分析

一、结构总览

spring security通过Filter技术实现对web资源的保护。

当初始化spring security时会创建一个名为SpringSecurityFilterChain 的servlet,类型是

org.springframework.security.web.FilterChainProxy ,它实现了Filter接口,所以所有的请求都会经过它。

FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中包含的各个过滤器,这些过滤器被spring容器管理,spring security通过这些过滤器实现认证和授权。但这些过滤器不直接处理认证和授权,而是调用认证管理器和决策管理器来实现。

总结一下就是spring security功能的实现是通过一些过滤器和认证管理器跟决策管理器相互配合实现的。

二、几个重点的过滤器介绍

2.1 SecurityContextPersistenceFilter

这个Filter是整个拦截过程的入口和出口

2.2 UsernamePasswordAuthenticationFilter

用于处理来自表单提交的认证,其内部还有登录成功和登录失败的处理器,AuthenticationSuccessHandler ,

AuthenticationFailureHandler

2.3 FilterSecurityInterceptor

用来进行授权的过滤器,通过AccessDecisionManager 对登录用户进行授权访问

当用户访问一个系统资源时,会进入这个过滤器,在这个过滤器中用决策管理器判断当前用户是否可以访问此资源。

2.4 ExceptionTranslationFilter

用来处理认证和授权过程中抛出的异常,能够捕获来自过滤器链的所有异常,但它只会处理AuthenticationException和AccessDeniedException这两类,其余的会继续向上抛出。

三、表单登录认证过程分析

(1) 用户提交用户名和密码被UsernamePasswordAuthenticationFilter拦截到,将用户信息封装为接口Authentication 对象,实际是UsernamePasswordAuthenticationToken这个实现类的对象

(2) 将Authentication 对象提交到认证管理器AuthenticationManager 进行认证

(3) 认证成功后AuthenticationManager 会返回一个填充了用户信息的Authentication对象。

(4) SecurityContextHolder 将设置了用户信息的Authentication对象设置到其内部。

3.1Authentication 接口源码分析

下面是封装用户信息的Authentication 接口的结构,里边维护了当前用户的身份信息

public interface Authentication extends Principal, Serializable {
	//权限列表
	Collection<? extends GrantedAuthority> getAuthorities();
	//登录凭据,比如密码,登录成功后会被移除
	Object getCredentials();
    //登录的细节信息,通常是WebAuthenticationDetails
    //记录登录者的ip和sessionId等值
	Object getDetails();

	//身份信息,UserDetails接口的实现类对象
	Object getPrincipal();
	boolean isAuthenticated();
	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

3.2 UsernamePasswordAuthenticationFilter源码分析

UsernamePasswordAuthenticationFilter这个过滤器用来处理表单登录请求,其继承自抽象类

AbstractAuthenticationProcessingFilter并实现了父类的抽象方法attemptAuthentication()登录请求过来后,实际是先走了这个filter的doFilter方法,

AbstractAuthenticationProcessingFilter的部分源码

public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
    
    ...//省略
    
    //过滤器的doFilter方法
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {

		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		if (!requiresAuthentication(request, response)) {
			chain.doFilter(request, response);

			return;
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Request is to process authentication");
		}

		Authentication authResult;

		try {
		    //这里调用子类 UsernamePasswordAuthenticationFilter 的认证方法
			authResult = attemptAuthentication(request, response);
			if (authResult == null) {
				// return immediately as subclass has indicated that it hasn't completed
				// authentication
				return;
			}
			sessionStrategy.onAuthentication(authResult, request, response);
		}
		catch (InternalAuthenticationServiceException failed) {
			logger.error(
					"An internal error occurred while trying to authenticate the user.",
					failed);
			unsuccessfulAuthentication(request, response, failed);

			return;
		}
		catch (AuthenticationException failed) {
			// Authentication failed
			unsuccessfulAuthentication(request, response, failed);

			return;
		}

		// Authentication success
		if (continueChainBeforeSuccessfulAuthentication) {
			chain.doFilter(request, response);
		}
         //认证成功后的处理逻辑
		successfulAuthentication(request, response, chain, authResult);
	}
}

AbstractAuthenticationProcessingFilter 的doFilter方法中调用了子类的 attemptAuthentication方法进行认证

在这个方法中获取当前登录的用户信息,封装成Authentication 接口的实现类

UsernamePasswordAuthenticationToken的对象,然后把这个对象提交给认证管理器进行认证,这里说的提交实际上就是调用认证管理器的认证方法。

public class UsernamePasswordAuthenticationFilter extends
    AbstractAuthenticationProcessingFilter {
    //这是开始认证的方法
    public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
		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();

		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);

		setDetails(request, authRequest);
        //提交认证管理器(实际就是调用认证管理器的认证方法)
		return this.getAuthenticationManager().authenticate(authRequest);
	}
}

其中 this.getAuthenticationManager().authenticate(authRequest)这句是调用认证管理器的认证方法,如果认证成功会返回一个填充了用户的权限等信息的Authentication 对象,但其中的用户密码会被清空

3.3 AuthenticationManager 分析

上面讲到UsernamePasswordAuthenticationFilter中会调用认证管理器AuthenticationManager的方法进行认证。AuthenticationManager是一个接口,其中只有一个方法,

Authentication authenticate(Authentication authentication) throws AuthenticationException;

springsecurity 提供了一个实现类ProviderManager,所以进行认证时实际调用的是这个类中的方法

这个类的名字可以看出它的功能,Provider管理,其实真正的认证逻辑是由各种AuthenticationProvider完成的,

这也是一个接口,不同的实现类对应不同的认证方式

public interface AuthenticationProvider {
    Authentication authenticate(Authentication authentication)
			throws AuthenticationException;
    boolean supports(Class<?> authentication);
}

其中的support方法用来判断当前传递过来的这个Authentication对象是不是自己该验证的,authenticate方法是具体的验证方法。

ProviderManager中维护着一个AuthenticationProvider的集合

private List<AuthenticationProvider> providers = Collections.emptyList();

UsernamePasswordAuthenticationFilter中调用的authenticate方法,实际就是ProviderManager中的方法

3.4 ProviderManager源码分析

ProviderManager类会调用具体的provider进行认证。外部调用的是其中的authenticate方法

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
    
    ...//省略变量定义
    public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
        //获取当前被认证的Authentication的class
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		AuthenticationException parentException = null;
		Authentication result = null;
		Authentication parentResult = null;
		boolean debug = logger.isDebugEnabled();
        //循环其中维护的provider列表,看那个可以认证当前的authentication对象
		for (AuthenticationProvider provider : getProviders()) {
		    //调用provider.supports()方法判断这个provider能否处理
            if (!provider.supports(toTest)) {
				//不能处理循环下一个
                continue;
			}

			if (debug) {
				logger.debug("Authentication attempt using "
						+ provider.getClass().getName());
			}

			try {
                //可以处理的话就调用具体provider的authenticate方法,
                //如果是表单登录,对应的是  DaoAuthenticationProvider
				result = provider.authenticate(authentication);

				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException e) {
				prepareException(e, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw e;
			}
			catch (InternalAuthenticationServiceException e) {
				prepareException(e, authentication);
				throw e;
			}
			catch (AuthenticationException e) {
				lastException = e;
			}
		}
        //只有没有进行认证 result才是null,认证失败会抛出异常,认证成功result返回authenticaion的对象
		if (result == null && parent != null) {
			// Allow the parent to try.
			try {
                 //如果循环完了provider列表还没有进行认证,就调用parent的方法,这个parent是构造方法传
                 //进来的
				result = parentResult = parent.authenticate(authentication);
			}
			catch (ProviderNotFoundException e) {
				// 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 e) {
				lastException = parentException = e;
			}
		}

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

		// Parent was null, or didn't authenticate (or throw an exception).

		if (lastException == null) {
			lastException = new ProviderNotFoundException(messages.getMessage(
					"ProviderManager.providerNotFound",
					new Object[] { toTest.getName() },
					"No AuthenticationProvider found for {0}"));
		}

		// If the parent AuthenticationManager was attempted and failed than 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;
	}
}

针对表单登录,最后进行认证的是 DaoAuthenticationProvider,这个类继承自AbstractUserDetailsAuthenticationProvider,其中authenticate方法在父类中,通过用户名查找用户的逻辑定义在父类中,是一个抽象方法protected abstract UserDetails retrieveUser(...),子类实现这个方法,调用

userDetailsService去数据库查询用户对象,然后和过滤器传过来的authentication对象进行比较。

三、总结

整个认证过程:

登录请求先走到AbstractAuthenticationProcessingFilter 的doFilter方法,在这个方法中调用了子类

UsernamePasswordAuthenticationFilterattemptAuthentication()方法,然后在这个方法中封装用户信息为

UsernamePasswordAuthenticationToken对象,调用认证管理器AuthenticationManager.authenticate()方法进行校验,校验成功后再回到AbstractAuthenticationProcessingFilter的doFilter方法中,执行登录成功后的处理逻辑,登录成功后会把当前用户填充到 SecurityContextHolder

原文地址:https://www.cnblogs.com/chengxuxiaoyuan/p/14018596.html