杰瑞科技汇

springsecurity教程

Spring Security 全方位教程:从入门到实战

目录

  1. 引言:为什么需要 Spring Security?
  2. 核心概念:认证与授权
  3. 快速上手:第一个 Spring Security 应用
  4. 深入认证:自定义登录逻辑
    • 1. 自定义登录页面
    • 2. 使用 UserDetailsServicePasswordEncoder
  5. 深入授权:基于角色的访问控制
    • 1. 方法级安全
    • 2. URL/URL 级安全 (Web Security)
  6. 实现注销功能
  7. 实战项目:集成 JWT 实现无状态认证
    • 1. JWT 简介
    • 2. 项目搭建与依赖
    • 3. 创建 JWT 工具类
    • 4. 配置 Spring Security (无状态)
    • 5. 创建认证与授权接口
    • 6. 测试接口
  8. 总结与最佳实践
  9. 学习资源

引言:为什么需要 Spring Security?

在 Web 应用开发中,安全是至关重要的一环,你需要确保:

springsecurity教程-图1
(图片来源网络,侵删)
  • 认证:用户是他们所声称的身份(通过用户名和密码登录)。
  • 授权:用户只能访问他们被允许的资源(普通用户不能访问管理员页面)。

手动实现这些逻辑非常繁琐且容易出错,Spring Security 正是为解决这个问题而生的,它是一个功能强大、高度可定制的安全框架,能够轻松为基于 Spring 的项目提供认证和授权功能。

核心优势:

  • 全面的安全服务:覆盖认证、授权、CSRF 防护、会话管理等。
  • 默认安全:开箱即用,默认提供强大的安全保护。
  • 高度可定制:几乎每一个组件都可以被替换或重写,以满足复杂业务需求。
  • 与 Spring 生态无缝集成:与 Spring Boot、Spring MVC 等完美配合。

核心概念:认证与授权

在深入代码之前,必须理解这两个核心概念。

1. 认证

认证是 “你是谁?” 的过程,系统通过验证用户的凭证来确认其身份,常见的认证方式有:

springsecurity教程-图2
(图片来源网络,侵删)
  • 用户名/密码:最传统的方式。
  • 令牌:如 JWT (JSON Web Token)。
  • OAuth2:允许用户使用第三方服务(如微信、GitHub)登录。

在 Spring Security 中,认证的核心组件是 Authentication 接口,一个完整的 Authentication 对象包含:

  • Principal:主体信息,通常是登录用户的用户名。
  • Credentials:凭证信息,通常是密码,在认证成功后,凭证通常会被清空,以防止泄露。
  • Authorities:权限列表,表示用户被授予的权限(如 ROLE_USER, ROLE_ADMIN)。

认证过程通常由 AuthenticationManager 来管理,它负责验证 Authentication 对象。

2. 授权

授权是 “你能做什么?” 的过程,在认证之后,系统会根据用户的权限决定其可以访问哪些资源,这通常通过访问控制列表或角色来实现。

在 Spring Security 中,授权的核心是 AccessDecisionVoter(投票器)和 AccessDecisionManager(决策管理器),当用户尝试访问一个受保护的资源时,系统会创建一个 Authentication 对象和一个 RequestAuthorizationContext(包含请求信息),然后由 AccessDecisionManager 协调各个投票器进行投票,最终决定是允许访问(ACCESS_GRANTED)、拒绝访问(ACCESS_DENIED)或弃权(ACCESS_ABSTAIN)。

springsecurity教程-图3
(图片来源网络,侵删)

快速上手:第一个 Spring Security 应用

我们将使用 Spring Boot 来快速创建一个受 Spring Security 保护的应用。

1. 创建项目

使用 Spring Initializr 创建一个新项目,添加以下依赖:

  • Spring Web: 用于构建 Web 应用。
  • Spring Security: 用于安全控制。

2. 启动并测试

创建一个简单的 Controller:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "Hello, World! This is a protected resource.";
    }
    @GetMapping("/admin")
    public String admin() {
        return "Hello, Admin! This is an admin-only resource.";
    }
}

启动应用,然后访问 http://localhost:8080/hello

你会发现,浏览器没有直接显示 "Hello, World!",而是跳转到了一个默认的登录页面,页面标题是 "Spring Security Login"。

为什么? 因为 Spring Security 默认会保护所有 URL,它会自动生成一个用户名为 user,密码为控制台随机生成的一串字符(启动时在日志中可以看到)。

