这一节来研究下spring security中FormLoginConfigurer这个配置器的作用

一、综述

FormLoginConfigurer 本质上还是一个SecurityConfigurer,用来对HttpSecurity这个构建器进行配置,它用来对表单登录的功能进行配置,通过HttpSecurity#formLogin()方法来给HttpSecurity 对象中添加此配置器,也就是添加表单登录的功能

二、构造方法

先看下构造方法

public FormLoginConfigurer() {
    // 创建了一个UsernamePasswordAuthenticationFilter,传给了父类构造方法
    super(new UsernamePasswordAuthenticationFilter(), null);
    //设置登录时接收用户名的参数名
    usernameParameter("username");
    //设置登录时接收密码的参数名
    passwordParameter("password");
}
//上边的usernameParameter,passwordParameter可以用来调整登录时的参数名

接下来看下父类AbstractAuthenticationFilterConfigurer的构造方法

private F authFilter;//这个过滤器最后会被添加到DefaultSecurityFilterChain中用来处理表单登录请求

protected AbstractAuthenticationFilterConfigurer(F authenticationFilter,
			String defaultLoginProcessingUrl) {
		//调自己的无参数构造方法
		this();
		//把子类中new出的UsernamePasswordAuthenticationFilter赋值给了类中的属性authFilter
		//这个过滤器最后会被添加到DefaultSecurityFilterChain中用来处理表单登录请求
		this.authFilter = authenticationFilter;
		//这里是在处理接收登录请求的url
		if (defaultLoginProcessingUrl != null) {
			loginProcessingUrl(defaultLoginProcessingUrl);
		}
}

再看下AbstractAuthenticationFilterConfigurer的的无参数构造方法

private String loginPage;
private String loginProcessingUrl;

protected AbstractAuthenticationFilterConfigurer() {
    //设置了登录页面的访问地址为/login
    setLoginPage("/login");
}

private void setLoginPage(String loginPage) {
    //对loginPage这个类属性赋默认值 /login
    this.loginPage = loginPage;
    this.authenticationEntryPoint = new LoginUrlAuthenticationEntryPoint(loginPage);
}

三、loginPage和loginProcessingUrl方法

loginPage方法用来自定义登录页面,loginProcessingUrl用来指定登录请求的url

public FormLoginConfigurer<H> loginPage(String loginPage) {
    //调用父类的方法
    return super.loginPage(loginPage);
}

public T loginProcessingUrl(String loginProcessingUrl) {
		this.loginProcessingUrl = loginProcessingUrl;
	//这样设置以后这个过滤器也就是UsernamePasswordAuthenticationFilter匹配到请求路径
    //是loginProcessingUrl就会把这次请求当做登录来进行处理
    authFilter	.setRequiresAuthenticationRequestMatcher(createLoginProcessingUrlMatcher(loginProcessingUrl));
		return getSelf();
}

父类AbstractAuthenticationFilterConfigurer#loginPage

protected T loginPage(String loginPage) {
    setLoginPage(loginPage);
    //更新认证相关的一些默认值
    updateAuthenticationDefaults();
    this.customLoginPage = true;
    return getSelf();
}

protected final void updateAuthenticationDefaults() {
    if (loginProcessingUrl == null) {
        //如果没有指定loginProcessingUrl就把loginPage作为它
        //所以结合上边的构造方法可以知道默认情况下使用表单登录时loginProcessingUrl
        //和loginPage都是 /login
        loginProcessingUrl(loginPage);
    }
    if (failureHandler == null) {
        //设置登录失败的url
        failureUrl(loginPage + "?error");
    }

    final LogoutConfigurer<B> logoutConfigurer = getBuilder().getConfigurer(
        LogoutConfigurer.class);
    if (logoutConfigurer != null && !logoutConfigurer.isCustomLogoutSuccess()) {
        //退出的Url
        logoutConfigurer.logoutSuccessUrl(loginPage + "?logout");
    }
}

根据上边的源码可以得出一个重要的结论,如果自定义了loginPage就一定也要自定义loginProcessingUrl,

因为如果你只自定义loginPage那么loginProcessingUrl就会等于loginPage,加入你的loginPage="/login.html",显然这个地址是不能处理登录的。

四、init方法

因为FormLoginConfigurer 本质上还是一个SecurityConfigurer,所以它会有一个init方法来进行初始化。

@Override
public void init(H http) throws Exception {
    //调用父类的init方法
    super.init(http);
    //初始化生成登录页面的过滤器
    initDefaultLoginFilter(http);
}

