一、文献参考
Spring Security认证与授权的原理(源码分析,超详细)_Zystem-CSDN博客_springsecurity认证原理
最简单易懂的Spring Security 身份认证流程讲解 - 曾俊杰的专栏 - 博客园
二、认证流程
2.1、AbstractAuthenticationProcessingFilter
用户名密码表单登录过滤器
是处理表单登陆的过滤器,与 表单登陆有关的所有操作都是在该类中及其子类中进行的。
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 {
authResult = attemptAuthentication(request, response);//在此处调用 的是`UsernamePasswordAuthenticationFilter`实现的方法。
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);//验证成功后调用此方法
}
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
+ authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);//将返回的用户对象放置到全局对象SecurityContext中
rememberMeServices.loginSuccess(request, response, authResult);
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
successHandler.onAuthenticationSuccess(request, response, authResult);
}
在验证成功后上面这个方法会将返回的Authentication放置到SecurityContext中,我们可以通过 SecurityContextHolder.getContext().getAuthentication()获取用户信息。
2.2、UsernamePasswordAuthenticationFilter
UsernamePasswordAuthenticationFilter
继承自AbstractAuthenticationProcessingFilter
。
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
用于拦截/login路径且为post的请求。其他请求不会走UsernamePasswordAuthenticationFilter拦截器。 此处的/login路径也可以进行修改。
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);//从request中获取用户名,
String password = obtainPassword(request);//从request中国获取密码。
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);//将用户名和密码封装程一个UsernamePasswordAuthenticationToken
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);//将封装好的UsernamePasswordAuthenticationToken交给AuthenticationManager去认证
}
attemptAuthentication方法实现父类的方法。用于具体的表单操作。
2.3、UsernamePasswordAuthenticationFilter是什么时候被加入到拦截器链中的呢?
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login/goLogin") // 指定登录页面(当我们未登录状态访问一个页面时会自动跳转到此处指定的页面)
.loginProcessingUrl("/my2/login") // 指定登录url(默认为/login,此处和表单的action要一致,即登录信息提交到此处指定的url后security才可进行登录处理)
.usernameParameter("username") // 指定登录用户名参数名称(默认为username)
.passwordParameter("password") // 指定密码参数名称(默认为password)
.successHandler(new LoginSuccessHandler(dataSource)) //登录成功后处理类
// .defaultSuccessUrl("/login/noPermit") // 指定登录成功后跳转页 可以在此接口处做一些其他的操作比如进行认证获取token,successHandler与defaultSuccessUrl只可选择其中一个方式
.failureUrl("/other/loginError") // 指定登录失败URL,可在该接口中抛出异常。(默认为/login?error,就是在controller中写一个接口,登录失败会跳转那个接口)
.permitAll();
}
这个方放不陌生吧。这是我们自己继承WebSecurityConfigurerAdapter并且重写的一个方法。在此方法中我们可以配置登录相关的东西。 http.formLogin()返回的是一个FormLoginConfigurer对象。
public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
return getOrApply(new FormLoginConfigurer<>());
}
在formLogin()方法中new了一个FormLoginConfigurer对象。接下来往下看FormLoginConfigurer类的构造方法
public FormLoginConfigurer() {
super(new UsernamePasswordAuthenticationFilter(), null);
usernameParameter("username");
passwordParameter("password");
}
在这个构造方法中调用了父类的有参构造方法,并且传入了一个UsernamePasswordAuthenticationFilter过滤器。此处便是添加UsernamePasswordAuthenticationFilter过滤器的关键。再次进入父类的构造方法中
protected AbstractAuthenticationFilterConfigurer(F authenticationFilter,
String defaultLoginProcessingUrl) {
this();
this.authFilter = authenticationFilter;
if (defaultLoginProcessingUrl != null) {
loginProcessingUrl(defaultLoginProcessingUrl);
}
}
将UsernamePasswordAuthenticationFilter对象传递进来了
在configure(HttpSecurity http)中http.formLogin()的配置都会传值到AbstractAuthenticationFilterConfigurer对象中。也就是说。当你调用http.formLogin()时UsernamePasswordAuthenticationFilter就会被当成一个拦截器加入到拦截器链中。
2.4、attemptAuthentication方法中的this.getAuthenticationManager().authenticate(authRequest);是怎么进行认证的?
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
private static final Log logger = LogFactory.getLog(ProviderManager.class);
private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
private List<AuthenticationProvider> providers = Collections.emptyList();
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private AuthenticationManager parent;
private boolean eraseCredentialsAfterAuthentication = true;
public ProviderManager(List<AuthenticationProvider> providers) {
this(providers, null);
}
public ProviderManager(List<AuthenticationProvider> providers,
AuthenticationManager parent) {
Assert.notNull(providers,