跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Java大前端java

Java 安全认证与权限控制实战

围绕 Java 系统的安全认证与权限控制,文章梳理了 RBAC 与 ABAC 两种权限模型,并结合 Web 常见风险、URL 访问控制、会话管理、单点登录和 API 权限管理说明了它们的应用场景。示例部分给出了 Spring Security 的 URL 拦截与方法级授权写法,以及 Spring Security 与 JWT 集成的服务端、前端处理流程,最后补充了 Token 存储、过期和传输安全等落地注意事项。

yhbxb发布于 2025/2/23更新于 2026/9/838 浏览
Java 安全认证与权限控制实战

背景

软件系统几乎都会遇到安全问题:漏洞、恶意代码、SQL 注入、XSS、跨站请求伪造、第三方组件风险,甚至是业务逻辑本身的缺陷。要把这些风险压下去,认证和权限控制就成了系统设计里绕不开的一环。

在实际项目里,权限管理通常会落到两类模型上:

  • 基于角色的权限控制(RBAC):先定义角色,再把权限分配给角色,最后把用户映射到角色。
  • 基于属性的权限控制(ABAC):根据用户属性、环境属性、资源属性等动态判断是否允许访问。

两者没有绝对的优劣。RBAC 更容易落地,ABAC 更灵活,很多系统会把它们结合起来用。

Web 场景里常见的安全问题

Web 应用最常见的风险,基本离不开这几类:

  • XSS(跨站脚本攻击)
  • SQL 注入
  • 信息泄露
  • 文件上传漏洞
  • CSRF(跨站请求伪造)

除此之外,还有拒绝服务攻击、任意文件下载、恶意脚本传播等问题。真正做系统时,认证只是起点,权限控制、输入校验、审计和最小权限原则,往往要一起上。

核心概念

用户角色

角色是权限管理里最直观的一层抽象。比如管理员、普通用户、运营人员、审核员,不同角色拥有不同的操作范围。角色的好处是简单清晰,权限模型也容易维护。

用户属性

属性关注的是'这个用户是什么样的人',例如身份证号、手机号、邮箱、部门、职位、IP 地址、地理位置等。属性信息常常参与登录认证、风控判断和细粒度授权。

URL 访问控制

URL 访问控制通常通过访问控制列表(ACL)实现,用来限制某个 URL 是否允许访问。它适合做最基础的入口拦截,比如静态资源放行、管理后台限制访问、某些接口仅允许特定角色调用。

会话管理

会话管理解决的是'如何在多次请求之间保持同一个登录状态'。用户登录成功后,服务端需要保存或识别会话标识,这样后续请求才能继续确认用户身份,避免每次都重新登录。

漏洞扫描

漏洞扫描的作用是尽早发现系统里已知的安全风险,比如弱口令、过期依赖、配置错误、暴露接口等。发现问题之后,升级版本、收紧权限、修复配置,都是常规动作。

单点登录

单点登录(SSO)适合多应用协同场景。用户只登录一次,就能在多个系统间复用认证状态。对于企业内部系统来说,这种方式既省事,也能减少账号体系分裂带来的安全隐患。

API 调用权限管理

API 权限管理通常以密钥、Token 或签名机制为基础,用来控制谁能调用什么接口、调用频率是多少、能不能访问敏感资源。对开放平台、微服务和内部接口治理来说,这一层非常关键。

RBAC 与 ABAC 的基本思路

RBAC 的思路很直接:先建角色,再给角色赋权,最后把用户放进对应角色里。用户访问资源时,系统检查当前角色是否具备相应权限。这个模型的优点是直观、稳定、易维护,也最符合大多数业务系统的习惯。

它的执行路径通常是这样:

  • 创建角色,并定义每个角色拥有的权限。
  • 把用户分配到一个或多个角色中。
  • 用户访问资源时,系统检查角色是否匹配。
  • 根据角色决定页面展示和功能可见性。

RBAC 的问题也很现实:当业务复杂起来后,角色会越来越多,授权和回收权限都可能变得琐碎。

ABAC 则更灵活。它不会只看'你是谁',而是结合用户属性、资源属性和上下文条件一起判断。例如,同一个用户在公司内网可以访问某些接口,到了外网就不行;或者某个部门的员工只能访问自己部门的数据。

它的执行路径大致是:

  • 定义属性类型及其取值范围。
  • 将用户、资源、环境信息纳入规则。
  • 请求到达时,按属性组合判断是否授权。
  • 当属性或业务规则变化时,更新策略。

ABAC 的弹性更大,但规则也更复杂,维护成本通常高于 RBAC。很多企业内部系统会用 ABAC 做精细化控制,而面向公网的业务系统则常常以 RBAC 为主。

Spring Security 中的权限控制示例

Spring Security 提供了比较完整的认证与授权能力。对于 Web 项目来说,常见做法是先通过安全过滤器链拦截请求,再根据 URL、角色或方法注解完成访问控制。

配置 URL 级别的访问控制

下面这个配置展示了典型的 URL 拦截思路:静态资源放行,根路径限制为管理员访问,其他路径按业务需要继续配置。

