本文目录导读:

- 项目结构
- 创建Maven项目并配置pom.xml
- 主启动类
- 配置类 - 基础版本
- 配置类 - 高级版本(支持数据库认证)
- 自定义UserDetailsService
- 控制器类
- 模板文件
- 扩展功能 - 用户注册
- 测试类
- 使用说明
- 常用功能说明
我来为你创建一个完整的Spring Security入门案例,包括基础配置、用户认证和权限控制。
项目结构
spring-security-demo
├── pom.xml
└── src/main/java/com/example/demo
├── DemoApplication.java
├── config/
│ └── SecurityConfig.java
├── controller/
│ └── HelloController.java
└── service/
└── CustomUserDetailsService.java
创建Maven项目并配置pom.xml
<?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.14</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>spring-security-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-security-demo</name>
<description>Spring Security入门案例</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Spring Boot 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>
<!-- Thymeleaf模板引擎(可选) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Spring Security Thymeleaf 整合(可选) -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<!-- Spring Boot 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Security 测试 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
主启动类
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
配置类 - 基础版本
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
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.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 配置密码编码器
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 配置认证信息(内存用户)
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder().encode("admin123"))
.roles("ADMIN")
.and()
.withUser("user")
.password(passwordEncoder().encode("user123"))
.roles("USER");
}
/**
* 配置请求授权规则
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 配置授权规则
.authorizeRequests()
// 不需要认证的路径
.antMatchers("/", "/public/**", "/login").permitAll()
// 需要ADMIN角色的路径
.antMatchers("/admin/**").hasRole("ADMIN")
// 需要USER或ADMIN角色的路径
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
// 其他所有请求都需要认证
.anyRequest().authenticated()
.and()
// 配置表单登录
.formLogin()
.loginPage("/login") // 自定义登录页
.loginProcessingUrl("/doLogin") // 登录提交路径
.defaultSuccessUrl("/home") // 登录成功跳转
.permitAll()
.and()
// 配置登出
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.permitAll()
.and()
// 配置记住我
.rememberMe()
.rememberMeParameter("remember-me")
.tokenValiditySeconds(86400); // 24小时
}
}
配置类 - 高级版本(支持数据库认证)
package com.example.demo.config;
import com.example.demo.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService userDetailsService;
/**
* 配置密码编码器
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 配置认证信息(数据库认证)
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
/**
* 配置请求授权规则
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 关闭CSRF(开发环境可关闭,生产环境建议开启)
.csrf().disable()
// 配置授权规则
.authorizeRequests()
.antMatchers("/", "/public/**", "/register", "/login", "/css/**", "/js/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
.and()
// 配置表单登录
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/doLogin")
.defaultSuccessUrl("/home", true)
.failureUrl("/login?error")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
// 配置登出
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll()
.and()
// 配置记住我(需要数据库支持)
.rememberMe()
.rememberMeParameter("remember-me")
.tokenValiditySeconds(86400)
.userDetailsService(userDetailsService)
// 配置会话管理
.and()
.sessionManagement()
.maximumSessions(1) // 同一用户最多一个会话
.maxSessionsPreventsLogin(false); // 是否阻止新登录
}
}
自定义UserDetailsService
package com.example.demo.service;
import org.springframework.security.core.authority.AuthorityUtils;
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;
import java.util.Arrays;
import java.util.List;
@Service
public class CustomUserDetailsService implements UserDetailsService {
/**
* 模拟数据库用户数据
*/
private static final List<UserAccount> USERS = Arrays.asList(
new UserAccount("admin", "$2a$10$8K1p/a0dL1LXMIgoEDFrwOfMQjzXhEhfCkJ5mJJUzLXGbYhQMoYQO", "ADMIN"), // admin123
new UserAccount("user", "$2a$10$4Wi5qVNBuFOMaNbTFoI0bucKpXgZiUcHwH0YJf5eNxS4AJkQ5D5qi", "USER") // user123
);
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return USERS.stream()
.filter(user -> user.getUsername().equals(username))
.findFirst()
.map(user -> {
return User.withUsername(user.getUsername())
.password(user.getPassword())
.roles(user.getRole())
.build();
})
.orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));
}
/**
* 内部用户账号类
*/
private static class UserAccount {
private String username;
private String password;
private String role;
public UserAccount(String username, String password, String role) {
this.username = username;
this.password = password;
this.role = role;
}
public String getUsername() { return username; }
public String getPassword() { return password; }
public String getRole() { return role; }
}
}
控制器类
package com.example.demo.controller;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
@Controller
public class HelloController {
/**
* 首页 - 所有用户可访问
*/
@GetMapping({"/", "/home"})
public String home(Model model, Authentication authentication) {
if (authentication != null) {
model.addAttribute("username", authentication.getName());
model.addAttribute("roles", authentication.getAuthorities());
}
return "home";
}
/**
* 公开页面
*/
@GetMapping("/public")
@ResponseBody
public String publicPage() {
return "这是公开页面,不需要登录就能访问!";
}
/**
* 登录页
*/
@GetMapping("/login")
public String login() {
return "login";
}
/**
* 用户页面 - USER和ADMIN角色可访问
*/
@GetMapping("/user")
public String userPage(Model model) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
model.addAttribute("username", auth.getName());
return "user";
}
/**
* 管理员页面 - 仅ADMIN角色可访问
*/
@GetMapping("/admin")
public String adminPage(Model model) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
model.addAttribute("username", auth.getName());
return "admin";
}
/**
* 获取当前用户信息 - REST接口
*/
@GetMapping("/api/current-user")
@ResponseBody
public String currentUser(Authentication authentication) {
if (authentication != null) {
return "当前登录用户: " + authentication.getName() +
", 角色: " + authentication.getAuthorities();
}
return "未登录";
}
/**
* 访问拒绝页面
*/
@GetMapping("/403")
public String accessDenied() {
return "error/403";
}
}
模板文件
login.html
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">登录页面</title>
<style>
body { font-family: Arial, sans-serif; background: #f5f5f5; display: flex; justify-content: center; align-items: center; height: 100vh; }
.login-container { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); width: 300px; }
h2 { text-align: center; color: #333; margin-bottom: 20px; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; color: #555; }
input[type="text"], input[type="password"] { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
.remember-me { display: flex; align-items: center; margin-bottom: 15px; }
.remember-me label { margin: 0 0 0 5px; }
button { width: 100%; padding: 10px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }
button:hover { background: #0056b3; }
.error { color: red; margin-bottom: 10px; text-align: center; }
.logout { color: green; margin-bottom: 10px; text-align: center; }
</style>
</head>
<body>
<div class="login-container">
<h2>用户登录</h2>
<!-- 登录失败信息 -->
<div class="error" th:if="${param.error}">
用户名或密码错误!
</div>
<!-- 登出信息 -->
<div class="logout" th:if="${param.logout}">
您已成功登出!
</div>
<form th:action="@{/doLogin}" method="post">
<div class="form-group">
<label>用户名</label>
<input type="text" name="username" required autofocus>
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password" required>
</div>
<div class="remember-me">
<input type="checkbox" name="remember-me" id="rememberMe">
<label for="rememberMe">记住我</label>
</div>
<button type="submit">登录</button>
</form>
</div>
</body>
</html>
home.html
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">首页</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
.navbar { background: #333; padding: 10px; color: white; display: flex; justify-content: space-between; }
.navbar a { color: white; margin-left: 15px; text-decoration: none; }
.content { margin-top: 20px; }
.btn { display: inline-block; padding: 10px 20px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; }
</style>
</head>
<body>
<div class="navbar">
<div>
<strong>Spring Security 入门案例</strong>
</div>
<div sec:authorize="isAuthenticated()">
<span>欢迎: <span sec:authentication="name">用户名</span></span>
<a th:href="@{/logout}">退出</a>
</div>
</div>
<div class="content">
<h1>欢迎访问Spring Security入门案例</h1>
<p>当前登录状态:</p>
<ul>
<li>用户名:<span th:text="${username}">未登录</span></li>
<li>角色:<span th:text="${roles}">无</span></li>
</ul>
<h2>可访问的页面:</h2>
<p>
<a class="btn" th:href="@{/public}">公共页面(所有人)</a>
<a class="btn" sec:authorize="isAuthenticated()" th:href="@{/user}">用户页面(USER)</a>
<a class="btn" sec:authorize="hasRole('ADMIN')" th:href="@{/admin}">管理员页面(ADMIN)</a>
</p>
<p>
<a class="btn" th:href="@{/api/current-user}">查看当前用户信息</a>
</p>
</div>
</body>
</html>
user.html
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">用户页面</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.btn { display: inline-block; padding: 10px 20px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; margin-right: 10px; }
</style>
</head>
<body>
<h1>用户页面</h1>
<p>欢迎,<span th:text="${username}"></span>!这是用户专属页面。</p>
<p>只有USER和ADMIN角色才能访问此页面。</p>
<a class="btn" th:href="@{/home}">返回首页</a>
</body>
</html>
admin.html
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">管理员页面</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.btn { display: inline-block; padding: 10px 20px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; margin-right: 10px; }
.admin-tools { border: 2px solid #ffc107; padding: 15px; margin: 20px 0; border-radius: 5px; background: #fff3cd; }
</style>
</head>
<body>
<h1>管理员页面</h1>
<p>欢迎,<span th:text="${username}"></span>!这是管理员专属页面。</p>
<p>只有ADMIN角色才能访问此页面。</p>
<div class="admin-tools">
<h2>系统管理工具(仅管理员可见)</h2>
<ul>
<li>用户管理</li>
<li>权限配置</li>
<li>系统日志</li>
<li>数据库管理</li>
</ul>
</div>
<a class="btn" th:href="@{/home}">返回首页</a>
<a class="btn" th:href="@{/user}">用户页面</a>
</body>
</html>
error/403.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">访问被拒绝</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
h1 { color: #dc3545; }
.btn { display: inline-block; padding: 10px 20px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; margin-top: 20px; }
</style>
</head>
<body>
<h1>403 - 访问被拒绝</h1>
<p>您没有权限访问此页面。</p>
<a class="btn" th:href="@{/home}">返回首页</a>
<a class="btn" th:href="@{/logout}">重新登录</a>
</body>
</html>
扩展功能 - 用户注册
package com.example.demo.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class RegisterController {
@Autowired
private PasswordEncoder passwordEncoder;
/**
* 注册页面
*/
@GetMapping("/register")
public String registerPage() {
return "register";
}
/**
* 处理注册请求
*/
@PostMapping("/register")
public String register(@RequestParam String username,
@RequestParam String password,
@RequestParam String confirmPassword) {
if (!password.equals(confirmPassword)) {
return "redirect:/register?error";
}
// 这里应该有保存用户到数据库的逻辑
System.out.println("注册用户: " + username);
System.out.println("加密密码: " + passwordEncoder.encode(password));
return "redirect:/login?registered";
}
}
测试类
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
public class SecurityTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testPublicPage() throws Exception {
mockMvc.perform(get("/public"))
.andExpect(status().isOk());
}
@Test
public void testUnauthorizedAccess() throws Exception {
mockMvc.perform(get("/user"))
.andExpect(status().is3xxRedirection());
}
@Test
@WithMockUser(roles = "USER")
public void testUserAccess() throws Exception {
mockMvc.perform(get("/user"))
.andExpect(status().isOk());
}
@Test
@WithMockUser(roles = "ADMIN")
public void testAdminAccess() throws Exception {
mockMvc.perform(get("/admin"))
.andExpect(status().isOk());
}
@Test
@WithMockUser(roles = "USER")
public void testDeniedAdminAccess() throws Exception {
mockMvc.perform(get("/admin"))
.andExpect(status().isForbidden());
}
}
使用说明
启动项目
mvn spring-boot:run # 或 mvn clean package java -jar target/spring-security-demo-0.0.1-SNAPSHOT.jar
测试账号
| 用户名 | 密码 | 角色 |
|---|---|---|
| admin | admin123 | ADMIN |
| user | user123 | USER |
访问地址
- 首页:http://localhost:8080/
- 登录页:http://localhost:8080/login
- 注册页:http://localhost:8080/register
- 公开页面:http://localhost:8080/public
- 用户页面:http://localhost:8080/user
- 管理员页面:http://localhost:8080/admin
常用功能说明
-
认证流程:用户访问受保护资源 → 未认证跳转登录页 → 输入用户名密码 → 验证通过 → 访问目标资源
-
授权机制:基于RBAC角色控制,不同角色访问不同URL
-
加密存储:使用BCrypt加密算法存储密码
-
记住我功能:勾选"记住我"后,7天内自动登录
-
REST API支持:可以通过REST接口获取当前用户信息
这个案例涵盖了Spring Security的主要功能,你可以在此基础上继续扩展:
- 数据库集成(MySQL/JPA)
- JWT令牌认证
- OAuth2.0单点登录
- 自定义登录页面
- 权限粒度控制等