SpringSecurity核心概念

174 阅读7分钟

Servlet安全

Filters

Spring Servlet基于过滤器链来保证网络安全的.


image.png
当客户端收到一个请求时,会创建一个过滤器链FilterChain,这其中包含了一系列的Filters和Servlet,所以不同URL会有不同过滤器.
这些过滤器主要:

  • Filter通常将编写HttpServletResponse, 防止下游过滤器或Servlet被调用。
  • 被下游的Servlet和FiltersHttpServletRequestHttpServletResponse
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // do something before the rest of the application
    chain.doFilter(request, response); // invoke the rest of the application
    // do something after the rest of the application
}

DelegatingFilterProxy

是一个特殊的Filter,通过此类将Servlet标准的Filter与Spring Application容器中的bean建立桥接关系.可以通过此类将注册到spring容器中的Filter找出来,执行其中的doFilter

image.png
DelegatingFilterProxy从Spring容器中找出来注册的Filter,然后调用此Filter中逻辑

// 伪代码
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // Lazily get Filter that was registered as a Spring Bean
    // For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0
    Filter delegate = getFilterBean(someBeanName,Filter.class);
    // delegate work to the Spring Bean
    delegate.doFilter(request, response);
}

FilterChainProxy

FilterChainProxy是Spring Security提供的特殊过滤器,允许通过SecurityFilterChain委派许多过滤器实例。 由于FilterChainProxy是Bean,因此通常将其包装在DelegatingFilterProxy中。

image.png

SecurityFilterChain

FilterChainProxy通过SecurityFilterChain决定当前Request应该经过哪些过滤器,他的实现类就是DefaultSecurityFilterChain


image.png

	private void doFilterInternal(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {

		// ...
		List<Filter> filters = getFilters(fwRequest);

		// ...
    	// 创建针对此子请求的过滤器链
		VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
		vfc.doFilter(fwRequest, fwResponse);
	}
	private List<Filter> getFilters(HttpServletRequest request) {
		for (SecurityFilterChain chain : filterChains) {
			if (chain.matches(request)) {
				return chain.getFilters();
			}
		}

		return null;
	}

Handling Security Exceptions

认证授权过程中的异常处理

image.png
首先,ExceptionTranslationFilter调用FilterChain.doFilter(request,response)来调用应用程序的其余部分。
如果程序执行过程中出现认证异常或认证失败(AuthenticationException)

  • SecurityContextHolder(环境上下文)会被清除
  • HttpServletRequest保存在RequestCache中。 用户成功进行身份验证后,将使用RequestCache重放原始请求。
  • AuthenticationEntryPoint用于从客户端请求凭据。 例如,它可能重定向到登录页面或发送WWW-Authenticate标头。

如果是AccessDeniedException, 则调用AccessDeniedHandler

认证

SecurityContextHolder

SpringSecurity存储认证信息的地方,认证成功后,将认证信息加入其中

SecurityContext context = SecurityContextHolder.createEmptyContext(); 
Authentication authentication =
    new TestingAuthenticationToken("username", "password", "ROLE_USER"); 
context.setAuthentication(authentication);

SecurityContextHolder.setContext(context); 

SpringSecurity默认使用ThreadLocal存储变量
image.png

Authentication

用于提供用户提供的用于身份验证的凭据。 在这种情况下使用时,isAuthenticated()返回false。代表当前经过身份验证的用户。 可以从SecurityContext获得当前的身份验证。

这玩意就是一般登录成功的时候将当前用户信息封装成Authentication,放入SecurityContextHolder中,以备以后使用

public interface Authentication extends Principal, Serializable {
	// 权限
	Collection<? extends GrantedAuthority> getAuthorities();
	// 密码
	Object getCredentials();
	// 其他相关信息,如IP
	Object getDetails();
	// 用户名
	Object getPrincipal();

	boolean isAuthenticated();

	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

Authentication接口的实现类,当然它还有很多的实现类,UsernamepasswordAuthenticationToken是它的最常用实现类之一
image.png

GrantedAuthentication

表示用户的权限信息

AuthenticationManager

AuthenticationManager是用于定义Spring Security的过滤器如何执行身份验证的API。 然后由调用AuthenticationManager的控制器(即Spring Security的Filters)在SecurityContextHolder上设置返回的身份验证。 如果没有与Spring Security的过滤器集成,则可以直接设置SecurityContextHolder,并且不需要使用AuthenticationManager。


虽然AuthenticationManager的实现可以是任何东西,但最常见的实现是ProviderManager。

public interface AuthenticationManager {
	Authentication authenticate(Authentication authentication)
			throws AuthenticationException;
}

ProviderManager

ProviderManager是最常用的AuthenticationManager实现类.

ProviderManager委托给AuthenticationProviders列表。 每个AuthenticationProvider都有机会指示认证应该成功,失败,或者表明它不能做出决定并允许下游AuthenticationProvider进行决定。 如果没有一个已配置的AuthenticationProviders可以进行身份验证,则身份验证将失败,并显示ProviderNotFoundException,这是一个特殊的AuthenticationException,它指示未配置ProviderManager支持传递给它的身份验证类型。

public class ProviderManager implements AuthenticationManager, MessageSourceAware,InitializingBean {
	public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
       // 遍历每个AuthenticationProvider
		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}
			try {
				result = provider.authenticate(authentication);
			}catch (AccountStatusException e) {
				//...
			}
		}
	}            
            
}           