<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
 <sec:http>
     <sec:intercept-url pattern="/resources/**" access="permitAll"/>
     <sec:intercept-url pattern="/" access="hasRole('ROLE_ADMIN')"/>
     <sec:intercept-url pattern="/*" />

     <sec:http-basic entry-point-ref="authenticationEntryPoint"/>
 </sec:http>
</bean>

这里需要注意一点:intercept-url 的顺序很重要。匹配规则是按顺序生效的,越具体的规则越应该放在前面,否则很容易被更宽泛的规则覆盖。

使用方法注解控制权限

除了 URL 拦截,Spring Security 也常用方法级注解做细粒度授权。这样做的好处是,权限规则可以直接贴近业务代码,维护起来更直观。

@RestController
public class MyController {

    @RequestMapping("/admin")
    @PreAuthorize("hasAuthority('permission:accessAdminPage')")
    public String admin() {
        return "Welcome Admin!";
    }

    @RequestMapping("/user")
    @RolesAllowed({"ROLE_USER", "ROLE_MANAGER"})
    public String user() {
        return "Hello User";
    }
}

@PreAuthorize 更适合表达复杂权限表达式,而 @RolesAllowed 更偏向角色判断。两者并不冲突,关键在于你想表达的是'某个具体权限'还是'某个角色集合'。

如果用户没有满足条件,Spring Security 会直接返回 403 Forbidden。这个行为在后端权限控制里很常见,也很清晰。

Spring Security 与 JWT 集成

在前后端分离或多服务协作的架构里,JWT 是很常见的认证方案。它的优势是无状态、可携带信息、便于跨服务传递身份。

这里以 RSA 签名思路为例说明整体流程:服务端签发 JWT,客户端在后续请求中携带 Token,服务端校验 Token 后再决定是否放行。

服务端依赖

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

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

生成和解析 JWT

下面这个 JwtConfig 负责生成 Token 和解析 Token。Token 里包含用户名、签发时间、过期时间等信息,签名后交给客户端保存。

import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class JwtConfig {

  @Value("${jwt.secret}")
  private String secret;

  public String generateToken(String username){
      Map<String, Object> map = new HashMap<>();
      map.put("username", username);
      Date now = new Date();
      Calendar calendar = Calendar.getInstance();
      calendar.add(Calendar.MINUTE, 10);
      Date expirationTime = calendar.getTime();

      return Jwts.builder().setHeaderParam("typ", "JWT").setClaims(map).setIssuer("auth.com").setSubject("subject")
             .setAudience("audience").setId("id").setIssuedAt(now).setNotBefore(now)
             .setExpiration(expirationTime).signWith(SignatureAlgorithm.HS512, this.secret).compact();
  }

  public String getUsernameFromToken(String token){
      Claims claims = Jwts.parser().setSigningKey(this.secret).parseClaimsJws(token).getBody();
      return claims.getSubject();
  }

  @Bean
  public JwtTokenProvider getTokenProvider(){
    return new JwtTokenProvider();
  }
}

这里有个细节值得留意:getUsernameFromToken 实际读取的是 subject,而 generateToken 里把用户名放进了 claims。如果你打算通过 subject 取用户名,生成时也要一致地写入;否则后续解析出来的值可能并不是你预期的用户名。

从请求中提取 JWT

JwtTokenProvider 的作用是从请求头里取出 Token,再交给 Spring Security 去完成认证。

import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Component
public class JwtTokenProvider{

  @Autowired
  private UserDetailsService userDetailsService;

  @Autowired
  private JwtConfig config;

  public Authentication getAuthentication(HttpServletRequest request) throws IOException, ExpiredJwtException, UnsupportedJwtException, MalformedJwtException {
    final String authorizationHeader = request.getHeader("Authorization");
    if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
      throw new RuntimeException("JWT Token not found");
    }
    final String token = authorizationHeader.substring(7);
    final String username = config.getUsernameFromToken(token);
    return userDetailsService.loadUserByUsername(username);
  }
}

实际项目里,一般还会补上过期校验、签名校验失败处理和异常映射,否则前端收到的错误会比较粗糙。这个例子保留了核心链路,思路是清楚的:拿到 Token、解析出用户名、再加载用户详情交给 Spring Security 处理。

前端如何携带 JWT

前端通常会在请求拦截器里自动加上 Authorization 头。这样业务代码不需要每次手工拼接 Token,请求层就能把认证信息统一带上。

import axios from 'axios';
class AxiosAuthPlugin {
  constructor(token) {
    this._token = `Bearer ${token}`;
  }

  request(config) {
    const headers = config.headers || {};
    headers['Authorization'] = this._token;
    config.headers = headers;
    return config;
  }
}

const instance = axios.create({
  baseURL: process.env.VUE_APP_BASE_URL || '/',
  timeout: parseInt(process.env.VUE_APP_TIMEOUT) || 10000
});

