跳到主要内容
Spring Boot 安全认证与授权实战 | 极客日志
Java java
Spring Boot 安全认证与授权实战 Spring Boot 安全认证与授权涉及 Spring Security 框架的集成与应用。通过配置依赖、编写安全策略类及用户服务,可实现基于内存或数据库的身份验证,并结合角色控制访问权限。演示了从基础集成到数据库认证的全流程,包含登录注册、权限拦截及测试验证,帮助开发者构建安全的 Web 应用。重点在于理解 AuthenticationManagerBuilder 的配置方式以及 UserDetailsService 的实现逻辑,确保不同角色对资源的访问受到严格控制。
追风少年 发布于 2026/3/22 更新于 2026/9/9 58 浏览Spring Boot 安全认证与授权实战
在 Web 开发中,安全是绕不开的话题。Spring Security 作为 Spring 生态的事实标准,提供了强大的认证与授权能力。本文将带你从零开始,掌握如何在 Spring Boot 中集成 Spring Security,实现基于内存和数据库的认证,以及基于角色的权限控制。
一、快速集成
要在项目中使用 Spring Security,首先需要在 pom.xml 中添加依赖。除了基础的 Web 支持,我们还需要引入 spring-boot-starter-security。
<dependencies >
<dependency >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-web</artifactId >
</dependency >
<dependency >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-security</artifactId >
</dependency >
<dependency >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-test</artifactId >
test
<scope >
</scope >
</dependency >
</dependencies >
添加依赖后,Spring Boot 会自动启用安全配置。默认情况下,所有请求都需要登录,且密码会随机生成并打印到控制台。为了自定义行为,我们需要创建一个配置类。
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.crypto.password.NoOpPasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure (AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser("admin" )
.password("admin123" )
.roles("ADMIN" )
.and()
.withUser("user" )
.password("user123" )
.roles("USER" );
}
@Override
protected void configure (HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**" ).hasRole("ADMIN" )
.antMatchers("/user/**" ).hasRole("USER" )
.antMatchers("/" ).permitAll()
.and()
.formLogin()
.loginPage("/login" )
.permitAll()
.and()
.logout()
.permitAll();
}
}
这里我们定义了两个角色:ADMIN 和 USER。/admin/** 路径仅允许管理员访问,而首页对所有用户开放。formLogin() 启用了默认的表单登录功能。
二、认证方式详解
1. 基于内存的认证 上面的示例就是典型的基于内存的认证。它适合开发环境或原型验证,因为用户数据硬编码在代码里。优点是简单直接,缺点是重启服务后数据丢失,不适合生产环境。
2. 基于数据库的认证 实际项目中,用户信息通常存储在数据库中。这需要引入 JPA 和数据库驱动(如 H2 用于演示)。
<dependency >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-data-jpa</artifactId >
</dependency >
<dependency >
<groupId > com.h2database</groupId >
<artifactId > h2</artifactId >
<scope > runtime</scope >
</dependency >
import javax.persistence.*;
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String role;
}
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository <User, Long> {
User findByUsername (String username) ;
}
核心服务层:
Spring Security 通过 UserDetailsService 加载用户详情。我们需要实现这个接口来从数据库查询用户。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.ArrayList;
import java.util.List;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null ) {
throw new UsernameNotFoundException ("用户不存在:" + username);
}
List<GrantedAuthority> authorities = new ArrayList <>();
authorities.add(new SimpleGrantedAuthority ("ROLE_" + user.getRole()));
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities(authorities)
.build();
}
}
配置更新:
在 SecurityConfig 中注入这个服务,替换掉内存配置。
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Override
protected void configure (AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
此时,系统会从数据库读取用户名和密码进行校验。记得在 application.properties 中配置好数据库连接信息。
三、授权策略 认证通过后,接下来要解决'能做什么'的问题。Spring Security 主要提供两种授权方式:基于角色和基于权限。
1. 基于角色的授权 这是最常用的方式,通过 hasRole("ROLE_NAME") 来控制 URL 访问。注意,Spring Security 会自动在角色前加 ROLE_ 前缀。
http.authorizeRequests()
.antMatchers("/admin/**" ).hasRole("ADMIN" )
.antMatchers("/user/**" ).hasRole("USER" );
2. 基于权限的授权 如果需要更细粒度的控制(例如某个按钮可见),可以使用 hasAuthority("PERMISSION_NAME")。这通常需要结合方法级注解或更复杂的表达式来实现。
四、前端视图与测试 为了让体验更完整,我们可以准备简单的 HTML 模板。使用 Thymeleaf 引擎可以方便地嵌入 Spring Security 标签。
<!DOCTYPE html >
<html lang ="zh-CN" >
<head > <meta charset ="UTF-8" > <title > 登录</title > </head >
<body >
<div class ="container" >
<h1 > 登录</h1 >
<form th:action ="@{/login}" method ="post" >
<label for ="username" > 用户名:</label >
<input type ="text" id ="username" name ="username" required >
<label for ="password" > 密码:</label >
<input type ="password" id ="password" name ="password" required >
<button type ="submit" > 登录</button >
</form >
<a href ="/" > 返回首页</a >
</div >
</body >
</html >
<!DOCTYPE html >
<html lang ="zh-CN" >
<head > <meta charset ="UTF-8" > <title > 注册</title > </head >
<body >
<div class ="container" >
<h1 > 注册</h1 >
<form th:action ="@{/register}" method ="post" >
<label for ="username" > 用户名:</label >
<input type ="text" id ="username" name ="username" required >
<label for ="password" > 密码:</label >
<input type ="password" id ="password" name ="password" required >
<label for ="role" > 角色:</label >
<select id ="role" name ="role" required >
<option value ="USER" > 用户</option >
<option value ="ADMIN" > 管理员</option >
</select >
<button type ="submit" > 注册</button >
</form >
<a href ="/" > 返回首页</a >
</div >
</body >
</html >
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class SecurityController {
@Autowired
private UserRepository userRepository;
@GetMapping("/")
public String index () { return "index" ; }
@GetMapping("/login")
public String login () { return "login" ; }
@PostMapping("/register")
public String registerUser (String username, String password, String role) {
User user = new User (username, password, role);
userRepository.save(user);
return "redirect:/login" ;
}
}
自动化测试:
最后,别忘了写单元测试来验证安全逻辑是否生效。下面是一个整合测试类的示例,模拟了未登录和已登录状态下的访问情况。
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import java.util.Base64;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class SecurityApplicationTests {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Test
void testIndexPage () {
String response = restTemplate.getForObject("http://localhost:" + port + "/" , String.class);
assertThat(response).contains("首页" );
}
@Test
void testUserPageWithUserAuthentication () {
String credentials = "user:user123" ;
String base64Credentials = Base64.getEncoder().encodeToString(credentials.getBytes());
HttpHeaders headers = new HttpHeaders ();
headers.add("Authorization" , "Basic " + base64Credentials);
HttpEntity<String> entity = new HttpEntity <>(headers);
ResponseEntity<String> response = restTemplate.exchange(
"http://localhost:" + port + "/user" , HttpMethod.GET, entity, String.class
);
assertThat(response.getStatusCodeValue()).isEqualTo(200 );
}
}
五、总结 通过本文,我们完成了 Spring Boot 与 Spring Security 的全流程集成。从依赖引入到内存/数据库认证切换,再到基于角色的权限拦截,每一步都是构建安全应用的基础。记住,安全不是一蹴而就的,随着业务复杂度的增加,你可能还需要引入 OAuth2、JWT 等更高级的方案。但在起步阶段,掌握这些核心概念足以应对大多数场景。
在实际开发中,建议始终开启 HTTPS,并对密码进行加密存储(如 BCrypt),避免使用 NoOpPasswordEncoder 这样的明文编码器。希望这些经验能帮助你在项目中少走弯路。
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online