Spring Security基本框架之认证和授权

  • Post author:
  • Post category:其他


本文内容来自王松老师的《深入浅出Spring Security》,自己在学习的时候为了加深理解顺手抄录的,有时候还会写一些自己的想法。

在具体学习Spring Security各种方法用法之前,我们先介绍下Spring Security中常见的概念,以及认证、授权的思路,方便读者整体把握Spring Security架构,这里涉及的所有组件在后面的章节中还会详细介绍。


认证



Spring Security在架构设计中认证(Authentication)和授权(Authorization)是分开的后面我们会学习到。无论采用什么样的认证方式都不会影响到授权,这是两个独立的存在。这种独立带来的好处之一就是Spring Security可以非常方便的整合一些外部的认证方案。

在Spring Security中,用户的认证信息主要由Authentication的实现类来保存的。Authentication接口定义如下:

public interface Authentication extends Principal, Serializable {

	Collection<? extends GrantedAuthority> getAuthorities(); 

	Object getCredentials();

	Object getDetails();

	Object getPrincipal();

	boolean isAuthenticated();

	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
  • getAuthorities()方法:用来获取用户权限
  • getCredentials()方法:用来获取用户凭证,一般来说是用户的密码
  • getDetails()方法:用来获取用户携带的详细信息,可能是当前请求之类等
  • getPrincipal()方法:用来获取当前用户,例如一个用户名或者一个用户对象
  • isAuthenticated()方法:当前用户是否认证成功

当用户使用用户名和密码登录或者采用Remember-me登录时,都会对应一个不同的Authentication对象实例。

Spring Security中的认证工作主要由AuthenticationManager接口来负责,我们看看该接口的定义:

public interface AuthenticationManager {

	Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

AuthenticationManager就只有一个认证方法,返回一个认证对象Authentication。

AuthenticationManager的主要实现类是ProviderManager。ProviderManager管理了很多的AuthenticationProvider实例,AuthenticationProvider有点类似AuthenticationManager,但是它多了一个supports方法来判断是否支持给定的Authentication类型。由于Authentication拥有众多不同的实现类型,这些不同的实现类又由不同的AuthenticationProvider来处理,所以这里需要一个supports是方法来判断当前的AuthenticationProvider是否支持对应的Authentication。再一次完成认证流程中,可能会同时存在多个AuthenticationProvider,例如项目中同时支持表单登录和短信验证码登录。多个AuthenticationProvider统一由ProviderManager来管理。同时,ProviderManager具有一个可选的parent,如果所有的AuthenticationProvider都认证失败的话就会调用parent进行认证。parent相当于一个备用的认证方式。

public interface AuthenticationProvider {

	Authentication authenticate(Authentication authentication) throws AuthenticationException;

	boolean supports(Class<?> authentication);
}

我们来看看ProviderManager的认证方法:authenticate()

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {

	@Override
	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();
        //这里会遍历所有的AuthenticationProvider判断当前的Authentication是否支持
		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 {
                //如果支持的话,就调用AuthenticationProvider的认证方法
				result = provider.authenticate(authentication);
				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException ex) {
				prepareException(ex, authentication);
				throw ex;
			}
			catch (AuthenticationException ex) {
				lastException = ex;
			}
		}
		if (result == null && this.parent != null) {
			try {
                //没有AuthenticationProvider处理的话就就调用ProviderManager在构造函数中传进来的一个名字叫parent的AuthenticationProvider来认证
				parentResult = this.parent.authenticate(authentication);
				result = parentResult;
			}
			catch (ProviderNotFoundException ex) {
	
			}
			catch (AuthenticationException ex) {
				parentException = ex;
				lastException = ex;
			}
		}
		if (result != null) {
			if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
				((CredentialsContainer) result).eraseCredentials();
			}

			if (parentResult == null) {
				this.eventPublisher.publishAuthenticationSuccess(result);
			}
			return result;
		}
		if (lastException == null) {
			lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
					new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
		}
		if (parentException == null) {
			prepareException(lastException, authentication);
		}
		throw lastException;
	}
}

授权

当人成认证之后接下来就是授权了。在Spring Security的授权体系中有两个关键的接口:

  • AccessDecisionMamager
  • AccessDecisionVoter

AccessDecisionVoter是一个投票器,投票器会检查用户是否具备应 有的角色,进而投出赞成、反对或者弃权票;AccessDecisionManager则 是一个决策器,来决定此次访问是否被允许。AccessDecisionVoter和 AccessDecisionManager都有众多的实现类,在


AccessDecisionManager


中会挨个遍历AccessDecisionVoter


,进而决定是否允许用户访问,因而 AccessDecisionVoter和


AccessDecisionManager


两者的关系类似于 AuthenticationProvider和


ProviderManager的关系。

在Spring Security中,用户请求一个资源(通常是一个网络接口或 者一个Java


方法)所需要的角色会被封装成一个


ConfigAttribute


对象,在 ConfigAttribute中只有一个


getAttribute


方法,该方法返回一个


String


字符 串,就是角色的名称。一般来说,角色名称都带有一个ROLE_


前缀,投票器AccessDecisionVoter


所做的事情,其实就是比较用户所具备的角色 和请求某个资源所需的ConfigAttribute之间的关系。

小结

看完的小伙伴可以在脑海里一边想一遍动手画一下Authentication、AuthenticationManager、AuthenticationProvider、ProviderManager、AccessDecisionMamager、AccessDecisionVoter它们之间的关系。



版权声明:本文为qq_27062249原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。