Spring Security(原理解析:简单入门)

内容分为三部分:1. spring security的链;2. spring security的基本类;3. spring security的验证流程

Spring Security的链

    Spring Security的实现依靠的是链,用户发起请求,客户端接受请求,再经过Web Filter的过滤请求最终传到Servlet上,而spring security就是再原有的链新添加了一条链,这条链专门用来处理用户的安全认证与授权。如图:
spring security链
    FilterChainProxy即spring security插入原web filter的的一个过滤器,这个过滤器管理着许多链,每条链对应一种过滤策略,如图:
spring security开始链
    FilterChainProxy部分源码:

public class FilterChainProxy extends GenericFilterBean {
    ...
    private List<SecurityFilterChain> filterChains;
    ...
	public void doFilter(...) throws IOException, ServletException {
    }
}

    filterChains保存的就是一个个过滤链,注意是链,不是单个的过滤器,这个链的类型是SecurityFilterChain,查看SecurityFilterChain的源码:

public interface SecurityFilterChain {   
    //用于对请求匹配
    boolean matches(HttpServletRequest request);   
    //返回过滤链,一般来说当调用matches方法返回true时会调用getFilters返回一条过滤链
    List<Filter> getFilters();
}

    可以看出SecurityFilterChain是一个接口,且只有两个方法,该接口只有一个实现类是DefaultSecurityFilterChain其源码是:

public final class DefaultSecurityFilterChain implements SecurityFilterChain {
	private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
	private final RequestMatcher requestMatcher;
    //此变量保存的正是该链上所有的过滤器
	private final List<Filter> filters;

	public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) {
		this(requestMatcher, Arrays.asList(filters));
	}

	public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
		logger.info("Creating filter chain: " + requestMatcher + ", " + filters);
		this.requestMatcher = requestMatcher;
		this.filters = new ArrayList<>(filters);
	}

	public RequestMatcher getRequestMatcher() {
		return requestMatcher;
	}

	public List<Filter> getFilters() {
		return filters;
	}

	public boolean matches(HttpServletRequest request) {
		return requestMatcher.matches(request);
	}

	@Override
	public String toString() {
		return "[ " + requestMatcher + ", " + filters + "]";
	}

    从此出可以看出spring security的数据过滤正是由DefaultSecurityFilterChain(具体过程更复杂)执行的,变量filters保存的就是该链上所有的过滤器,例如spring security中重要的过滤器:

  • ChannelProcessingFilter

    因为请求可能需要重定向到不同的协议

  • SecurityContextPersistenceFilter

    在用户请求成功认证后,会将认证成功后的上下文信息SecurityContext通过该类存储在HttpSession中。当用户下次进行请求时,可以直接从通过该类从HttpSession中获取已经认证后的上下文信息SecurityContext,从而避免重复认证。

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
                throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;
     
            if (request.getAttribute(FILTER_APPLIED) != null) {
                // 确保对于每个请求只应用一次过滤器
                chain.doFilter(request, response);
                return;
            }
     
            final boolean debug = logger.isDebugEnabled();
     
            request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
     
            if (forceEagerSessionCreation) {
                HttpSession session = request.getSession();
     
                if (debug && session.isNew()) {
                    logger.debug("Eagerly created session: " + session.getId());
                }
            }
            // 将 request/response 对象交给HttpRequestResponseHolder维持  
            HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
                    response);
            //通过SecurityContextRepository接口的实现类装载SecurityContext实例  
            //HttpSessionSecurityContextRepository将产生SecurityContext实例的任务交给-->	      	   //SecurityContextHolder.createEmptyContext()  
            //SecurityContextHolder再根据策略模式的不同,  
            //把任务再交给相应策略类完成SecurityContext的创建  
            //如果没有配置策略名称,则默认为  
            //ThreadLocalSecurityContextHolderStrategy,  
            //该类直接通过new SecurityContextImpl()创建实例
            SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
     
            try {
                //将产生的SecurityContext再通过SecurityContextHolder->  
                //ThreadLocalSecurityContextHolderStrategy设置到ThreadLocal 
                SecurityContextHolder.setContext(contextBeforeChainExecution);
                //继续把请求流向下一个过滤器执行  
                chain.doFilter(holder.getRequest(), holder.getResponse());
     
            }
            finally {
                //先从SecurityContextHolder获取SecurityContext实例  
                SecurityContext contextAfterChainExecution = SecurityContextHolder
                        .getContext();
                //关键性地除去SecurityContextHolder内容 - 在任何事情之前执行此操作
                //再把SecurityContext实例从SecurityContextHolder中清空
                //若没有清空,会受到服务器的线程池机制的影响  
                SecurityContextHolder.clearContext();
                //将SecurityContext实例持久化到session中 
                repo.saveContext(contextAfterChainExecution, holder.getRequest(),
                        holder.getResponse());
                request.removeAttribute(FILTER_APPLIED);
     
                if (debug) {
                    logger.debug("SecurityContextHolder now cleared, as request processing completed");
                }
            }
    }
    /*通过源码中的注释,应该可以看出来,这个Filter的作用主要是创建一个空的SecurityContext
    (如果session中没有SecurityContext实例),然后持久化到session中。*/
    
  • ConcurrentSessionFilter

  • UsernamePasswordAuthenticationFilter,CasAuthenticationFilter,BasicAuthenticationFilter

    等处理身份验证。以便SecurityContextHolder可以修改为包含有效的Authentication请求令牌

    UsernamePasswordAuthenticationFilter所担负的功能:

    1. 调用 ProviderManager 类中的 authenticate() 方法进行认证
    2. 将用户名和密码封装成 UsernamePasswordAuthenticationToken
  • SecurityContextHolderAwareRequestFilter

  • JaasApiIntegrationFilter

  • RememberMeAuthenticationFilter

    记住我服务处理

  • AnonymousAuthenticationFilter

    匿名身份处理,更新SecurityContextHolder

  • ExceptionTranslationFilter

    获任何Spring Security异常,以便可以返回HTTP错误响应或启动适当的AuthenticationEntryPoint

  • FilterSecurityInterceptor

    用于保护web URI并在访问被拒绝时引发异常