image.png

AuthenticationProvider

真正做做认证的接口

public interface AuthenticationProvider {
	
	Authentication authenticate(Authentication authentication)
			throws AuthenticationException;

	boolean supports(Class<?> authentication);
}

### AuthenticationEntryPoint > AuthenticationEntryPoint用于从客户端请求登录凭据的HTTP响应

客户端将向未经授权访问的资源发出未经身份验证的请求。 在这种情况下,AuthenticationEntryPoint的实现用于从客户端请求凭据。 AuthenticationEntryPoint实现可能会执行重定向到登录页面,使用WWW-Authenticate标头进行响应等。

AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter用作基础过滤器,用于验证用户的凭据。 在对凭证进行身份验证之前,Spring Security通常使用AuthenticationEntryPoint请求凭证。


接下来,AbstractAuthenticationProcessingFilter可以对提交给它的任何身份验证请求进行身份验证。
image.png

  • 首先当一个request经过AbstractAuthenticationProcessingFilter时,比如他的实现类UsernamePasswordAuthenticationFilter时,会将username,password封装成UsernamePasswordAuthenticationToken(Authentication的实现类)
  • 获取AuthenticationManager,这里一般是ProviderManager,进行认证
  • 如果成功
    • SessionAuthenticationStrategy收到新的登录通知
    • SecurityContextHolder将保存登录信息,稍后SecurityContextPersistenceFilter将登录信息保存到Session中
    • RememberMeServices.loginSuccess被调用。 如果记住我未配置,则为空。
    • ApplicationEventPublisher将会发送InteractiveAuthenticationSuccessEvent事件
    • 调用AuthenticationSuccessHandler中登录成功逻辑
  • 如果失败
    • SecurityContextHolder将进行清除操作
    • AuthenticationFailureHandler将会调用

UsernamePasswordAuthentication

验证用户身份的最常见方法之一是验证用户名和密码。 这样,Spring Security为使用用户名和密码进行身份验证提供了全面的支持。


基于username/password的认证方式有

  • form Login
  • Basic Authentication
  • Digest Authentication

Form Login

image.png

  • 首先访问一个需要验证的请求
  • FilterSecurityInterceptor会拦截未认证的请求,并且抛出一个AccessDeniedException
  • 由于未对用户进行身份验证,因此ExceptionTranslationFilter会启动“开始身份验证”,并使用配置的AuthenticationEntryPoint将重定向发送到登录页面。 在大多数情况下,AuthenticationEntryPointLoginUrlAuthenticationEntryPoint的实例。
  • 然后,浏览器将请求将其重定向到的登录页面
  • 程序内部包含了登录页面,将登录页面渲染到前端

