(1)初步配置自定义表单登录页
首先覆盖config方法
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .loginPage("/myLogin.html") // 使登录页不设限访问 .permitAll() .and() .csrf().disable(); } }
(2)认识HttpSecurity
HttpSecurity 实际上对应了 =Spring Security 命名空间配置方式中 XML 文件内的标签,允许我们为特定的 HTTP 请求配置安全策略。
在 XML 文件中,声明大量配置早已司空见惯;但在 Java 配置中,按照传统的方式,我们需要这样来调用。
HttpSecurity 首先被设计为链式调用,在执行每个方法后,都会返回一个预期的上下文,便于连续调用。我们不需要关心每个方法究竟返回了什么、如何进行下一个配置等细节。
HttpSecurity 提供了很多配置相关的方法,分别对应命名空间配置中的子标签
authorizeRequests()方法实际上返回了一个 URL 拦截注册器,我们可以调用它提供的 anyanyRequest()、antMatchers()和 regexMatchers()等方法来匹配系统的 URL,并为其指定安全策略。
formLogin()方法和 httpBasic()方法都声明了需要 Spring Security 提供的表单认证方式,分别返回对应的配置器。其中,formLogin().loginPage(「/myLogin.html」)指定自定义的登录页/myLogin.html,同时,Spring Security 会用/myLogin.html 注册一个 POST 路由,用于接收登录请求。
csrf()方法是 Spring Security 提供的跨站请求伪造防护功能,当我们继承 WebSecurityConfigurer Adapter 时会默认开启 csrf()方法。关于 csrf()方法的更多内容会在后面的章节专门探讨,以使测试进程更加顺利。
重新启动服务后再次访问 localhost:8080,页面会自动跳转到 localhost:8080/myLogin.html。由于/myLogin.html 无法定位到页面资源,所以会显示一个 404 页面
(3)编写表单登录页
在表单登录页中,仅有一个表单,用户名和密码分别为 username 和 password,并以 POST 的方式提交到/myLogin.html。
我们将其命名为 myLogin.html,放置在 resources/static/下。重启服务,再次访问 localhost:8080,即可看到自定义的表单登录页。
输入正确的用户名和密码后,单击「Login」按钮,即可成功跳转。
(4)其他表单配置项
在自定义表单登录页之后,处理登录请求的 URL 也会相应改变。如何自定义 URL 呢?很简单,Spring Security 在表单定制里提供了相应的支持。
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .loginPage("/myLogin.html") // 指定处理登录请求的路径 .loginProcessingUrl("/login") // 使登录页不设限访问 .permitAll() .and() .csrf().disable(); } }
此时,有些同学可能会有疑问,因为按照惯例,在发送登录请求并认证成功之后,页面会跳转回原访问页。在某些系统中的确是跳转回原访问页的,但在部分前后端完全分离、仅靠 JSON 完成所有交互的系统中,一般会在登录时返回一段 JSON 数据,告知前端成功登录成功与否,由前端决定如何处理后续逻辑,而非由服务器主动执行页面跳转。这在 Spring Security 中同样可以实现。
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .loginPage("/myLogin.html") // 指定处理登录请求的路径 .loginProcessingUrl("/login") .successHandler(new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { response.setContentType("application/json;charset=utf-8"); PrintWriter out = response.getWriter(); out.write("欢迎登录系统!"); } }) .failureHandler(new AuthenticationFailureHandler() { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { response.setStatus(401); PrintWriter out = response.getWriter(); out.write("登录失败!"); } }) // 使登录页不设限访问 .permitAll() .and() .csrf().disable(); } }
表单登录配置模块提供了 successHandler()和 failureHandler()两个方法,分别处理登录成功和登录失败的逻辑。其中,successHandler()方法带有一个 Authentication 参数,携带当前登录用户名及其角色等信息;而 failureHandler()方法携带一个 AuthenticationException 异常参数。具体处理方式需按照系统的情况自定义。
在形式上,我们确实使用了 Spring Security 的表单认证功能,并且自定义了表单登录页。但实际上,这还远远不够。例如,在实际系统中,我们正常登录时使用的用户名和密码都来自数据库,这里却都写在配置上。更进一步,我们可以对每个登录用户都设定详细的权限,而并非一个通用角色。这些内容将在后面章节讲解。