以上是基本的执行流程,接下来要说的是spring security的验证授权

Spring Security的基本类

Spring Security使用前需要了解一些类

UserDetails(保存用户基本信息)

public interface UserDetails extends Serializable {
 	//获取用户权限,一般是用户的角色信息
	Collection<? extends GrantedAuthority> getAuthorities();
    //获取密码
	String getPassword();
    //获取用户名
	String getUsername();
    //账户是否过期
	boolean isAccountNonExpired();
    //账户是否被锁定
	boolean isAccountNonLocked();
    //密码是否过期
	boolean isCredentialsNonExpired();
    //账户是否可用
	boolean isEnabled();
}

UserDetailsService(从数据库加载用户信息保存到UserDetail)

public interface UserDetailsService {   
    //通过用户名加载用户信息
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
//我们通常会自定义一个类继承该接口,重写loadUserByUsername方法,同时创建一个继承UserDetails的类,可   //以返回我们想要返回的额外的数据库中的信息,该类的具体使用会在之后说明。

Authentication (保存验证信息)

public interface Authentication extends Principal, Serializable {
	//获取用户权限的集合,一般来说是用户的角色信息
	Collection<? extends GrantedAuthority> getAuthorities();
	//翻译为证书,获取证明用户认证的信息,通常是密码
	Object getCredentials();
	//获取用户信息额外的信息(这部分信息可以是我们的用户表中的信息)
	Object getDetails();
	//获取用户身份信息,未认证时获取的是用户名,认证后获取的是UserDetails类或其子类保存的用户信息
	Object getPrincipal();
	//获取当前 Authentication 是否已认证
	boolean isAuthenticated();
	//设置当前 Authentication 是否已认证
	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
//因为该类是接口无法做任何操作的,因此下文所述的Authentication都是其实现类,最常见的实现类就是UsernamePasswordAuthenticationToken了
//实际上并非直接实现,中间还有一个AbstractAuthenticationToken
//关系是: UsernamePasswordAuthenticationToken->AbstractAuthenticationToken->Authentication

Authentication在刚开始未认证前通过getPrincipal()获取的知识用户名,我们需要通过认证去获取更多的用户信息。

AuthenticationManager(验证Authentication并获取UserDetails信息)

public interface AuthenticationManager {
    //验证登陆者身份
    Authentication authenticate(Authentication var1) throws AuthenticationException;
}
//该方法通过var1的getPrincipal()获取用户名,再借助UserDetailsService的实现类的       		   //loadUserByUsername方法去加载数据库内保存的用户信息放置在Authentication继承类内,具体实现下文会讲。

Authentication可以通过上述方法进行验证了,验证后还要存储验证后的Authentication,以留后用。

SecurityContext(保存Authentication)

public interface SecurityContext extends Serializable {
	//获取验证信息
	Authentication getAuthentication();
	//设置验证信息
	void setAuthentication(Authentication authentication);
}
//可以看出该类的作用就是保存验证信息的,该类的实现类是SecurityContextImpl,也是唯一实现类

项目往往是多用户同时使用的即多线程的,有时也会为单线程的因此SecurityContext的保存就需要多种存储策略

SecurityContextHolderStrategy(存储SecurityContext)

public interface SecurityContextHolderStrategy {
    //清除SecurityContext
    void clearContext();
	//获取SecurityContext
    SecurityContext getContext();
	//设置SecurityContext
    void setContext(SecurityContext var1);
	//创建空的SecurityContext,即创建一个SecurityContext,其保存的Authentication内是空的
    SecurityContext createEmptyContext();
}
//该类的实现类有三个对应三种不同的存储策略:
//1. ThreadLocalSecurityContextHolderStrategy 将SecurityContext存储在线程中,但子线程可以获取到父线程中的SecurityContext
//2. InheritableThreadLocalSecurityContextHolderStrategy 将SecurityContext存储在当前线程中
//3. GlobalSecurityContextHolderStrategy 将SecurityContext作为所有线程的公共变量存储
ThreadLocalSecurityContextHolderStrategy(每个线程独有SecurityContext)
final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
    //此处是关键,可以看出这个类中的SecurityContext是通过ThreadLocal保存的
    //ThreadLocal可以在当前线程存储数据,只有在当前线程内才能访问到数据
    //因此本类实行存储策略就是将SecurityContext的访问限制在单个线程内
    private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal();

