这一节来研究下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表单认证的原理