private void initDefaultLoginFilter(H http) {
    //spring security中哪个默认的登录页面就是通过这个过滤器生成的,这里是从共享对象中获取它,
    //它是在DefaultLoginPageConfigurer这个配置器中添加到共享对象中的。
    //共享对象是spring security定义的,用来在配置过程中共享对象的。
    DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = http
        .getSharedObject(DefaultLoginPageGeneratingFilter.class);
    //底下就是在把通过FormLoginConfigurer设置的一些属性设置到过滤器中,用来生成默认登录页面
    //例如username,password肯定会被作为登录页面中的表单属性。
    if (loginPageGeneratingFilter != null && !isCustomLoginPage()) {
        loginPageGeneratingFilter.setFormLoginEnabled(true);
        loginPageGeneratingFilter.setUsernameParameter(getUsernameParameter());
        loginPageGeneratingFilter.setPasswordParameter(getPasswordParameter());
        loginPageGeneratingFilter.setLoginPageUrl(getLoginPage());
        loginPageGeneratingFilter.setFailureUrl(getFailureUrl());
        loginPageGeneratingFilter.setAuthenticationUrl(getLoginProcessingUrl());
    }
}

继续看下父类的init方法

@Override
//这个方法就是设置了一些默认值
public void init(B http) throws Exception {
    //设置认证相关的默认值
    updateAuthenticationDefaults();
    updateAccessDefaults(http);
    registerDefaultAuthenticationEntryPoint(http);
}

protected final void updateAuthenticationDefaults() {
    // 如果url为空就设置这个url
    if (loginProcessingUrl == null) {
        //url为空就设置成loginPage也就是上边构造方法传的 /login
        //如果你仅自定义了loginPage,执行到这里就会造成loginPage==loginProcessingUrl
        //所以loginPage,loginProcessingUrl这两个要一起自定义
        loginProcessingUrl(loginPage);
    }
    //登录失败处理器的默认值设置
    if (failureHandler == null) {
        failureUrl(loginPage + "?error");
    }
	//logout配置
    final LogoutConfigurer<B> logoutConfigurer = getBuilder().getConfigurer(
        LogoutConfigurer.class);
    //如果有logout配置,并且不是用户自定义的,就设置logoutSuccessUrl
    if (logoutConfigurer != null && !logoutConfigurer.isCustomLogoutSuccess()) {
        logoutConfigurer.logoutSuccessUrl(loginPage + "?logout");
    }
}

五、configure方法

因为FormLoginConfigurer 本质上还是一个SecurityConfigurer,所以它或者它的父类会有一个configure方法

AbstractAuthenticationFilterConfigurer#configure

//这个方法用来对属性authFilter也就是UsernamePasswordAuthenticationFilter做一些配置
@Override
public void configure(B http) throws Exception {
    PortMapper portMapper = http.getSharedObject(PortMapper.class);
    if (portMapper != null) {
        authenticationEntryPoint.setPortMapper(portMapper);
    }

    RequestCache requestCache = http.getSharedObject(RequestCache.class);
    if (requestCache != null) {
        this.defaultSuccessHandler.setRequestCache(requestCache);
    }
	//这个filter就是构造方法中传过来的UsernamePasswordAuthenticationFilter,
    //在这个filter中会有一个authenticationManager属性,用来验证用户名和密码是否正确
    //这个属性就是在这里赋值的,从共享对象中取出AuthenticationManager
    authFilter.setAuthenticationManager(http
                                        .getSharedObject(AuthenticationManager.class));
    authFilter.setAuthenticationSuccessHandler(successHandler);
    authFilter.setAuthenticationFailureHandler(failureHandler);
    if (authenticationDetailsSource != null) {
        authFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
    }
    SessionAuthenticationStrategy sessionAuthenticationStrategy = http
        .getSharedObject(SessionAuthenticationStrategy.class);
    if (sessionAuthenticationStrategy != null) {
        authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
    }
    RememberMeServices rememberMeServices = http
        .getSharedObject(RememberMeServices.class);
    if (rememberMeServices != null) {
        authFilter.setRememberMeServices(rememberMeServices);
    }
    //这是在后处理器对authFilter进行加工,后处理是由使用者提供来对authFilter做进一步处理,
    //大部分情况都没有处理器
    F filter = postProcess(authFilter);
    //把authFilter添加到HttpSecurity中,最终会被加到DefaultSecurityFilterChain中
    http.addFilter(filter);
}

上边提到了从SharedObject中获取AuthenticationManager的对象,那么它是在什么时候放进SharedObject的呢?

是在HttpSecurity的beforeConfigure方法中添加进去的

HttpSecurity

@Override
protected void beforeConfigure() throws Exception {
    //实际是调用了AuthenticationManagerBuilder.build方法来生成对象,
    //这个AuthenticationManagerBuilder也实现了SecurityBuilder,也是一个构件器
    setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());
}

private AuthenticationManagerBuilder getAuthenticationRegistry() {
    return getSharedObject(AuthenticationManagerBuilder.class);
}

关于AuthenticationManager的作用,可以看这一篇详细了解下 spring seurity表单认证的原理