    ThreadLocalSecurityContextHolderStrategy() {}

    public void clearContext() {...}

    public SecurityContext getContext() {...}

    public void setContext(SecurityContext context) {...}

    //从这里也可以看出SecurityContext的实现类就是SecurityContextImpl
    public SecurityContext createEmptyContext() {
        return new SecurityContextImpl();
    }
}
InheritableThreadLocalSecurityContextHolderStrategy(父子线程共用)
final class InheritableThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
    //可以看出本类也是利用线程存储的,不过实现类是InheritableThreadLocal,inherit即继承之意
    //因此本类实行存储策略就是将SecurityContext的访问限制在父子线程内
    private static final ThreadLocal<SecurityContext> contextHolder = new InheritableThreadLocal();

    InheritableThreadLocalSecurityContextHolderStrategy() { }

    public void clearContext() {...}

    public SecurityContext getContext() {...}

    public void setContext(SecurityContext context) {...}

    public SecurityContext createEmptyContext() {
        return new SecurityContextImpl();
    }
}
GlobalSecurityContextHolderStrategy(所有线程共用)
final class GlobalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
	//可以看出本类中SecurityContext就是存储在普通变量中的,因此每个线程访问的都是同一个SecurityContext
    //因此本类实行存储策略就是将SecurityContext的访问在所有线程内共享
	private static SecurityContext contextHolder;

	public void clearContext() {...}

    public SecurityContext getContext() {...}

    public void setContext(SecurityContext context) {...}

    public SecurityContext createEmptyContext() {
        return new SecurityContextImpl();
    }
}

三种SecurityContext的存储策略介绍完毕那么还需要能够决定使用哪种存储策略的类

