本文内容来自王松老师的《深入浅出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;
- }
当用户使用用户名和密码登录或者采用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的授权体系中有两个关键的接口:
看完的小伙伴可以在脑海里一边想一遍动手画一下Authentication、AuthenticationManager、AuthenticationProvider、ProviderManager、AccessDecisionMamager、AccessDecisionVoter它们之间的关系。