安全的基本概念

概述

安全不是一个单独的功能点,而是一套围绕身份、权限、数据和边界建立起来的系统。写WEB项目的时候,很多问题看起来是业务问题,最后都会落到安全上,比如用户是谁、能访问什么、数据有没有被篡改、接口是不是被别人反复调用。

在Spring Security里,最常接触的两个词是Authentication和Authorization。前者解决“你是谁”,后者解决“你能做什么”。这两个概念分清以后,后面的JWT、小程序登录、接口鉴权都会顺很多。

Authentication

Authentication通常翻译成认证,也有人叫鉴权。它的重点是识别当前请求背后的用户身份。

常见认证方式有很多:

  • 用户名和密码登录。
  • 手机号加验证码登录。
  • 微信、支付宝等第三方登录。
  • HTTP Basic Auth。
  • Cookie和Session。
  • Bearer Token。

这些方式看起来差别很大,本质上都是把一段用户提交的凭证,转换成一个应用内部能理解的用户身份。

在Spring Security里,这个身份通常会被包装成Authentication对象。它里面会包含用户主体、凭证、权限和认证状态。

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
boolean authenticated = authentication.isAuthenticated();

这段代码就是从当前线程上下文中取出已经认证过的用户。只要请求进入业务代码之前完成了认证,后面的Controller、Service就可以从这里拿到用户身份。

Authorization

Authorization通常翻译成授权或访问控制。用户已经知道是谁了,接下来就要判断这个用户能不能访问某个资源。

比如同样是/admin/users这个接口:

  • 游客不能访问。
  • 普通用户不能访问。
  • 管理员可以访问。
  • 超级管理员可以删除用户。

这就是访问控制。

Spring Security里的权限一般用GrantedAuthority表示。最常见的写法是角色和权限混合使用:

List<GrantedAuthority> authorities = List.of(
        new SimpleGrantedAuthority("ROLE_ADMIN"),
        new SimpleGrantedAuthority("user:read"),
        new SimpleGrantedAuthority("user:write")
);

ROLE_ADMIN更像是身份标签,user:readuser:write更像是具体动作。项目小的时候只用角色也可以,项目复杂以后,角色负责分组,权限负责控制具体资源,会更容易维护。

Principal和Credential

Principal是用户主体,Credential是凭证。

以用户名密码登录为例:

  • Principal是用户名,或者根据用户名查出来的用户对象。
  • Credential是密码。

以JWT访问接口为例:

  • Principal可以是JWT里的用户ID。
  • Credential是HTTP Header里的那段Token字符串。

凭证只应该用来完成认证,不应该长期保留在上下文中。认证完成以后,系统真正需要的是用户身份和权限,而不是密码、短信验证码或者第三方平台返回的临时code。

SecurityContext

SecurityContext是Spring Security保存当前用户状态的地方。默认情况下,它会和当前请求线程绑定。

UsernamePasswordAuthenticationToken authentication =
        new UsernamePasswordAuthenticationToken(user, null, authorities);

SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);

这段代码表示:我已经确认当前请求属于这个用户,并且给他分配了对应权限。后面的过滤器和业务代码就会认为当前请求已经通过认证。

在JWT这种无状态系统里,每个请求都要重新解析Token,然后重新放入SecurityContext。请求结束以后,上下文会被清理掉。

Session和Token

Session和Token是WEB系统最常见的两种登录态设计。

Session的思路是服务器保存登录态,浏览器只保存一个Session ID。浏览器每次请求带上这个ID,服务器就能找到对应用户。

Token的思路是客户端保存一段令牌,服务器每次校验这段令牌,校验成功以后就认为用户已经登录。JWT就是Token的一种常见格式。

方式 登录态保存位置 服务端压力 适合场景
Session 服务端 需要保存会话 后台管理系统、传统网页
Token 客户端 不保存会话 前后端分离、App、小程序、纯API服务

如果项目主要提供API,我一般会优先考虑Token。如果项目是传统服务端页面,Session仍然是简单且稳定的方案。

密码和加密

密码不能明文保存,也不要自己发明加密算法。Spring Security默认推荐使用BCryptPasswordEncoder,它会自动处理盐值和多轮哈希。

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

注册时保存密码摘要:

String encodedPassword = passwordEncoder.encode(rawPassword);

登录时比较密码:

boolean matches = passwordEncoder.matches(rawPassword, encodedPassword);

这里要注意,密码摘要不是加密后的密码,它不能被解密回原文。系统只需要判断用户输入的密码和数据库里的摘要是否匹配。

安全边界

WEB项目里最容易忽略的是边界。很多人只在Controller上做权限判断,但实际系统里还有很多入口。

常见边界有这些:

  • HTTP接口。
  • 静态资源。
  • 定时任务。
  • MQ消费者。
  • 管理后台。
  • 内部RPC接口。
  • 文件上传和下载。

Spring Security主要处理HTTP请求链路,其他入口要结合业务自己处理。比如MQ消息里带了用户ID,也不能默认可信,至少要确认消息来源和消息内容没有被篡改。

我的建议

安全模块不要一开始就写得很复杂。先把认证、授权、密码、Token、异常返回这几块跑通,再逐步补上刷新Token、权限模型、限流、防重放这些内容。

系统越复杂,安全越要保持清晰。能在过滤器里做完的事情,不要散落到每个Controller里;能用统一异常返回的地方,不要让每个接口自己拼错误信息。

© 舟哥 all right reserved,powered by Gitbook文件修订时间: 2026-07-16 12:50:33
作者: 舟哥
链接:https://www.b919p4.com
来源: B919P4
本文原创发布于B919P4,©著作权归作者所有,转载请注明出处,谢谢合作!

undefined

results matching ""

    No results matching ""