Using generated security password: 1a2b3c4d-5e6f-7890-1234-567890abcdef

你可以使用这个默认的用户名和密码进行登录,登录成功后就能看到 "Hello, World!"。

仅仅添加 spring-boot-starter-security 依赖,你的应用就已经具备了强大的安全防护能力。


深入认证:自定义登录逻辑

默认的登录页面和用户显然不适用于生产环境,下面我们来自定义登录逻辑。

1. 自定义登录页面

创建一个简单的 HTML 登录页面 src/main/resources/templates/login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>Login Page</title>
    <style>
        body { font-family: Arial, sans-serif; background-color: #f4f4f4; }
        .container { max-width: 400px; margin: 50px auto; padding: 20px; background: #fff; border-radius: 5px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
        input[type="text"], input[type="password"] { width: 100%; padding: 10px; margin: 10px 0; border: 1px solid #ddd; box-sizing: border-box; }
        input[type="submit"] { width: 100%; padding: 10px; background-color: #5cb85c; color: white; border: none; border-radius: 4px; cursor: pointer; }
        .error { color: red; margin-bottom: 15px; }
    </style>
</head>
<body>
    <div class="container">
        <h2>Login</h2>
        <div th:if="${param.error}" class="error">
            Invalid username and/or password.
        </div>
        <form th:action="@{/login}" method="post">
            <div>
                <label for="username">Username:</label>
                <input type="text" id="username" name="username" required autofocus />
            </div>
            <div>
                <label for="password">Password:</label>
                <input type="password" id="password" name="password" required />
            </div>
            <div>
                <input type="submit" value="Log In" />
            </div>
        </form>
    </div>
</body>
</html>

2. 使用 UserDetailsServicePasswordEncoder

我们需要告诉 Spring Security 如何验证用户,最佳实践是实现 UserDetailsService 接口,并配置密码编码器。

创建 UserDetailsService 实现

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 MyUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 这里可以替换为从数据库查询用户
        if ("admin".equals(username)) {
            return User.withUsername("admin")
                       .password("{noop}admin123") // {noop} 表示不加密,仅用于演示!生产环境必须加密!
                       .roles("ADMIN")
                       .build();
        } else if ("user".equals(username)) {
            return User.withUsername("user")
                       .password("{noop}user123")
                       .roles("USER")
                       .build();
        }
        throw new UsernameNotFoundException("User not found with username: " + username);
    }
}
  • User 是 Spring Security 提供的一个 UserDetails 实现类。
  • {noop} 是一个密码编码前缀,表示密码是明文。强烈不建议在生产环境中使用!

配置密码编码器 在 Spring Boot 2.x 之后,默认要求密码必须加密,我们需要指定一个密码编码器。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig {
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

我们需要修改 MyUserDetailsService 来使用 BCrypt 加密。

// 修改 MyUserDetailsService
@Service
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if ("admin".equals(username)) {
            return User.withUsername("admin")
                       .password(passwordEncoder.encode("admin123")) // 使用编码器
                       .roles("ADMIN")
                       .build();
        } else if ("user".equals(username)) {
            return User.withUsername("user")
                       .password(passwordEncoder.encode("user123"))
                       .roles("USER")
                       .build();
        }
        throw new UsernameNotFoundException("User not found with username: " + username);
    }
}

配置 Spring Security 我们需要编写一个配置类来告诉 Spring Security 使用我们的登录页面和认证逻辑。

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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
@Configuration
@EnableWebSecurity // 启用 Spring Security 的 Web 安全支持
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN") // /admin/** 路径需要 ADMIN 角色
                .antMatchers("/", "/home", "/login").permitAll() // 这些路径允许所有人访问
                .anyRequest().authenticated() // 其他所有请求都需要认证
                .and()
            .formLogin()
                .loginPage("/login") // 自定义登录页面
                .defaultSuccessUrl("/hello") // 登录成功后跳转的页面
                .permitAll()
                .and()
            .logout()
                .permitAll(); // 允许所有人注销
    }
    // 如果你需要使用基于内存的用户(不推荐用于生产),可以这样配置
    /*
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("user").password(passwordEncoder().encode("password")).roles("USER")
            .and()
            .withUser("admin").password(passwordEncoder().encode("admin")).roles("ADMIN");
    }
    */
}

