Spring Security 教程(三):自定义登录页面

2022-08-16 13:33 阅读

看到上一章的登录页面,大部分人的第一反应就是,我要用自己的登录页面怎么办?

之前全部使用SpringBoot的默认配置,现在开始需要增加自己的配置项了。

配置自定义登录地址

定义自己的SecurityFilterChain,SpringBoot自动配置的SecurityFilterChain将会自动失效。

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 注意不要使用旧版的 http.authorizeRequests(),该方法启用 FilterSecurityInterceptor
        // 启用授权Filter:AuthorizationFilter(替换FilterSecurityInterceptor)
        http.authorizeHttpRequests()
            // 所有的请求
            .anyRequest()
            // 都必须登录
            .authenticated();
        // 启用表单登录Filter:UsernamePasswordAuthenticationFilter
        http.formLogin()
            // 自定义登录页面地址
            .loginPage("/login")
            // 放行登录地址(访问登录地址无需已登录状态)
            .permitAll();
        return http.build();
    }

其中关键代码是.loginPage("/login"),定义了自己的登录页面地址为/login。这个地址和默认登录地址是一样的,但如此定义之后,访问这个地址将访问我们自定义的地址,而不会访问系统自带的登录页面。当然也可以定义为其它地址,如.loginPage("/my-login")

自定义Controller

既然自定义了自己的登录地址,必然需要一个Controller处理这个地址。

@Controller
public class LoginController {
    @GetMapping("/login")
    public String index() {
        return "login";
    }
}

自定义登录页面

/src/main/java/resources/templates/login.ftlh

<html>
<head>
    <title>登录</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
    <link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/>
</head>
<body>
    <div class="container">
        <form class="form-signin" method="post" action="/login">
            <h2 class="form-signin-heading">请登录</h2>
            <p>
                <label for="username" class="sr-only">用户名</label>
                <input type="text" id="username" name="username" class="form-control" placeholder="用户名" required autofocus>
            </p>
            <p>
                <label for="password" class="sr-only">密码</label>
                <input type="password" id="password" name="password" class="form-control" placeholder="密码" required>
            </p>
            <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}">
            <button class="btn btn-lg btn-primary btn-block" type="submit">提交</button>
        </form>
    </div>
</body>
</html>

这里有几个关键点:

  • 用户名表单名称必须使用username
  • 密码表单名称必须使用password
  • 必须加上隐藏字段<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}">,用于防范CSRF攻击。
  • 提交的登录地址必须为之前配置的地址,此处为/login

涉及的Filter

之前介绍过,Spring Security自带了大量的Filter,用于实现各种不同的功能。此处就用到了两个:

  • AuthorizationFilter(替换FilterSecurityInterceptor):用于权限控制,即哪些地址需要什么权限。
  • UsernamePasswordAuthenticationFilter:用户登录,使用用户名、密码进行登录。

自动装配的SecurityFilterChain

前面提到,我们自定义的SecurityFilterChain代替了SpringBoot自动配置的SecurityFilterChain,那么SpringBoot自动配置的SecurityFilterChain是怎么样的呢?

自动配置类为org.springframework.boot.autoconfigure.security.servlet.SpringBootWebSecurityConfiguration

    @Bean
    @Order(SecurityProperties.BASIC_AUTH_ORDER)
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated();
        http.formLogin();
        http.httpBasic();
        return http.build();
    }

看起来和我们自定义的配置相差无几,只是多了一个http.httpBasic(),该方法启用BasicAuthenticationFilter

这是浏览器自带的登录方式,也是输入用户名、密码,界面是这样的

但界面不可自定义,基本不使用这种登录方式。要学的东西够多了,就不用管这个了。

默认启用的Filter

前面说过,所有的功能都是通过Filter实现,确实我们也亲手定义了两个Filter,而且都起到了效果。

但是细心的同学肯定发现了,在登录页面我们加上了一个隐藏字段,用于防范CSRF攻击,如果不加这个字段,就无法提交请求。那么防范CSRF攻击的功能是在哪加上去的呢?我们说了所有功能都通过Filter实现,可我们只加了两个Filter呀。

实际上SpringBoot会把常用的Filter先加好,我们再加上一些自己需要的Filter。好吧,居然这么玩。我不能连自己用了哪些Filter都不知道吧。

org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration

    @Bean(HTTPSECURITY_BEAN_NAME)
    @Scope("prototype")
    HttpSecurity httpSecurity() throws Exception {
        WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder = new WebSecurityConfigurerAdapter.LazyPasswordEncoder(
                this.context);
        AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(
                this.objectPostProcessor, passwordEncoder);
        authenticationBuilder.parentAuthenticationManager(authenticationManager());
        HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
        // @formatter:off
        http
            .csrf(withDefaults())
            .addFilter(new WebAsyncManagerIntegrationFilter())
            .exceptionHandling(withDefaults())
            .headers(withDefaults())
            .sessionManagement(withDefaults())
            .securityContext(withDefaults())
            .requestCache(withDefaults())
            .anonymous(withDefaults())
            .servletApi(withDefaults())
            .apply(new DefaultLoginPageConfigurer<>());
        http.logout(withDefaults());
        // @formatter:on
        applyDefaultConfigurers(http);
        return http;
    }

我们看到了里面包含.csrf(withDefaults())代码,就在这里加上了CsrfFilter。其它还默认启用了很多过滤器,咱也看不懂,咱也不敢问。不敢问就对了,越问越糊涂,用到哪个再了解哪个。

目前也就CSRF对我们有影响。SpringBoot这么霸道的加上这些东西,就不问问我们同意不同意?如果我们不需要CSRF怎么办呢?

实际上默认启用的配置一般都是需要的,如果不需要,我们可以关闭或者调整Filter。如:

// 关闭CSRF功能
http.csrf().disable();

SpringBoot自动配置类

仅仅是做了一个自定义登录的页面,就已经山路十八弯了,Spring Security你可以的。是时候告诉我,你到底干了些什么。

SpringBoot自动配置类主要包括:SecurityAutoConfigurationUserDetailsServiceAutoConfiguration

SecurityAutoConfiguration引入了SpringBootWebSecurityConfiguration,并启用了@EnableWebSecurity

@EnableWebSecurity引入了WebSecurityConfigurationHttpSecurityConfiguration,并启用了@EnableGlobalAuthentication

@EnableGlobalAuthentication引入了AuthenticationConfiguration

总结

说Spring Security复杂一点都不冤枉,但架不住功能确实强大,熟练掌握之后更是如鱼得水。

咨询
交流群
电话