SpringBoot整合Spring Security实现基础认证与授权
作者:希望永不加班
在后端开发领域,认证(Authentication) 和授权(Authorization) 是系统安全的核心基石。简单来说:认证是确认「你是谁」,授权是决定「你能做什么」。
Spring Security 作为 Spring 生态官方推荐的安全框架,凭借高度模块化、可扩展、无缝整合 SpringBoot 等特性,成为 Java 后端安全开发的首选方案。
一、前置知识与环境说明
1.1 核心概念
1. 认证(Authentication)
验证用户身份是否合法,比如输入账号密码登录、短信验证码登录,都属于认证流程。
2. 授权(Authorization)
认证通过后,根据用户的角色/权限,控制用户能访问哪些接口、页面,比如管理员能访问所有接口,普通用户只能访问个人接口。
3. Spring Security
基于 Spring AOP 和 Servlet 过滤器实现的安全框架,默认提供了表单登录、注销、会话管理、csrf 防护、权限控制等全套安全能力。
1.2 开发环境
- JDK 8+
- SpringBoot 2.7.x(稳定版,兼容绝大多数企业项目)
- Maven 3.6+
- IDEA 开发工具
- Lombok(简化代码)
二、项目初始化与依赖引入
2.1 创建 SpringBoot 项目
打开 IDEA,创建一个标准的 SpringBoot 项目,项目名称:springboot-security-demo。
2.2 核心依赖(pom.xml)
直接复制以下依赖,无需额外版本号,SpringBoot 自动管理版本兼容:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>springboot-security-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-security-demo</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- SpringBoot Web 核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security 安全框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Lombok 简化代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>2.3 启动项目,体验默认安全机制
直接启动 SpringBoot 项目,控制台会打印一段默认密码:
Using generated security password: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
默认用户名:user
默认密码:控制台打印的随机字符串
访问测试接口:http://localhost:8080
会自动跳转到 Spring Security 提供的默认登录页面,输入账号密码即可登录成功。
这就是 Spring Security 的自动配置机制,零代码实现基础登录保护,但实际项目中,我们必须自定义登录逻辑、用户信息、权限规则。
三、Spring Security 核心架构理解
在动手写代码前,先搞懂核心组件,后续配置会一目了然:
1. SecurityContextHolder
安全上下文持有者,存储当前登录用户的信息(Authentication 对象)。
2. Authentication
认证对象,包含用户身份信息、权限信息、认证状态。
3. UserDetailsService
核心接口,用于加载用户信息(我们必须实现它,自定义查询用户逻辑)。
4. PasswordEncoder
密码加密器,Spring Security 强制要求密码加密,不能明文存储。
5. SecurityFilterChain
安全过滤器链,所有认证、授权、防护逻辑都通过过滤器链执行。
6. @PreAuthorize
权限注解,用于控制方法/接口的访问权限。
四、内存用户认证
第一步,我们先使用内存用户实现登录认证,不连接数据库,快速理解配置逻辑。
4.1 创建 Security 配置类
创建配置类:com.example.config.SecurityConfig
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
/**
* Spring Security 核心配置类
* @EnableWebSecurity 开启 Spring Security web 安全支持
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig {
/**
* 密码加密器
* 推荐使用 BCrypt 算法,自动加盐,不可逆加密
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 内存用户信息管理
* 定义两个用户:admin(管理员)、user(普通用户)
*/
@Bean
public UserDetailsService userDetailsService() {
// 管理员用户
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder().encode("123456")) // 密码加密
.roles("ADMIN") // 角色
.build();
// 普通用户
UserDetails user = User.builder()
.username("user")
.password(passwordEncoder().encode("123456"))
.roles("USER")
.build();
// 内存存储用户信息
return new InMemoryUserDetailsManager(admin, user);
}
}4.2 配置说明
1. @EnableWebSecurity:开启 Spring Security 安全配置。
2. PasswordEncoder:必须配置,Spring Security 禁止明文密码,BCrypt 是官方推荐加密方式。
3. UserDetailsService:用户信息加载接口,这里使用内存实现。
4. .roles():为用户分配角色,用于后续授权控制。
4.3 测试登录
重启项目,访问:http://localhost:8080/login
- 管理员账号:admin / 123456
- 普通用户账号:user / 123456
登录成功后,即可访问所有接口(此时还未做权限控制)。
五、URL 权限控制
我们需要对不同接口设置不同的访问权限,比如:
- 公共接口:所有人可访问(登录、首页、静态资源)
- 用户接口:仅登录用户可访问
- 管理员接口:仅 ADMIN 角色可访问
5.1 修改 SecurityConfig,添加 HttpSecurity 配置
在 SecurityConfig 类中添加以下方法:
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.web.SecurityFilterChain;
/**
* SpringBoot 2.7+ 推荐使用 Lambda 风格配置
* 弃用 WebSecurityConfigurerAdapter
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// 1. 授权配置
.authorizeRequests()
// 公共接口:无需登录即可访问
.antMatchers("/", "/home", "/login", "/css/**", "/js/**").permitAll()
// 管理员接口:仅 ADMIN 角色可访问
.antMatchers("/admin/**").hasRole("ADMIN")
// 用户接口:仅 USER 角色可访问
.antMatchers("/user/**").hasRole("USER")
// 所有其他请求:必须登录才能访问
.anyRequest().authenticated()
.and()
// 2. 表单登录配置
.formLogin()
// 自定义登录页面(可选)
.loginPage("/login")
// 登录请求处理接口
.loginProcessingUrl("/doLogin")
// 登录成功默认跳转页面
.defaultSuccessUrl("/home")
// 登录失败跳转页面
.failureUrl("/login?error=true")
// 允许所有人访问登录相关接口
.permitAll()
.and()
// 3. 登出配置
.logout()
// 登出请求接口
.logoutUrl("/logout")
// 登出成功跳转页面
.logoutSuccessUrl("/login?logout=true")
// 登出后清除会话
.invalidateHttpSession(true)
// 清除认证信息
.clearAuthentication(true)
.permitAll()
.and()
// 4. 关闭 CSRF 防护(前后端分离项目建议关闭)
.csrf().disable();
return http.build();
}5.2 权限规则说明
1. permitAll():所有人可访问,无需登录。
2. hasRole("角色名"):必须拥有指定角色才能访问。
3. anyRequest().authenticated():除了上面配置的接口,其余都需要登录。
4. formLogin():开启表单登录,Spring Security 自动生成登录接口。
5. logout():开启登出功能,自动清理会话。
5.3 创建测试接口
创建控制器:com.example.controller.TestController
package com.example.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
// 公共接口
@GetMapping("/home")
public String home() {
return "首页:所有人可访问";
}
// 普通用户接口
@GetMapping("/user/info")
public String userInfo() {
return "用户信息:仅 USER 角色可访问";
}
// 管理员接口
@GetMapping("/admin/info")
public String adminInfo() {
return "管理员信息:仅 ADMIN 角色可访问";
}
// 测试接口
@GetMapping("/test")
public String test() {
return "测试接口:仅登录用户可访问";
}
}5.4 权限测试
1. 未登录:访问 /admin/info、/user/info、/test 都会自动跳转到登录页。
2. 登录 user 用户:
- 可访问:
/home、/user/info、/test - 不可访问:
/admin/info(403 无权限)
3. 登录 admin 用户:
- 完美实现基于角色的 URL 权限控制!
六、注解式权限控制
除了 URL 配置,Spring Security 还支持方法级注解权限,更灵活,适合分布式、微服务项目。
6.1 开启注解权限
在启动类添加 @EnableMethodSecurity(SpringBoot 2.7+)或 @EnableGlobalMethodSecurity(旧版):
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
// 开启方法级权限控制
@EnableMethodSecurity(prePostEnabled = true)
@SpringBootApplication
public class SpringbootSecurityDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootSecurityDemoApplication.class, args);
}
}6.2 常用权限注解
1. @PreAuthorize:方法执行前校验权限(最常用)
2. @PostAuthorize:方法执行后校验权限
3. @Secured:旧版角色注解
6.3 实战使用
@RestController
public class AuthController {
// 仅 ADMIN 角色可访问
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/auth/admin")
public String authAdmin() {
return "注解权限:管理员专属接口";
}
// 仅 USER 角色可访问
@PreAuthorize("hasRole('USER')")
@GetMapping("/auth/user")
public String authUser() {
return "注解权限:普通用户专属接口";
}
// 拥有 ADMIN 或 USER 角色均可访问
@PreAuthorize("hasAnyRole('ADMIN','USER')")
@GetMapping("/auth/common")
public String authCommon() {
return "注解权限:登录用户均可访问";
}
// 拥有指定权限标识(非角色)
@PreAuthorize("hasAuthority('user:add')")
@GetMapping("/auth/authority")
public String authAuthority() {
return "注解权限:拥有 user:add 权限可访问";
}
}注解式权限代码侵入性低、灵活度高,是企业项目主流用法。
七、数据库查询用户认证
内存用户仅适合测试,真实项目必须从数据库加载用户。
核心步骤:
1. 实现 UserDetailsService 接口
2. 重写 loadUserByUsername 方法
3. 从数据库查询用户信息+角色权限
4. 封装成 UserDetails 对象返回
7.1 引入数据库依赖(可选 MyBatis/MyBatis-Plus/JPA)
这里以 MyBatis-Plus 为例(简化数据库操作):
<!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- MyBatis-Plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency>
7.2 数据库表设计
-- 用户表
CREATE TABLE `sys_user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码(BCrypt加密)',
`status` int DEFAULT 1 COMMENT '状态 0-禁用 1-正常',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 角色表
CREATE TABLE `sys_role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`role_name` varchar(50) NOT NULL COMMENT '角色名称',
`role_code` varchar(50) NOT NULL COMMENT '角色编码 ADMIN/USER',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 用户角色关联表
CREATE TABLE `sys_user_role` (
`user_id` bigint NOT NULL,
`role_id` bigint NOT NULL,
PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 插入测试数据
INSERT INTO sys_user (username, password, status) VALUES
('admin', '$2a$10$wJ5i4y9y9Q9m0pOe1x2L3u4S5D6F7G8H9J0K1L2M3N4B5V6C7X8Z9A', 1),
('user', '$2a$10$wJ5i4y9y9Q9m0pOe1x2L3u4S5D6F7G8H9J0K1L2M3N4B5V6C7X8Z9A', 1);
INSERT INTO sys_role (role_name, role_code) VALUES
('管理员', 'ADMIN'),('普通用户', 'USER');
INSERT INTO sys_user_role (user_id, role_id) VALUES (1,1),(2,2);密码明文:123456,已使用 BCrypt 加密。
7.3 自定义 UserDetails 实现类
Spring Security 默认的 User 类不够用,我们自定义:
package com.example.entity;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
/**
* 自定义用户详情,实现 UserDetails 接口
*/
@Data
public class SecurityUser implements UserDetails {
// 用户ID
private Long id;
// 用户名
private String username;
// 密码
private String password;
// 状态
private Integer status;
// 角色列表
private List<String> roles;
/**
* 获取权限/角色集合
* Spring Security 要求角色必须以 ROLE_ 开头
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
// 账户是否未过期
@Override
public boolean isAccountNonExpired() {
return true;
}
// 账户是否未锁定
@Override
public boolean isAccountNonLocked() {
return true;
}
// 密码是否未过期
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 账户是否可用
@Override
public boolean isEnabled() {
return this.status == 1;
}
}7.4 实现 UserDetailsService 核心接口
package com.example.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.example.entity.SecurityUser;
import com.example.entity.SysUser;
import com.example.service.SysRoleService;
import com.example.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
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;
import java.util.List;
/**
* 自定义用户认证服务
* 核心:从数据库查询用户信息
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private SysUserService userService;
@Autowired
private SysRoleService roleService;
/**
* 根据用户名加载用户信息
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 根据用户名查询用户
SysUser user = userService.getOne(Wrappers.<SysUser>lambdaQuery()
.eq(SysUser::getUsername, username));
if (user == null) {
throw new UsernameNotFoundException("用户名不存在");
}
// 2. 查询用户角色
List<String> roles = roleService.getRolesByUserId(user.getId());
// 3. 封装成 SecurityUser 对象返回
SecurityUser securityUser = new SecurityUser();
securityUser.setId(user.getId());
securityUser.setUsername(user.getUsername());
securityUser.setPassword(user.getPassword());
securityUser.setStatus(user.getStatus());
securityUser.setRoles(roles);
return securityUser;
}
}7.5 配置类注入自定义 UserDetailsService
删除之前的内存用户配置,直接使用我们自定义的服务:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private UserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.userDetailsService(userDetailsService) // 注入自定义用户服务
.authorizeRequests()
.antMatchers("/", "/home", "/login").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("USER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/doLogin")
.defaultSuccessUrl("/home")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.permitAll()
.and()
.csrf().disable();
return http.build();
}
}至此,企业级数据库认证配置完成!
登录逻辑完全基于数据库查询,支持多角色、账户状态控制。
八、登录用户信息获取
在业务代码中,我们经常需要获取当前登录用户信息,Spring Security 提供了便捷工具:
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserInfoController {
/**
* 获取当前登录用户信息
*/
@GetMapping("/getUserInfo")
public String getUserInfo() {
// 1. 获取认证对象
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// 2. 判断是否登录
if (authentication == null || !authentication.isAuthenticated()) {
return "未登录";
}
// 3. 获取用户信息
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails) {
SecurityUser securityUser = (SecurityUser) principal;
return "当前登录用户:" + securityUser.getUsername()
+ ",角色:" + securityUser.getRoles();
}
return "获取用户信息失败";
}
}九、异常处理:401 未认证 / 403 无权限
Spring Security 默认异常页面不友好,我们可以自定义异常处理:
package com.example.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* 自定义安全异常处理
*/
@Configuration
public class SecurityExceptionConfig {
/**
* 401 未登录处理
*/
@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return (request, response, authException) -> {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
Map<String, Object> result = new HashMap<>();
result.put("code", 401);
result.put("msg", "未登录,请先登录");
new ObjectMapper().writeValue(response.getWriter(), result);
};
}
/**
* 403 无权限处理
*/
@Bean
public AccessDeniedHandler accessDeniedHandler() {
return (request, response, accessDeniedException) -> {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
Map<String, Object> result = new HashMap<>();
result.put("code", 403);
result.put("msg", "无权限访问该资源");
new ObjectMapper().writeValue(response.getWriter(), result);
};
}
}在 SecurityConfig 中注入:
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);十、Spring Security 核心知识点总结
1. 两大核心:认证(登录)+ 授权(权限)
2. 核心接口:UserDetailsService(加载用户)、PasswordEncoder(密码加密)
3. 两种权限控制:URL 配置 + 方法注解
4. 密码必须加密:BCrypt 算法是最佳实践
5. 前后端分离:关闭 CSRF,使用 Token 认证(下篇文章讲解)
6. 异常统一处理:401 未登录、403 无权限
结语
Spring Security 是 Java 后端必备安全框架,掌握基础认证与授权,足以应对绝大多数企业项目的安全需求。
如果文章对你有帮助,欢迎点赞、在看、转发,你的支持是我持续更新的动力!
有任何问题,欢迎在评论区留言交流~
- 可访问所有接口。
1. @PreAuthorize:方法执行前校验权限(最常用)
2. @PostAuthorize:方法执行后校验权限
3. @Secured:旧版角色注解
1. 实现 UserDetailsService 接口
2. 重写 loadUserByUsername 方法
3. 从数据库查询用户信息+角色权限
4. 封装成 UserDetails 对象返回
1. 两大核心:认证(登录)+ 授权(权限)
2. 核心接口:UserDetailsService(加载用户)、PasswordEncoder(密码加密)
3. 两种权限控制:URL 配置 + 方法注解
4. 密码必须加密:BCrypt 算法是最佳实践
5. 前后端分离:关闭 CSRF,使用 Token 认证(下篇文章讲解)
6. 异常统一处理:401 未登录、403 无权限
以上就是SpringBoot整合Spring Security实现基础认证与授权的详细内容,更多关于SpringBoot Spring Security认证与授权的资料请关注脚本之家其它相关文章!
