SpringSecurity中的一些问题及梳理

简介

我们前面已经基本介绍了Spring Security中最重要和常用的组件与功能。

SpringSecurity原理(一)——初探
SpringSecurity原理(二)——认证
SpringSecurity原理(三)——授权
SpringSecurity原理(四)——过滤器
SpringSecurity原理(五)——扩展与配置

这篇文章,将介绍一下SpringSecurity中一下坑和奇怪的现象,可能会有助于更好理解SpringSecurity。

登录和登录处理问题

我们已经知道SpringSecurity的DefaultLoginPageGeneratingFilter会我们默认生成登录页,这个路径是login。

但是我们处理登录的路径是啥呢?

SpringSecurity中的一些问题及梳理

我们可以看到登录页面也是login。

这就其了怪了,为什么同样的路径产生了不同的效果?

因为:HTTP method的不同,登录页面是GET请求,登录操作是POST请求。

操作就是这么骚,让无数人闪了腰。

所以,当你做前后端分离项目,前端使用get(’/login’,optioin)发现是404,不要奇怪,不要流泪,改成post也许就好了。

这么骚的操作是如何实现的呢?

答案在UsernamePasswordAuthenticationFilter:

SpringSecurity中的一些问题及梳理

我们可以看到,UsernamePasswordAuthenticationFilter只匹配post的login,也可以看到为什么通过UsernamePasswordAuthenticationFilter认证参数必须是username和password了。

其实,操作都是在它的父类AbstractAuthenticationProcessingFilter中完成,既然是Filter肯定看doFilter方法,调用了requiresAuthentication。

2次login:

首先是(login,get)UsernamePasswordAuthenticationFilter没有处理直接跳过了,如果是需要授权的url,后面到了ExceptionTranslationFilter,因为需要认证和授权,于是被重定向到了登录页面。

然后是(login,post)匹配上了,通过UsernamePasswordAuthenticationFilter完成了认证操作。

当然上面参数都是可以配置的,例如,登录页面url、处理登录的url、用户名参数、密码参数、没有认证咋处理,认证成功咋处理:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
            .loginPage("/login")//登录页面
            .loginProcessingUrl("/do-login")//处理登录逻辑
            .passwordParameter("username")//用户名参数
            .passwordParameter("password")//密码参数
            // 三个配置一个就可以了
            .defaultSuccessUrl("/loginsucess").successForwardUrl("/sucess").successHandler(sucessHandler)
            // 三个配置一个就可以了
            .failureForwardUrl("/fail").failureUrl("/failure").failureHandler(loginAuthenticationFailureHandler)
            .permitAll();
}

defaultSuccessUrl和successForwardUrl,最好使用defaultSuccessUrl,可以重定向到来登录页面之前的url,defaultSuccessUrl还接收一个boolean参数,如果设置为true,就和successForwardUrl等价。

如果,想要了解login和UsernamePasswordAuthenticationFilter到底是怎样联系起来的,还得看http.formLogin():

public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
    return getOrApply(new FormLoginConfigurer<>());
}

FormLoginConfigurer看名字我们就知道是一个Configurer配置类,这个类前面的文章已经介绍了,可以看一下,也可以简单看一下后面的重要SecurityBuilder提示:
SpringSecurity中的一些问题及梳理

有兴趣可以自己看一下这个类,这里就不详细展开了。

重定向无限循环问题

SpringSecurity中的一些问题及梳理

如果出现如上图所示的无限循环问题,那么多半有类似下面的配置:

@Override
public void configure(WebSecurity web) {
    web.ignoring().antMatchers(
            "/login"
     );
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().antMatchers("/index.html").permitAll().anyRequest().authenticated();
    http.formLogin();
}

了解了前面的登录流程,理解这个无限循环就容易多了,就是因为login不需要被认证,但是login需要被授权,于是ExceptionTranslationFilter到被定向到默认的登录页面login,然后进入无限套娃模式。

关于授权

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().antMatchers("/index.html").permitAll().anyRequest().authenticated();
    http.formLogin();
}

关于授权主要看http.authorizeRequests()方法:

public ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests()
        throws Exception {
    ApplicationContext context = getContext();
    return getOrApply(new ExpressionUrlAuthorizationConfigurer<>(context)).getRegistry();
}

跨越问题

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {

	private CorsConfiguration buildConfig() {
		CorsConfiguration corsConfiguration = new CorsConfiguration();
		corsConfiguration.addAllowedOrigin("*");
		corsConfiguration.addAllowedHeader("*");
		corsConfiguration.addAllowedMethod("*");
		corsConfiguration.addExposedHeader("Authorization");
		return corsConfiguration;
	}

	@Bean
	public CorsFilter corsFilter() {
		UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
		source.registerCorsConfiguration("/**", buildConfig());
		return new CorsFilter(source);
	}

	@Override
	public void addCorsMappings(CorsRegistry registry) {
		registry.addMapping("/**")
				.allowedOrigins("*")
//          .allowCredentials(true)
				.allowedMethods("GET", "POST", "DELETE", "PUT")
				.maxAge(3600);
	}
}

除了上面的配置,还需要:

protected void configure(HttpSecurity http) throws Exception {
		http.cors();
}

自己测试很多时候会把csrf禁用,否则,使用post,但是参数在url中这种请求就会被拦截。

protected void configure(HttpSecurity http) throws Exception {
		http.cors().and().csrf().disable();
}

重要SecurityBuilder提示

WebSecurity

WebSecurity是一个SecurityBuilder,所以它的主要职责之一就是创建Filter,重点关注它的build方法,是继承了AbstractSecurityBuilder的build,具体逻辑在AbstractConfiguredSecurityBuilder的doBuild方法中。

@Override
protected final O doBuild() throws Exception {
    synchronized (this.configurers) {
        this.buildState = BuildState.INITIALIZING;
        beforeInit();
        init();
        this.buildState = BuildState.CONFIGURING;
        beforeConfigure();
        configure();
        this.buildState = BuildState.BUILDING;
        O result = performBuild();
        this.buildState = BuildState.BUILT;
        return result;
    }
}

很标准的模板方法模式。

正真执行构建的逻辑在performBuild方法中,这个方法在WebSecurity创建了FilterChainProxy,在HttpSecurity中创建了DefaultSecurityFilterChain。

HttpSecurity

前面已经分析了,HttpSecurity的目的就一个创建DefaultSecurityFilterChain,注意它的performBuild方法。

上一篇:Scala 中异常的处理


下一篇:学习Java声明异常throws