参考:陈木鑫老师的《Spring Security 实战》

创建 spring boot 项目

通过 Intellij IDEA 创建 Spring Boot 项目的方式有许多种,其中最简单的方式就是使用 Spring Initializr
工具。
Spring Initializr 允许我们提前选定一些常用的项目依赖,此处我们选择 Security 作为构建 Spring
Security 项目的最小依赖,选择 Web 作为 Spring Boot 构建 Web 应用的核心依赖。
(一)创建简单的Spring security 项目 - 图1
Next :
(一)创建简单的Spring security 项目 - 图2
Next:
(一)创建简单的Spring security 项目 - 图3
创建项目的目录结构:
(一)创建简单的Spring security 项目 - 图4

maven 引用

在自动构建的 Spring Security 项目中,Spring Initializr 为我们引入了以下依赖:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-security</artifactId>
  4. </dependency>

我们点开spring-boot-starter-security可以看到,其包含了以下依赖:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter</artifactId>
  4. <version>2.3.1.RELEASE</version>
  5. <scope>compile</scope>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.springframework</groupId>
  9. <artifactId>spring-aop</artifactId>
  10. <scope>compile</scope>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.springframework.security</groupId>
  14. <artifactId>spring-security-config</artifactId>
  15. <scope>compile</scope>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.springframework.security</groupId>
  19. <artifactId>spring-security-web</artifactId>
  20. <scope>compile</scope>
  21. </dependency>

其中 spring-security-webspring-security-config两个核心模块,正是官方建议引入的 Spring Security 最小依赖。

声明 controller

在项目中声明一个测试路由TestController

  1. package com.haan.springsecuritydemo;
  2. import org.springframework.web.bind.annotation.GetMapping;
  3. import org.springframework.web.bind.annotation.RestController;
  4. @RestController
  5. public class TestController {
  6. @GetMapping
  7. public String hello(){
  8. return "Hello Spring Security!";
  9. }
  10. }

(一)创建简单的Spring security 项目 - 图5

运行SpringsecuritydemoApplication

运行SpringsecuritydemoApplication, 默认启动 8080 端口,打开浏览器,访问localhost:8080, 我们发现页面跳转到了localhost:8080/login:
(一)创建简单的Spring security 项目 - 图6
在引入 Spring Security 项目之后,虽然没有进行任何相关的配置或编码,但 Spring Security 有一个默认的运行状态,要求在经过表单基本认证后才能访问对应的 URL 资源,其默认使用的用户名为 user ,密码则是动态生成并打印到控制台的一串随机码。翻看控制台的打印信息:
(一)创建简单的Spring security 项目 - 图7
输入用户名和密码后,单击 “登录” 按钮即可成功访问:
(一)创建简单的Spring security 项目 - 图8

修改登录信息

基本表单认证中,用户名和密码都是可以配置的,最常见的就是在 resources 下的application配置文件中修改:

  1. spring.security.user.name=user02
  2. spring.security.user.password=aaaaaa

重新启动程序,发现控制台不再打印默认密码串了,此时使用我们自定义的用户名和密码即可登录。

WebSecurityConfigurerAdapter

  1. protected void configure(HttpSecurity http) throws Exception {
  2. this.logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
  3. ((HttpSecurity)((HttpSecurity)((AuthorizedUrl)http.authorizeRequests().anyRequest()).authenticated().and()).formLogin().and()).httpBasic();
  4. }

可以看到 WebSecurityConfigurerAdapter 已经默认声明了一些安全特性:

  • 验证所有请求。
  • 允许用户使用表单登录进行身份验证(Spring Security 提供了一个简单的表单登录页面)。
  • 允许用户使用 HTTP 基本认证。

spring boot 默认定义了DefaultConfigurerAdapter , 由@ConditionalOnMissingBean可知当没有其他WebSecurityConfigurerAdapter被定义时,将使用DefaultConfigurerAdapter

  1. @Configuration(
  2. proxyBeanMethods = false
  3. )
  4. @ConditionalOnClass({WebSecurityConfigurerAdapter.class})
  5. @ConditionalOnMissingBean({WebSecurityConfigurerAdapter.class})
  6. @ConditionalOnWebApplication(
  7. type = Type.SERVLET
  8. )
  9. public class SpringBootWebSecurityConfiguration {
  10. public SpringBootWebSecurityConfiguration() {
  11. }
  12. @Configuration(
  13. proxyBeanMethods = false
  14. )
  15. @Order(2147483642)
  16. static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {
  17. DefaultConfigurerAdapter() {
  18. }
  19. }
  20. }

自定义表单登录页

Spring boot 提供了WebSecurityConfigurerAdapter 的默认实现DefaultConfigurerAdapter,能够提供基本表单登录认证。