Basic Login

image.png

UserDetails

UserDetails通过UserDetailsService返回.DaoAuthenticationProvider.DaoAuthenticationProvider验证UserDetails,然后返回具有主体的Authentication,该主体是配置的UserDetailsService返回的UserDetails。

这是他的接口信息

public interface UserDetails extends Serializable {
	// ~ Methods
	// ========================================================================================================

	// 获取权限
	Collection<? extends GrantedAuthority> getAuthorities();

	// 获取密码
	String getPassword();

	// 获取用户名
	String getUsername();

	// 账号是否过期
	boolean isAccountNonExpired();

	// 是否锁定
	boolean isAccountNonLocked();

	// 密码是否过期
	boolean isCredentialsNonExpired();

	// 用户是否可用
	boolean isEnabled();
}

org.springframework.security.core.userdetails.User是其常用的实现类

UserDetailsService

DaoAuthenticationProvider使用UserDetailsService检索用户名,密码和其他用于使用用户名和密码进行身份验证的属性。 Spring Security提供UserDetailsService的内存中和JDBC实现.


### PasswordEncoder > 圈起来的为自适应密码哈希算法

image.png

授权

Authorities

权限接口:

public interface GrantedAuthority extends Serializable {
	
	String getAuthority();
}

AccessDecisionManager

AbstractSecurityInterceptor通过调用AccessDecisionManager,由AccessDecisionManager决定请求的最终访问权限

SpringSecurity提供了大量的控制权限访问的拦截器去保障某些对象的安全,像方法调用啊,web请求等等.而AccessDecisionManager就决定这个请求,调用是不是会调用成功

基于AccessDecisionManager实现类的投票

他们之间的类图关系

image.png
AffirmativeBased为例

public void decide(Authentication authentication, Object object,
			Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
		int deny = 0;
		for (AccessDecisionVoter voter : getDecisionVoters()) {
			int result = voter.vote(authentication, object, configAttributes);
			switch (result) {
			case AccessDecisionVoter.ACCESS_GRANTED:
				return;
			case AccessDecisionVoter.ACCESS_DENIED:
				deny++;
				break;
			default:
				break;
			}
		}
		// 抛异常
		if (deny > 0) {
			throw new AccessDeniedException(messages.getMessage(
					"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
		}

		// 检查是不是允许所有投票者都弃权
		checkAllowIfAllAbstainDecisions();
	}

通过一系列AccessDecisionVoter的实现类不断轮询投票,然后AccessDecisionManager基于投票结果决定是否抛出AccessDeniedException

每个AccessDecisionVoter有三中结果

  • ACCESS_GRANTED: 同意访问
  • ACCESS_ABSTAIN:如果对授权决定没有意见,就投此票,
  • ACCESS_DENIED:拒绝访问

**

RoleVoter

基于角色的投票者,以ROLE_开头的权限,用此投票

AuthenticatedVoter

用于匿名,完全认证和记住我的认证用户的权限控制,用此投票


当然.可以继承AccessDecisionVoter进行定制投票器

After Invocation Handling

虽然在进行安全对象调用之前AbstractSecurityInterceptor会调用AccessDecisionManager,但某些应用程序需要一种方法来修改安全对象调用实际返回的对象。 尽管可以轻松实现自己的AOP问题来实现这一点,但Spring Security提供了一个方便的挂钩,该挂钩具有几种与其ACL功能集成的具体实现。

image.png

授权过程



image.png

  • 首先FilterSecurityInterceptorSecurityContextHolder中获取Authentication
  • FilterSecurityInterceptor创建FilterInvocation(包含HttpServletRequest,HttpServletResponse,FilterChain)
  • FilterInvocation传递给SecurityMetaDataSource获取ConfigAttributes
  • FilterInvocation, Authentication, ConfigAttributes传递给AccessDecisionManager
    • 如果授权失败,则抛出AccessDeniedException,然后ExceptionTranslationFilter处理这个异常
    • 如果授权成功,则FilterSecurityInteceptor继续执行FilterChain后面逻辑


参考文档