Spring Security 是一个强大且可扩展的框架,用于保护 Java 应用程序,尤其是基于 Spring 的应用。它提供了身份验证(验证用户身份)、授权(管理用户权限)和防护机制(如 CSRF 保护和防止会话劫持)等功能。
Spring Security 允许开发者通过灵活的配置实现安全控制,确保应用程序的数据和资源安全。通过与其他 Spring 生态系统的无缝集成,Spring Security 成为构建安全应用的理想选择。
核心概念
- 身份验证 (Authentication): 验证用户的身份(例如,用户名/密码)。
- 授权 (Authorization): 确定用户是否有权限访问特定资源。
- 安全上下文 (Security Context): 存储已认证用户的详细信息,应用程序中可以访问。
1、准备工作
1.1 引入依赖
当我们引入 security
依赖后,访问需要授权的 url 时,会重定向到 login
页面(security 自己创建的),login
页面需要账号密码,账号默认是 user
, 密码是随机的字符串,在spring项目的输出信息中
-
spring-boot-starter-security
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>3.3.4</version> </dependency>
-
jjwt-api
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.12.6</version> </dependency>
-
jjwt-impl
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.12.6</version> <scope>runtime</scope> </dependency>
-
jjwt-jackson
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.12.6</version> <scope>runtime</scope> </dependency>
一般我们会创建一个 SecurityConfig
类,来管理我们所有与 security
相关的配置。(我们讲的是 security 5.7 版本之后的配置方法,之前的方法跟现在不太一样)
@Configuration
@EnableWebSecurity // 该注解启用 Spring Security 的 web 安全功能。
public class SecurityConfig {
}
下面的都要写到 SecurityConfig
类中
1.2 用户认证的配置
基于内存的用户认证
通过 createUser
, manager 把用户配置的账号密码添加到spring的内存中, InMemoryUserDetailsManager
类中有一个 loadUserByUsername
的方法通过账号(username)从内存中获取我们配置的账号密码,之后调用其他方法来判断前端用户输入的密码和内存中的密码是否匹配。
@Bean
public UserDetailsService userDetailsService() {
// 创建基于内存的用户信息管理器
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(
// 创建UserDetails对象,用于管理用户名、用户密码、用户角色、用户权限等内容
User.withDefaultPasswordEncoder().username("user").password("user123").roles("USER").build()
);
// 如果自己配置的有账号密码, 那么上面讲的 user 和 随机字符串 的默认密码就不能用了
return manager;
}
当我们点进 InMemoryUserDetailsManager
中 可以发现它实现了 UserDetailsManager
和 UserDetailsPasswordService
接口,其中 UserDetailsManager
接口继承的 UserDetailsService
接口中就有 loadUserByUsername
方法
基于数据库的用户认证
上面讲到,spring security 是通过 loadUserByUsername
方法来获取 User
并用这个 User
来判断用户输入的密码是否正确。所以我们只需要继承 UserDetailsService
接口并重写 loadUserByUsername
方法即可
下面的样例我用的 mybatis-plus 来查询数据库中的 user
, 然后通过当前查询到的 user
返回特定的 UserDetails
对象
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
queryWrapper.eq("username", username); // 这里不止可以用username,你可以自定义,主要根据你自己写的查询逻辑
User user = userMapper.selectOne(queryWrapper);
if (user == null) {
throw new UsernameNotFoundException(username);
}
return new UserDetailsImpl(user); // UserDetailsImpl 是我们实现的类
}
}
UserDetailsImpl
是实现了 UserDetails
接口的类。UserDetails
接口是 Spring Security 身份验证机制的基础,通过实现该接口,开发者可以定义自己的用户模型,并提供用户相关的信息,以便进行身份验证和权限检查。
@Data
@AllArgsConstructor
@NoArgsConstructor // 这三个注解可以帮我们自动生成 get、set、有参、无参构造函数
public class UserDetailsImpl implements UserDetails {
private User user; // 通过有参构造函数填充赋值的
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of();
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
// 检查账户是否 没过期。
return true;
}
@Override
public boolean isAccountNonLocked() {
// 检查账户是否 没有被锁定。
return true;
}
@Override
public boolean isCredentialsNonExpired() {
//检查凭据(密码)是否 没过期。
return true;
}
@Override
public boolean isEnabled() {
// 检查账户是否启用。
return true;
}
// 这个方法是 @Data注解 会自动帮我们生成,用来获取 loadUserByUsername 中最后我们返回的创建UserDetailsImpl对象时传入的User。
// 如果你的字段包含 username和password 的话可以用强制类型转换, 把 UserDetailsImpl 转换成 User。如果不能强制类型转换的话就需要用到这个方法了
public User getUser() {
return user;
}
}
1.3 基本的配置
下面这个是 security
的默认配置。我们可以修改并把它加到spring容器中,完成我们特定的需求。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 开启授权保护
.authorizeHttpRequests(authorize -> authorize
// 不需要认证的地址有哪些
.requestMatchers("/blog/**", "/public/**", "/about").permitAll()