解释

  • authorizeRequests(): 开始配置 URL 授权规则。
  • antMatchers("/admin/**").hasRole("ADMIN"): 指定匹配 /admin/ 下的所有 URL,访问这些 URL 的用户必须拥有 ADMIN 角色。
  • permitAll(): 允许任何人访问。
  • anyRequest().authenticated(): 剩下的所有请求都必须经过认证。
  • formLogin(): 启用表单登录。
  • loginPage("/login"): 指定自定义的登录页面 URL。
  • defaultSuccessUrl("/hello"): 登录成功后默认跳转的 URL。
  • logout(): 启用注销功能。

创建登录控制器 为了让 /login 路径能渲染我们的 HTML 页面,需要一个 Controller。

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
    @GetMapping("/login")
    public String login() {
        return "login"; // 返回 Thymeleaf 模板名
    }
}

注意:你需要添加 Thymeleaf 依赖才能使用模板。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

启动应用,访问 http://localhost:8080/hello,你会看到自定义的登录页面,使用 user/user123admin/admin123 登录,你会发现 admin 用户可以访问 /admin,而 user 用户会收到 403 Forbidden 错误。


深入授权:基于角色的访问控制

我们已经通过 URL 级别实现了授权,现在来看看方法级别的授权。

1. 方法级安全

Spring Security 允许你在方法上添加注解来控制访问权限。

启用方法安全 在配置类上添加 @EnableGlobalMethodSecurity 注解。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 启用 @PreAuthorize 等注解
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // ...
}

在 Controller 方法上添加注解

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "Hello, World!";
    }
    @GetMapping("/admin")
    @PreAuthorize("hasRole('ADMIN')") // 只有拥有 ADMIN 角色的用户才能调用此方法
    public String admin() {
        return "Hello, Admin!";
    }
    @GetMapping("/user")
    @PreAuthorize("hasAnyRole('USER', 'ADMIN')") // 拥有 USER 或 ADMIN 角色的用户都可以调用
    public String user() {
        return "Hello, User!";
    }
}

即使一个 URL 路径本身是公开的,但如果被 @PreAuthorize 注解保护,未授权的用户也无法通过 API 调用访问它。

2. URL/URL 级安全 (Web Security)

这部分我们在之前的 configure(HttpSecurity http) 方法中已经详细讲解过了,核心就是使用 antMatchers()access()hasRole() 来定义规则。


实现注销功能

Spring Security 默认提供了 /logout 路径用于注销,我们只需要在前端添加一个注销链接即可。

login.html 或任何页面上添加:

<a th:href="@{/logout}">Logout</a>

当用户点击这个链接时,Spring Security 会:

  1. 使当前用户的 Session 失效。
  2. 清除 Security Context。
  3. 重定向到 /login?logout(默认的注销成功页面)。

你可以在 configure(HttpSecurity http) 中自定义注销行为:

.and()
.logout()
    .logoutUrl("/perform_logout") // 自定义注销请求的URL
    .logoutSuccessUrl("/login?logout") // 注销成功后跳转的页面
    .invalidateHttpSession(true) // 使session失效
    .deleteCookies("JSESSIONID") // 删除cookies
    .permitAll();

实战项目:集成 JWT 实现无状态认证

对于前后端分离的应用(如 React, Vue, Angular),传统的 Session 认证不再适用,JWT (JSON Web Token) 是目前最流行的无状态认证方案。

1. JWT 简介

JWT 是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于在各方之间安全地传输信息,它由三部分组成:Header(头部)、Payload(载荷)、Signature(签名)。

工作流程

  1. 登录:用户发送用户名密码到后端。
  2. 验证:后端验证成功后,生成一个 JWT,并将其返回给前端。
  3. 携带令牌:前端在后续的每一个请求中,通过 Authorization 请求头(Bearer <token>)将 JWT 发送给后端。
  4. 验证:后端收到请求后,验证 JWT 的有效性,如果有效,则从载荷中获取用户信息,并允许访问资源。

2. 项目搭建与依赖

创建一个新的 Spring Boot 项目,添加以下依赖:

  • Spring Web
  • Spring Security
  • Spring Data JPA (可选,用于数据库操作)
  • JWT 相关库:我们使用 io.jsonwebtoken:jjwt
  • Lombok (可选,简化代码)
<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

3. 创建 JWT 工具类

创建一个工具类来生成和解析 JWT。

import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtil {
    @Value("${jwt.secret:mySecretKey}")
    private String secret;
    @Value("${jwt.expiration:86400000}") // 24 hours
    private long expiration;
    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }
    public Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }
    public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }
    private Claims extractAllClaims(String token) {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    }
    private Boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, userDetails.getUsername());
    }
    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();
    }
    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = extractUsername(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }
}

