注:本文大体上是对Spring Security官方文档(https://spring.io/guides/gs/securing-web/)的一次编码实践。
开发环境操作系统:Ubuntu 20.04开发工具:IntelliJ IDEA 2021.3.1 (Community Edition)JDK:
➜ ~ java -version java version "17.0.1" 2021-10-19 LTS Java(TM) SE Runtime Environment (build 17.0.1+12-LTS-39) Java HotSpot(TM) 64-Bit Server VM (build 17.0.1+12-LTS-39, mixed mode, sharing)
Maven:
➜ ~ mvn -v Apache Maven 3.6.3 Maven home: /usr/share/maven Java version: 17.0.1, vendor: Oracle Corporation, runtime: /home/ding/Downloads/jdk-17.0.1 Default locale: en_US, platform encoding: UTF-8 OS name: "linux", version: "5.11.0-44-generic", arch: "amd64", family: "unix"“不安全”的程序
在这一部分,我们首先创建一个不安全的web application。
打开 https://start.spring.io/ ,在右侧的“Dependency”中,添加 Spring Web 和 Thymeleaf 依赖,如下图所示:
设置完成后,下载zip包,解压并用IntelliJ IDEA打开。
首先在 src/main/resources/templates 目录下添加2个非常简单的HTML文件:
home.html
Spring Security Example
Welcome!
Click here to see a greeting.
hello.html
Hello World!
Hello world!
在 src/main/java/com/example/securingweb 目录下添加 MvcConfig.java 文件:
package com.example.securingweb;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/").setViewName("home");
registry.addViewController("/hello").setViewName("hello");
registry.addViewController("/login").setViewName("login");
}
}
注:第4个controller /login 所对应的view login 此刻还没有实现,先不用管它。
现在我们可以先运行一下这个“不安全”的程序,来看看效果。
访问 / 和 /home 的效果如下:
访问 /hello 的效果如下:
当然,在 home 页面点击 here 链接也能到达hello页面。
“安全”的程序本例中,我们假设 hello 页面是受保护的,只有授权用户才能访问。也就是说,用户必须登录,才能访问该页面。
打开 pom.xml 并添加如下依赖:
org.springframework.boot spring-boot-starter-security org.springframework.security spring-security-test test
接下来,在 src/main/java/com/example/securingweb 目录添加 WebSecurityConfig.java 文件如下:
package com.example.securingweb;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
@Bean
@Override
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
现在创建缺失的 login 页面。同样在 src/main/resources/templates 目录下,创建 login.html 文件如下:
Spring Security Example
Invalid username and password.
You have been logged out.
并将 hello.html 文件修改如下:
Hello World!
Hello [[${#httpServletRequest.remoteUser}]]!
再次运行程序。
首先访问主页,并没有任何变化:
但是在点击 here 链接时,并没有跳转到 hello 页面,而是跳转到了 login 页面:
如果输入错误的用户名密码,登录将会失败:
只有输入正确的用户名密码(也就是 user 和 password ,定义在 WebSecurityConfig.java 配置类里面),才会跳转到 hello 页面:
先不要点击 Sign Out 按钮,重新打开主页,再次点击 here 链接,这次就能直接跳转到 hello 页面了,这是因为用户已经登录了。
点击 Sign Out 按钮,会跳转到 login 页面:
说明WebSecurityConfig 类被 @EnableWebSecurity 注解所修饰,该注解开启了Spring Security的web security支持,并提供了Spring MVC集成。
WebSecurityConfig 类扩展了 WebSecurityConfigurerAdapter 类,并override了一些方法。
configure() 方法里定义了哪些URL path将会受到保护。本例中, / 和 /home 不需要authorization,而其它请求需要authorization( /login 是被显式指定为登录页面的,它也不需要authorization)。当用户登录成功之后,将会重定向到之前的请求上。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
注: WebSecurityConfigurerAdapter 类有3种重载的 configure() 方法:
- configure(AuthenticationManagerBuilder auth) - configure(HttpSecurity http) - configure(WebSecurity web)
userDetailsService() 方法在本例中创建了一个in-memory的user,其用户名,密码,角色都已预定义好。
@Bean
@Override
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
我们并没有配置 logout 的handler,跳转页面等,应该是使用了Spring Security的默认配置。有待研究。 参考
https://spring.io/guides/gs/securing-web/