instance.interceptors.request.use((config) => {
  let token = localStorage.getItem('token');
  if (!token && JSON.stringify(config.data).includes('refresh')) {
    return Promise.reject(new Error('No refresh token provided'));
  } else {
    token = token ? JSON.parse(token).access_token : '';
    config.headers = {'Authorization': `${token}`};
    return config;
  }
}, error => {
  return Promise.reject(error);
});

export default function createAxiosInstance() {
  return instance;
}

这段代码的逻辑很典型:普通请求直接带上 access_token,刷新请求则单独处理。这样做能把'访问接口'和'续期 Token'这两条链路分开,失败时也更容易定位问题。

在 Vue 中调用时,通常会把登录、刷新和拉取数据的流程串起来:

import plugin from './AxiosAuthPlugin';
const instance = plugin.createAxiosInstance();

async login() {
  try {
    const res = await instance.post('/login', {username, password});
    localStorage.setItem('token', JSON.stringify(res));
    router.push('/');
  } catch (err) {
    console.log(`Login failed, message:${err}`);
  }
},

async refreshToken() {
  try {
    const refreshToken = JSON.parse(localStorage.getItem('token')).refresh_token;
    const res = await instance.post('/token/refresh', {refresh_token: refreshToken});
    localStorage.setItem('token', JSON.stringify(res));
  } catch (err) {
    console.log(`Refresh token failed, message:${err}`);
    localStorage.removeItem('token');
    alert('Please log in again.');
    router.push('/login');
  }
},

async getData() {
  try {
    const res = await instance.get('/data');
    return res.data;
  } catch (err) {
    console.log(`Get data failed, message:${err}`);
    if (err.response && err.response.status === 401) {
      await refreshToken();
      const newData = await getData();
      return newData;
    }
    throw err;
  }
}

这里的关键点是:当接口返回 401 时,不要急着让用户重新登录,可以先尝试刷新 Token。只有刷新失败,再把用户引导回登录页。这样体验会顺很多。

实践中的一些提醒

JWT 和 Spring Security 结合起来很方便,但落地时别忽略这些问题:

  • Token 不要存得太久,过期时间要合理。
  • 解析和校验失败要统一处理,避免前端拿到一堆杂乱异常。
  • 如果是敏感系统,优先考虑 HTTPS,避免 Token 在传输过程中被窃取。
  • 浏览器本地存储并不是最安全的方案,生产环境里要结合业务风险谨慎选择。
  • 权限模型不要一开始就设计得过重,先保证清晰可维护,再逐步细化。

小结

认证解决的是'你是谁',权限控制解决的是'你能做什么'。在 Java 体系里,Spring Security 提供了比较完整的支撑:既能做 URL 级别拦截,也能做方法级授权,还能和 JWT 这类无状态认证方案配合使用。实际项目里,RBAC 适合做主骨架,ABAC 适合补细节,二者结合起来,通常更容易在安全性和可维护性之间找到平衡。

目录

  1. 背景
  2. Web 场景里常见的安全问题
  3. 核心概念
  4. 用户角色
  5. 用户属性
  6. URL 访问控制
  7. 会话管理
  8. 漏洞扫描
  9. 单点登录
  10. API 调用权限管理
  11. RBAC 与 ABAC 的基本思路
  12. Spring Security 中的权限控制示例
  13. 配置 URL 级别的访问控制
  14. 使用方法注解控制权限
  15. Spring Security 与 JWT 集成
  16. 服务端依赖
  17. 生成和解析 JWT
  18. 从请求中提取 JWT
  19. 前端如何携带 JWT
  20. 实践中的一些提醒
  21. 小结

更多推荐文章

查看全部
  • 青海学子 AI 农业创业实践:基于 ViT 的病虫害检测系统
  • 基于 Rust 和 DeepSeek 的微服务故障日志精准定位与修复
  • C++中 memcpy 和赋值拷贝的核心区别
  • Python 基于朴素贝叶斯的中文评价情感分析
  • SD-WebUI模型下载器:国内用户免代理高速下载Civitai模型完整指南
  • 二分查找实战:山峰数组峰顶索引与寻找峰值
  • 使用 Higress 将 REST API 转换为 MCP Server
  • MATLAB 实现基于 DQN-MLP 的无人机三维路径规划
  • AI 时代如何脱颖而出:商业认知与行动策略
  • Qwen3-VL WEBUI 环境配置与部署实战
  • Photoshop AI 插件使用指南:ComfyUI 与 Stable Diffusion 集成
  • Spring Boot 微服务架构设计与实现
  • Llama 3.1 开源发布:LLM 新里程碑与部署指南
  • Blender 集成 AI 绘画插件实战:AI-Render 使用指南
  • MySQL 数据类型深度解析:选对类型提升性能
  • Windows 系统 Elasticsearch 安装配置指南
  • ClawPanel 开源智能管理面板:支持 20+ 通道与多模型配置
  • Java 在 AI 时代的崛起:从传统机器学习到 AIGC 实践
  • Django REST Framework 重构智能合同审查系统实战
  • Git LFS 跨平台安装指南:Linux、macOS 与 Windows 配置详解

相关免费在线工具

  • 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