application.properties 中配置:

jwt.secret=mySecretKeyForJWT
jwt.expiration=86400000

4. 配置 Spring Security (无状态)

这是最关键的一步,我们需要禁用 Session,并配置一个自定义的 UsernamePasswordAuthenticationFilter 来处理 JWT。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.config.http.SessionCreationPolicy;
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.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private JwtUtil jwtUtil;
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
                .antMatchers("/api/auth/**").permitAll() // 允许访问认证相关接口
                .anyRequest().authenticated() // 其他所有请求都需要认证
            .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 无状态
        // 添加 JWT 过滤器
        http.addFilterBefore(new JwtRequestFilter(jwtUtil, userDetailsService), UsernamePasswordAuthenticationFilter.class);
    }
}
  • csrf().disable(): 因为是无状态的 REST API,不需要 CSRF 保护。
  • sessionCreationPolicy(SessionCreationPolicy.STATELESS): 明确告诉 Spring Security 不要创建或使用 Session。
  • addFilterBefore(...): 添加我们自定义的 JWT 过滤器到 UsernamePasswordAuthenticationFilter 之前。

创建 JWT 过滤器

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtRequestFilter extends OncePerRequestFilter {
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private JwtUtil jwtUtil;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        final String authorizationHeader = request.getHeader("Authorization");
        String username = null;
        String jwt = null;
        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            jwt = authorizationHeader.substring(7);
            username = jwtUtil.extractUsername(jwt);
        }
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            if (jwtUtil.validateToken(jwt, userDetails)) {
                UsernamePasswordAuthenticationToken authenticationToken =
                        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
        }
        chain.doFilter(request, response);
    }
}

5. 创建认证与授权接口

认证请求 DTO

public class AuthenticationRequest {
    private String username;
    private String password;
    // getters and setters
}

认证响应 DTO

public class AuthenticationResponse {
    private String token;
    public AuthenticationResponse(String token) {
        this.token = token;
    }
    // getters and setters
}

认证 Controller

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/auth")
public class AuthenticationController {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private JwtUtil jwtUtil;
    @Autowired
    private MyUserDetailsService userDetailsService;
    @PostMapping("/authenticate")
    public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {
        try {
            authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword())
            );
        } catch (BadCredentialsException e) {
            throw new Exception("Incorrect username or password", e);
        }
        final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
        final String jwt = jwtUtil.generateToken(userDetails);
        return ResponseEntity.ok(new AuthenticationResponse(jwt));
    }
}

一个受保护的资源 Controller

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/resource")
public class ResourceController {
    @GetMapping("/user")
    @PreAuthorize("hasRole('USER')")
    public String userResource() {
        return "This is a user resource.";
    }
    @GetMapping("/admin")
    @PreAuthorize("hasRole('ADMIN')")
    public String adminResource() {
        return "This is an admin resource.";
    }
}

6. 测试接口

  1. 启动应用
  2. 获取 Token:使用 Postman 或 curl 调用 /api/auth/authenticate 接口。
    curl -X POST http://localhost:8080/api/auth/authenticate \
    -H "Content-Type: application/json" \
    -d '{"username":"user", "password":"user123"}'

    你会得到一个类似这样的 JSON 响应:

    {
        "token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIi..."
    }
  3. 访问受保护资源:复制返回的 token,在请求头中添加 Authorization 字段来访问 /api/resource/user
    curl -X GET http://localhost:8080/api/resource/user \
    -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIi..."

    token 有效,你会得到 "This is a user resource." 的响应,token 无效或未提供,你会得到 401 Unauthorized 错误。


总结与最佳实践

  • 从默认配置开始:Spring Security 的默认配置非常安全,是很好的起点。
  • 始终加密密码:生产环境中,绝对不要使用明文密码,使用 BCrypt 等强哈希算法。
  • 最小权限原则:只授予用户完成其任务所必需的最小权限。
  • 方法级安全:对于复杂的业务逻辑,结合 URL 级和方法级安全可以提供更精细的控制。
  • 无状态 API:对于前后端分离的应用,JWT 是一个很好的选择,它使你的服务易于扩展和部署。
  • 关注官方文档:Spring Security 的更新很快,官方文档是最好的学习资源。

学习资源

这份教程涵盖了从基础到高级的 Spring Security 知识,希望对你有所帮助!安全是一个深奥的领域,多实践、多思考是掌握它的关键。

分享:
扫描分享到社交APP
上一篇
下一篇