SecurityContextHolder(决定SecurityContext的存储策略)

 public class SecurityContextHolder {
    //SecurityContext 存储在当前线程中(默认使用)
	public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
    //SecurityContext 存储在线程中,但子线程可以获取到父线程中的 SecurityContext
    public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
    //SecurityContext 在所有线程中都相同
    public static final String MODE_GLOBAL = "MODE_GLOBAL";
    public static final String SYSTEM_PROPERTY = "spring.security.strategy";
    private static String strategyName = System.getProperty("spring.security.strategy");
    //这个类是重点,是SecurityContext存储策略的实施者,该类也是SecurityContext的真正存储者
    private static SecurityContextHolderStrategy strategy;
    private static int initializeCount = 0;

    public SecurityContextHolder() {
    }
	//清除SecurityContext
    public static void clearContext() {
        strategy.clearContext();
    }
	//获取SecurityContext
    public static SecurityContext getContext() {
        return strategy.getContext();
    }
	//获取initialize()的执行次数
    public static int getInitializeCount() {
        return initializeCount;
    }
	//初始化存储策略
    private static void initialize() {
        if (!StringUtils.hasText(strategyName)) {
            strategyName = "MODE_THREADLOCAL";
        }

        if (strategyName.equals("MODE_THREADLOCAL")) {
            //将SecurityContext存储在当前线程中(默认使用)
            strategy = new ThreadLocalSecurityContextHolderStrategy();
        } else if (strategyName.equals("MODE_INHERITABLETHREADLOCAL")) {
            //将SecurityContext存储在线程中,但子线程可以获取到父线程中的SecurityContext
            strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
        } else if (strategyName.equals("MODE_GLOBAL")) {
            //将SecurityContext作为所有线程的公共变量存储
            strategy = new GlobalSecurityContextHolderStrategy();
        } else {
            try {
                Class<?> clazz = Class.forName(strategyName);
                Constructor<?> customStrategy = clazz.getConstructor();
                strategy = (SecurityContextHolderStrategy)customStrategy.newInstance();
            } catch (Exception var2) {
                ReflectionUtils.handleReflectionException(var2);
            }
        }

        ++initializeCount;
    }
	//设置SecurityContext,亦即存储SecurityContext
    public static void setContext(SecurityContext context) {
        strategy.setContext(context);
    }
	//设存储策略
    //例如: SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL)
    public static void setStrategyName(String strategyName) {
        strategyName = strategyName;
        initialize();
    }
	//返回SecurityContext的真正存储者
    public static SecurityContextHolderStrategy getContextHolderStrategy() {
        return strategy;
    }
	//创建一个空的SecurityContextImpl,其保存的Authentication没有任何信息
    public static SecurityContext createEmptyContext() {
        return strategy.createEmptyContext();
    }

    public String toString() {
        return "SecurityContextHolder[strategy='" + strategyName + "'; initializeCount=" + initializeCount + "]";
    }
	//静态语句块,初始化存储策略
    static {
        initialize();
    }
}

常见使用的例如 SecurityContextHolder.getContext().setAuthentication(token);就是存储当前验证信息(token一般是继承了Authentication的UsernamePasswordAuthenticationToken)

Spring Security的验证流程

先说spring security的正常验证流程:

    前文提及过UsernamePasswordAuthenticationFilter这个过滤器,它就是spring security真正开始授权验证的过滤器,流程如下:

    UsernamePasswordAuthenticationFilter继承了AbstractAuthenticationProcessingFilter,执行到UsernamePasswordAuthenticationFilter过滤器时执行的实际时其抽象父类的doFilter(…)方法:

public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
		implements ApplicationEventPublisherAware, MessageSourceAware {
    ...
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {

		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
		//判断是否需要验证,不是本过滤类需要处理的url不进行验证
		if (!requiresAuthentication(request, response)) {
			chain.doFilter(request, response);
			return;
		}
		
		Authentication authResult;

		try {
            //这一步是关键,调用了子类的验证方法进行验证,子类指的其实就是UsernamePasswordAuthenticationFilter
			authResult = attemptAuthentication(request, response);
			if (authResult == null) {
				//没有得到认证结果,表明子类实现中无法处理该类型的认证
				return;
			}
            //存到session内(可设置不存到session内)
			sessionStrategy.onAuthentication(authResult, request, response);
		}
		catch (InternalAuthenticationServiceException failed) {...}
		catch (AuthenticationException failed) {
			// 验证失败
			unsuccessfulAuthentication(request, response, failed);
			return;
		}

		// 验证成功
		if (continueChainBeforeSuccessfulAuthentication) {
			chain.doFilter(request, response);
		}
		// 验证成功后要存储Authentication到应用上下文中
		successfulAuthentication(request, response, chain, authResult);
	}
    ...
    ...
    protected void successfulAuthentication(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain, Authentication authResult)
			throws IOException, ServletException {
        ...
		//存储Authentication的继承类
		SecurityContextHolder.getContext().setAuthentication(authResult);
		...
		//子类可以配置 successHandler进行自定义成功时的处理
		successHandler.onAuthenticationSuccess(request, response, authResult);
	}
    ...
}

    从上可以看出验证还是需要UsernamePasswordAuthenticationFilter的attemptAuthentication方法的,AbstractAuthenticationProcessingFilter自身的作用就是将验证添加到过滤链中,可能会考虑为什么不直接在自己的类中验证,我想通过子类验证或许是因为这种方式可以让我们自定义验证方式式,当然仅是猜测,没做过实验,接着看下UsernamePasswordAuthenticationFilter的源码:

public class UsernamePasswordAuthenticationFilter extends
		AbstractAuthenticationProcessingFilter {
    
	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;
	private boolean postOnly = true;

	public UsernamePasswordAuthenticationFilter() {
		super(new AntPathRequestMatcher("/login", "POST"));
	}
    //验证的方法
	public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
        //限定登录请求的类型为POST否则抛出异常
		if (postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException(...);
		}
		//获取请求中的用户名和密码
		String username = obtainUsername(request);
		String password = obtainPassword(request);
		//设置用户名与密码不为null
		if (username == null) {username = "";}
		if (password == null) {password = "";}
		username = username.trim();
		//该类就是Authentication的子孙类
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);
		//允许子类设置“details”属性
		setDetails(request, authRequest);
		// 调用 AuthenticationManager 接口的 authenticate()方法进行认证
		// 实际上是调用它的实现类 ProviderManager类中的 authenticate()方法
		return this.getAuthenticationManager().authenticate(authRequest);
	}

	protected String obtainPassword(HttpServletRequest request) {
		return request.getParameter(passwordParameter);
	}

	protected String obtainUsername(HttpServletRequest request) {
		return request.getParameter(usernameParameter);
	}

	protected void setDetails(HttpServletRequest request,
			UsernamePasswordAuthenticationToken authRequest) {
		authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
	}

	
	public void setUsernameParameter(String usernameParameter) {
		this.usernameParameter = usernameParameter;
	}
    
	public void setPasswordParameter(String passwordParameter) {
		this.passwordParameter = passwordParameter;
	}
    
	public void setPostOnly(boolean postOnly) {this.postOnly = postOnly;}

	public final String getUsernameParameter() {return usernameParameter;}

	public final String getPasswordParameter() {return passwordParameter;}
}

    根据上述可看出UsernamePasswordAuthenticationFilter的作用还是匹配特定的请求和解析请求中的用户名和密码,做好了验证前的准备,验证任务接着交给了ProviderManager类中的 authenticate方法:

public class ProviderManager implements AuthenticationManager,MessageSourceAware,
		InitializingBean {
	...
    public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		Authentication result = null;
		boolean debug = logger.isDebugEnabled();

		//遍历所有的provider
		//SpringSecurity提供了许多AuthenticationProvider的实现类用于处理各种认证
		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}

			if (debug) {logger.debug(...);}

			try {
                //某个AuthenticationProvider进行实际上的验证
                //这里找到的是AbstractUserDetailsAuthenticationProvider
				result = provider.authenticate(authentication);
				//验证成功了
				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException e) {...}
			catch (InternalAuthenticationServiceException e) {...}
			catch (AuthenticationException e) {...}
		}
		//剩下的代码是其余情况的异常处理
		...
		throw lastException;
	}
    ...
}

    从上可以看出ProviderManager的任务是找到合适的AuthenticationProvider去处理验证,其实根据名字直译就是验证提供者有很多,现在要由验证提供者的管理者决定哪个人去处理这个验证。接下来看分析下AbstractUserDetailsAuthenticationProvider的源码:

