Spring Security入门案例

wen java案例 2

本文目录导读:

Spring Security入门案例

  1. 项目结构
  2. 创建Maven项目并配置pom.xml
  3. 主启动类
  4. 配置类 - 基础版本
  5. 配置类 - 高级版本(支持数据库认证)
  6. 自定义UserDetailsService
  7. 控制器类
  8. 模板文件
  9. 扩展功能 - 用户注册
  10. 测试类
  11. 使用说明
  12. 常用功能说明

我来为你创建一个完整的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

常用功能说明

  1. 认证流程:用户访问受保护资源 → 未认证跳转登录页 → 输入用户名密码 → 验证通过 → 访问目标资源

  2. 授权机制:基于RBAC角色控制,不同角色访问不同URL

  3. 加密存储:使用BCrypt加密算法存储密码

  4. 记住我功能:勾选"记住我"后,7天内自动登录

  5. REST API支持:可以通过REST接口获取当前用户信息

这个案例涵盖了Spring Security的主要功能,你可以在此基础上继续扩展:

  • 数据库集成(MySQL/JPA)
  • JWT令牌认证
  • OAuth2.0单点登录
  • 自定义登录页面
  • 权限粒度控制等

抱歉,评论功能暂时关闭!