看到上一章的登录页面,大部分人的第一反应就是,我要用自己的登录页面怎么办?
之前全部使用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
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
之前介绍过,Spring Security自带了大量的Filter,用于实现各种不同的功能。此处就用到了两个:
前面提到,我们自定义的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,而且都起到了效果。
但是细心的同学肯定发现了,在登录页面我们加上了一个隐藏字段,用于防范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();
仅仅是做了一个自定义登录的页面,就已经山路十八弯了,Spring Security你可以的。是时候告诉我,你到底干了些什么。
SpringBoot自动配置类主要包括:SecurityAutoConfiguration
和UserDetailsServiceAutoConfiguration
。
SecurityAutoConfiguration
引入了SpringBootWebSecurityConfiguration
,并启用了@EnableWebSecurity
。
@EnableWebSecurity
引入了WebSecurityConfiguration
和HttpSecurityConfiguration
,并启用了@EnableGlobalAuthentication
。
@EnableGlobalAuthentication
引入了AuthenticationConfiguration
。
说Spring Security复杂一点都不冤枉,但架不住功能确实强大,熟练掌握之后更是如鱼得水。