Spring Security 使用教程:从入门到实战
在Web应用开发中,安全永远是不可忽视的环节。Spring Security作为Spring生态中成熟的安全框架,提供了全面的认证(Authentication)和授权(Authorization)解决方案。本文将从基础到进阶,带你一步步掌握Spring Security的核心用法,构建安全可靠的Java Web应用。(对的,这是Spring Security的入门篇)
一、Spring Security 核心概念
在开始实战前,先理解几个核心概念,有助于后续学习:
认证(Authentication):验证用户身份的过程,比如登录时校验用户名和密码是否正确。
授权(Authorization):在认证通过后,判断用户是否有权限执行某个操作(如访问特定接口、修改数据)。
SecurityContext:存储当前认证用户的信息,通过SecurityContextHolder
可以随时获取。
UserDetails:封装用户信息的接口,包含用户名、密码、权限等核心字段。
AuthenticationManager:认证的核心管理器,负责调度AuthenticationProvider
完成认证。
二、环境搭建:快速集成Spring Security
本文基于Spring Boot 3.x进行演示,Spring Boot已为Spring Security提供了自动配置,集成过程非常简单。
2.1 引入依赖
在pom.xml
(Maven)或build.gradle
(Gradle)中添加Spring Security依赖:
查看代码
1 2 3 4 5 6
| <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>7.0.0-M2</version> </dependency>
|
2.2 启动应用,体验默认安全配置
添加依赖后,直接启动Spring Boot应用,Spring Security会自动生效,默认做了以下安全配置:
所有接口默认需要认证才能访问。
生成一个默认用户,用户名是user
,密码会在控制台输出(格式如:Using generated security password: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
)。
提供一个默认的登录页面(访问任意接口会自动跳转)。
测试:访问http://localhost:8080/hello
(需先编写一个简单的Hello接口),会跳转至登录页,输入默认用户名和密码即可访问。
三、自定义认证:从默认到数据库
默认配置仅用于测试,实际项目中需要对接数据库用户,下面实现自定义认证逻辑。
3.1 编写配置类
创建SecurityConfig
类,继承WebSecurityConfigurerAdapter
(Spring Boot 3.x中已 deprecated,推荐使用函数式风格配置):
查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| 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.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain;
@Configuration @EnableWebSecurity public class SecurityConfig {
private final UserDetailsService userDetailsService;
public SecurityConfig(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; }
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth .requestMatchers("/login").permitAll() .anyRequest().authenticated() ) .formLogin(form -> form .loginPage("/custom-login") .loginProcessingUrl("/do-login") .defaultSuccessUrl("/index", true) .failureUrl("/custom-login?error=true") ) .logout(logout -> logout .logoutUrl("/logout") .logoutSuccessUrl("/custom-login?logout=true") .invalidateHttpSession(true) );
return http.build(); } }
|
3.2 实现UserDetailsService
UserDetailsService
负责从数据库加载用户信息,我们需要实现它来对接自己的用户表:
查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service;
@Service public class CustomUserDetailsService implements UserDetailsService {
private final UserDao userDao;
public CustomUserDetailsService(UserDao userDao) { this.userDao = userDao; }
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserEntity user = userDao.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("用户不存在"); }
return User.withUsername(user.getUsername()) .password(user.getPassword()) .roles(user.getRoles().split(",")) .build(); } }
|
注意:数据库中存储的密码必须是通过`BCryptPasswordEncoder`加密后的字符串,不能存储明文。可以通过`passwordEncoder.encode("123456")`生成加密密码。
四、授权控制:基于角色和权限
认证通过后,需要对不同用户授予不同权限,Spring Security支持多种授权方式。
4.1 基于角色的URL授权
在securityFilterChain
中通过hasRole
或hasAnyRole
配置URL的角色权限:
查看代码
1 2 3 4 5 6 7
| .authorizeHttpRequests(auth -> auth .requestMatchers("/admin/**").hasRole("ADMIN") .requestMatchers("/user/**").hasAnyRole("ADMIN", "USER") .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() )
|
4.2 基于注解的方法级授权
在Controller方法上使用注解控制权限,需先开启全局方法安全:
查看代码
1 2 3 4 5
| @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class MethodSecurityConfig { }
|
然后在Controller方法上使用注解:
查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @RestController @RequestMapping("/admin") public class AdminController {
@PreAuthorize("hasRole('ADMIN')") @GetMapping("/user-manage") public String manageUser() { return "用户管理页面"; }
@PreAuthorize("hasAuthority('DELETE')") @DeleteMapping("/delete/{id}") public String deleteUser(@PathVariable Long id) { return "删除用户:" + id; } }
|
五、进阶功能:记住我与JWT
除了基础的认证授权,Spring Security还支持“记住我”、JWT令牌等高级功能。
5.1 实现“记住我”功能
只需在securityFilterChain
中添加rememberMe
配置:
查看代码
1 2 3 4 5 6
| .rememberMe(remember -> remember .rememberMeParameter("remember-me") .tokenValiditySeconds(60 * 60 * 24 * 7) .userDetailsService(userDetailsService) )
|
然后在登录表单中添加一个名为remember-me
的复选框即可。
5.2 集成JWT实现无状态认证
对于前后端分离项目,通常使用JWT(JSON Web Token)实现无状态认证,核心步骤如下:
引入JWT依赖:添加jjwt-api
、jjwt-impl
、jjwt-jackson
依赖。
编写JWT工具类:实现Token的生成、解析、验证逻辑。
自定义JwtAuthenticationFilter:从请求头中获取Token,解析后完成认证。
配置SecurityFilterChain:关闭session,添加JWT过滤器,放行登录接口等。
由于JWT集成步骤较多,后续会单独写一篇实战文章,感兴趣的可以关注。
六、常见问题与排查
密码加密失败:确保数据库中存储的是加密后的密码,且配置了正确的PasswordEncoder
。
接口授权不生效:检查URL匹配规则的顺序(具体规则要放在通用规则前面),以及角色/权限名称是否正确(hasRole
会自动添加ROLE_
前缀,hasAuthority
需要完整名称)。
CSRF问题:前后端分离项目中,若关闭CSRF,需确保请求头中没有携带CSRF Token;若开启,需在前端请求中添加Token。
七、总结
本文介绍了Spring Security的核心概念、环境搭建、自定义认证、授权控制及进阶功能,涵盖了大部分实际项目中的常用场景。Spring Security功能强大且灵活,掌握它能有效提升应用的安全性。建议结合实际项目多动手实践,深入理解其核心原理(如过滤器链、认证流程等),以便应对更复杂的安全需求。