public abstract class AbstractUserDetailsAuthenticationProvider implements
		AuthenticationProvider, InitializingBean, MessageSourceAware {
    public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
         //要求传入的authentication对象必须是UsernamePasswordAuthenticationToken类或其子类的实例
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				() -> messages.getMessage(
						"AbstractUserDetailsAuthenticationProvider.onlySupports",
						"Only UsernamePasswordAuthenticationToken is supported"));

		// 获取用户名
		String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
				: authentication.getName();
		//从缓存中取出用户信息
		boolean cacheWasUsed = true;
		UserDetails user = this.userCache.getUserFromCache(username);
        //从缓存中取出用户信息失败
		if (user == null) {
			cacheWasUsed = false;
			try {
                //该方法在本类中是一个抽象方法未实现,因此这个方法的实现依靠的是其子类
                //用户信息与用户权限也正是在此处从数据库获取到的
                //框架为其实现了一个也是唯一的一个实现类DaoAuthenticationProvider
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
			}
            //用户未找到的异常,需要子类的retrieveUser方法抛出
			catch (UsernameNotFoundException notFound) {
				logger.debug("User '" + username + "' not found");

				if (hideUserNotFoundExceptions) {
					throw new BadCredentialsException(messages.getMessage(
							"AbstractUserDetailsAuthenticationProvider.badCredentials",
							"Bad credentials"));
				}
				else {
					throw notFound;
				}
			}
			Assert.notNull(user,
					"retrieveUser returned null - a violation of the interface contract");
		}
		try {
            //preAuthenticationChecks是UserDetailsChecker接口的实现类
            //因此账号验证是完全可以使用用户自定义的check方法验证
            //这个方法的实现类默认是当前类内的一个内部类
            //验证帐号是否锁定\是否禁用\帐号是否到期
			preAuthenticationChecks.check(user);
            //additionalAuthenticationChecks在本类是抽象方法,因此该方法仍是其子类实现的
            //其子类在此指的仍是DaoAuthenticationProvider
            //该方法是信用检查,通常指的就是密码检查,即密码是否输入正确
			additionalAuthenticationChecks(user,
					(UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException exception) {
            //调用某个UserDetailsChecker接口的实现类验证失败后,就判断下用户信息是否从内存中得到
            //如果之前是从内存中得到的用户信息,那么考虑到可能数据是不实时的,即数据失效的可能性
            //此情况下就重新通过retrieveUser方法去取出用户信息,再次重复进行检查验证
			if (cacheWasUsed) {
				cacheWasUsed = false;
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
				preAuthenticationChecks.check(user);
				additionalAuthenticationChecks(user,
						(UsernamePasswordAuthenticationToken) authentication);
			}
			else {
				throw exception;
			}
		}
		//此处后置处理只是判断了下用户的密码是否过期,如过期则记入日志
		postAuthenticationChecks.check(user);
 		//如果没有缓存则进行缓存,此处的userCache默认是由NullUserCache类实现的
        //名如其义,该类的putUserInCache没做任何事
        //这就是public void putUserInCache(UserDetails user) {}
		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}
		//以下代码主要是把用户的信息和之前用户提交的认证信息重新组合成一个authentication实例返回
        //返回类是UsernamePasswordAuthenticationToken类的实例
		Object principalToReturn = user;
		
		if (forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}
		return createSuccessAuthentication(principalToReturn, authentication, user);
	}
    ...
    protected Authentication createSuccessAuthentication(Object principal,
			Authentication authentication, UserDetails user) {
		// 使用构造器构造 UsernamePasswordAuthenticationToken 对象
		UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
				principal, authentication.getCredentials(),
            	//此处会将用户权限信息查询出来传入构造方法内
				authoritiesMapper.mapAuthorities(user.getAuthorities()));
        //把验证过的用户信息赋值给新的要返回的UsernamePasswordAuthenticationToken
		result.setDetails(authentication.getDetails());
		return result;
	}
    ...
}

    现在留有一个问题:retrieveUser方法是如何获取用户信息的,之前我们提到过用户信息是通过UserDetailsService子类的loadUserByUsername方法加载的,因此可以联想这两个方法之间的关系。

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
    ...
    //这就是之前提到过的密码验证,就是在子类中验证的
    protected void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
        //获取登录接口接收的密码,为空则抛异常
        //可能会有疑惑UsernamePasswordAuthenticationFilter对密码进行了非空设置为什么还要判断
        //if (username == null) {username = "";}
		//if (password == null) {password = "";}
        //这是因为如果不用过滤器,我们自己是可以直接调用ProviderManager的authenticate方法的
        //Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
        //authenticationManagerBuilder类型是AuthenticationManagerBuilder
		if (authentication.getCredentials() == null) {
			logger.debug("Authentication failed: no credentials provided");
			throw new BadCredentialsException(messages.getMessage(
					"AbstractUserDetailsAuthenticationProvider.badCredentials",
					"Bad credentials"));
		}
		//获取登录接口接收的密码
		String presentedPassword = authentication.getCredentials().toString();
		//比较登录接口接收的密码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"));
		}
	}
    ...
    //这就是获取用户信息与用户权限的方法
    protected final UserDetails retrieveUser(String username,
			UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		prepareTimingAttackProtection();
		try {
            //在此处调用了loadUserByUsername方法
            //因此我们只需要创建一个继承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);
		}
	}
    
}

借鉴:

  1. https://www.cnblogs.com/demingblog/p/10874753.html
  2. https://blog.csdn.net/weixin_34082695/article/details/89661676
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值