虽然自动生成的表单登录页可以方便、快速地启动,但是大多数应用程序更希望提供自己的表单登录页,此时就需要我们提供自己的WebSecurityConfigurerAdapter来代替DefaultConfigurerAdapter, 覆写WebSecurityConfigurerAdapterconfigure方法:

  1. @EnableWebSecurity
  2. public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
  3. @Override
  4. protected void configure(HttpSecurity http) throws Exception {
  5. http.authorizeRequests()
  6. .anyRequest().authenticated()
  7. .and()
  8. .formLogin()
  9. .loginPage("/myLogin.html") //指明登录页面
  10. .permitAll() //指明登录页允许所有进行访问
  11. .and()
  12. .csrf().disable();
  13. }
  14. }
  • authorizeRequests()方法实际上返回了一个 URL 拦截注册器,我们可以调用它提供的anyRequest()antMatchers()regexMatchers()等方法来匹配系统的 URL,并为其指定安全策略。
  • formLogin()方法和httpBasic()方法都声明了需要 Spring Security 提供的表单认证方式,分别返回对应的配置器。其中formLogin().loginPage("/myLogin.html")指定自定义的登录
    /myLogin.html,同时,Spring Security 会用/myLogin.html注册一个POST路由,用于接收登录请求。
  • csrf()方法是 Spring Security 提供的跨站请求伪造防护功能,当我们继承WebSecurityConfigurer Adapter时会默认开启 csrf()方法。

访问localhost:8080 , 我们发现,页面就跳转到了localhost:8080/myLogin.html, 由于我们静态文件中并没有myLogin.html 文件,所以提示了一个 404 的white page:
(一)创建简单的Spring security 项目 - 图9
我们在resources/static 文件夹下创建页面myLogin.html:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Hello Security!</title>
  6. </head>
  7. <body>
  8. <h3>登录页</h3>
  9. <form action="/myLogin.html" method="post">
  10. <input type="text" > <br/>
  11. <input type="text" > <br/>
  12. <input type="submit" value="login">
  13. </form>
  14. </body>
  15. </html>

重启服务,再次访问localhost:8080:
(一)创建简单的Spring security 项目 - 图10
输入上面的application.properties中配置的用户登录账户和密码进行登录,登陆成功:
(一)创建简单的Spring security 项目 - 图11

其他表单配置项

指定登录处理 URL

在自定义表单登录页之后,处理登录请求的URL也会相应改变, 默认情况下,
如果只配置loginPage而不配置loginProcessingUrl的话那么loginProcessingUrl默认就是loginPage,如果需要自定义登录请求的URL, 需要配置loginProcessingUrl:

重启登录,我们发现中间访问了localhost:8080/myLogin
(一)创建简单的Spring security 项目 - 图12

补充:loginPageloginProcessingUrl

  • 两者都不配置:默认都是/login
  • 两者都配置:按自己的来
  • 只配置loginProcessingUrlloginPage默认/login
  • 只配置loginPage: loginProcessingUrl默认就是loginPage

设置登录成功处理

此时,有些读者可能会有疑问,因为按照惯例,在发送登录请求并认证成功之后,页面会跳转回原访问页。在某些系统中的确是跳转回原访问页的,但在部分前后端完全分离、仅靠 JSON 完成所有交互的系统中,一般会在登录时返回一段 JSON 数据,告知前端成功登录成功与否,由前端决定如何处
理后续逻辑,而非由服务器主动执行页面跳转。这在 Spring Security 中同样可以实现。

表单登录配置模块提供了successHandler()failureHandler()两个方法,分别处理登录成功和登录失败的逻辑。

  • successHandler()方法带有一个Authentication参数,携带当前登录用户名及其角色等信息;
  • failureHandler()方法携带一个AuthenticationException异常参数。具体处理方式需按照系统的情况自定义。
  1. @EnableWebSecurity
  2. public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
  3. @Override
  4. protected void configure(HttpSecurity http) throws Exception {
  5. http.authorizeRequests()
  6. .anyRequest().authenticated()
  7. .and()
  8. .formLogin()
  9. .loginPage("/myLogin.html") //指明登录页面
  10. .loginProcessingUrl("/myLogin") //指明处理登陆的URL路径,即登陆表单提交请求
  11. .successHandler(new AuthenticationSuccessHandler() { // 设置登录成功的处理器
  12. @Override
  13. public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
  14. PrintWriter responseWriter = httpServletResponse.getWriter();
  15. String name = authentication.getName();
  16. responseWriter.write(name+" login success!");
  17. }
  18. })
  19. .failureHandler(new AuthenticationFailureHandler() { // 设置登录失败的处理器
  20. @Override
  21. public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
  22. PrintWriter responseWriter = httpServletResponse.getWriter();
  23. responseWriter.write("login error!");
  24. }
  25. })
  26. .permitAll() //指明登录页允许所有进行访问
  27. .and()
  28. .csrf().disable();
  29. }
  30. }

正确的账号密码:
(一)创建简单的Spring security 项目 - 图13
错误的账号密码:
(一)创建简单的Spring security 项目 - 图14