年底,大厂的招聘机会依然不少。以前的金九银十,现在变成了黄金年底。
一名小伙伴在面试阿里时,遇到了一系列关于统一账号系统与权限控制的基础面试题:
- 看到你做了统一账号系统。讲下单点登录的流程?
- 统一账号系统后,怎么做到不同系统访问不同权限?
- JWT为何相对于用户名、密码会更安全?
面试官认为小伙伴的答案不及格,未予通过。这里,我们对这些问题进行系统化、体系化的梳理,并提供一份足以应对高规格面试的参考答案。
1. 单点登录(SSO)流程详解
首先,我们先复盘一个基础但存在问题的回答。
问题原始答案:
- 第三方用户发起请求调用统一账号系统返回验证码。
- 返回验证前端重定向到第三方回调接口。
- 第三方回调接口带着验证码请求统一账号系统获取token
- 使用token获取用户信息。
答案问题分析:
- 技术细节缺失:未说明使用的认证协议(如 OAuth2.0 / JWT)、核心组件、安全机制。
- 逻辑不严谨:“验证码”表述模糊,容易误解为短信验证码,实际应为授权码(Authorization Code)。
- 缺少背景和动机:没有交代为何要建设统一账号系统,解决什么业务痛点。
- 概念混淆风险:“验证码” ≠ “授权码”,易被追问质疑基本概念。
1.1 升级版完整回答(含技术细节、场景、痛点、方案)
总述:一句话概括核心技术逻辑
我们基于 OAuth2.0 授权码模式构建了统一账号系统,通过标准的 SSO 流程实现多子系统间的无缝登录与身份共享,核心是“授权码换取 Token + JWT 身份凭证 + 网关鉴权”的三段式架构。
分述:典型案例详解——某商城(Mall)的统一账号系统落地实践
核心业务场景
某商城包含商品、订单、库存、会员等多个微服务,同时对接官网、App、小程序、线下POS等十余个前端渠道。用户在任意端登录后,需在其他系统中自动识别身份,避免重复登录。目标:实现跨域、跨系统的单点登录(SSO),支撑千万级注册用户的高并发访问。
遇到的痛点问题
| 痛点 |
具体表现 |
| 1. 登录状态无法共享 |
用户在App登录后,进入官网仍需再次输入账号密码 |
| 2. 安全性差 |
多个系统各自维护Session,存在Cookie泄露、CSRF攻击风险 |
| 3. 架构耦合严重 |
每个微服务都做用户校验,代码重复且难以统一权限策略 |
| 4. 扩展性弱 |
新增一个子系统就要重新接入一套登录逻辑 |
解决方案设计与实施(工具 + 场景 + 方案细节)
我们采用 OAuth2.0 + JWT + Spring Cloud Gateway + Redis 的组合方案,构建统一认证中心(UAA - Unified Authentication Authority)。
1.1.1 选用 OAuth2.0 授权码模式(Authorization Code Flow)
为什么选这个?—— 最安全的标准协议,适用于前后端分离、多客户端场景。
典型流程如下:
(1) 用户访问 Mall官网(client) → 跳转至统一账号系统(auth-server):
https://sso.example.com/oauth/authorize?
response_type=code&
client_id=mall-web&
redirect_uri=https://mall.example.com/callback&
scope=user_info&
state=abc123
(2) 用户在统一登录页输入账号密码完成认证;
(3) 统一账号系统生成一次性 authorization_code,302重定向回 redirect_uri?code=xxx&state=abc123;
(4) Mall官网后端服务接收到 code,用 client_secret 向 auth-server 发起 POST 请求交换 access_token:
POST /oauth/token
grant_type=authorization_code&
code=xxx&
redirect_uri=...&
client_id=mall-web&
client_secret=secret_xxx
(5) auth-server 验证成功后返回 JWT 格式的 access_token(含用户ID、角色、过期时间等);
(6) Mall网关(Spring Cloud Gateway)拦截所有微服务请求,通过 JWT 解析用户身份并做权限控制;
(7) 用户后续请求携带 token(Header: Authorization: Bearer <token>),实现无感通行各系统。

核心令牌解析
authorization_code (授权码): 一个临时的、一次性的安全信使。用户授权后生成,通过前端重定向传递,由后端服务配合client_secret兑换access_token。有效期短(5-10分钟),防止截获滥用。
access_token (访问令牌): 真正的“通行证”。采用JWT格式,自包含用户身份和基础权限信息(如用户ID、角色)。有效期较短(1-2小时),用于访问受保护的资源。
refresh_token (刷新令牌): 长期有效的“续命令牌”。用于在access_token过期后,静默获取新令牌而无需用户重新登录。存储在后端,安全性高,可被主动吊销。
1.1.2 关键技术选型
| 工具/框架 |
用途 |
说明 |
| Spring Security OAuth2 |
实现认证服务器与资源服务器 |
提供标准化的 endpoint 支持 /authorize, /token |
| JWT (JSON Web Token) |
生成无状态令牌 |
减少对 Session 和数据库查询依赖,提升性能 |
| Redis |
存储 authorization_code 及 token 黑名单 |
code有效期仅5分钟;支持登出时加入黑名单 |
| Nacos |
配置 client_id/client_secret 等敏感信息 |
动态管理客户端白名单 |
| Spring Cloud Gateway |
统一网关层鉴权 |
在 gateway 层解析 JWT 并注入用户上下文传递给下游服务 |
1.1.3 关键优化点
- 防 CSRF 攻击:使用
state 参数防止跨站请求伪造;
- Token 刷新机制:设置 access_token 过期时间为2小时,refresh_token 为7天;
- 登出全局失效:将登出的 token 加入 Redis 黑名单,有效期内拒绝访问;
- 高性能缓存支持:热点用户信息缓存至 Redis,减少 DB 查询压力。
2. 多系统权限隔离方案
首先,复盘一个存在问题的回答。
问题原始答案:
答:鉴权使用spring scret+jwt 责任链模式 ,最后会查询该用户下不同系统拥有的权限,然后去访问对应的菜单
存在问题分析:
- 概念模糊:未说明 JWT 的生成与校验机制、权限数据结构设计、责任链的具体实现方式。
- 缺乏场景支撑:没有结合项目实际业务背景解释为何需要统一鉴权。
- 缺少解决方案细节:未体现高并发下性能优化、权限变更的实时性等关键挑战。
2.1 升级版完整回答
总述:
统一账号系统的权限隔离核心是 “系统维度 + 责任链鉴权 + 缓存加速” 的三位一体架构,实现了跨系统的安全、高效、灵活的访问控制。
分述:
在商城中,我们有多个微服务系统(商品、订单、库存等),每个系统对应不同的前端应用(如PC、APP、POS)。不同角色的用户需要根据其身份和所属组织,访问不同系统中的特定功能和菜单。
解决方案:
我们采用 Spring Security + JWT + Redis 的方案。用户登录后,生成仅包含userId和roleType的JWT令牌,同时将用户的完整权限集合写入Redis缓存,并设置TTL。
鉴权阶段——基于责任链模式的多层过滤
微服务网关(Spring Cloud Gateway)中构建三层 Filter 责任链:
| 层级 |
功能 |
工具/机制 |
| 第一层:身份认证 |
校验 JWT 是否合法、是否过期 |
JwtUtil.verify(token) |
| 第二层:权限加载 |
根据 userId 查询 Redis 获取权限集合 |
redis.get("user:perms:" + userId) |
| 第三层:访问控制 |
匹配当前请求 URL 是否在权限白名单中 |
antMatcher.match(pattern, requestURI) |
权限隔离设计:支持“多系统多租户”视角
- 在权限表中新增
system_code 字段,标识该权限归属哪个子系统。
- 用户登录时传入
systemCode 参数,表示本次登录是进入哪个系统。
- 查询权限时按
(userId, systemCode) 组合查询,确保跨系统权限隔离。
动态刷新机制
当管理员修改某角色权限时,发送消息队列通知(如RocketMQ)。所有在线网关节点订阅该主题,收到消息后清空相关用户的 Redis 缓存,实现权限变更秒级生效。
3. JWT为何比用户名/密码更安全?
首先,复盘一个存在问题的回答。
问题原始答案:
答:1)jwt秘钥存在后端。2)jwt-token有过期时间。3)jwt相关敏感信息没存和暴露在前端
答案分析:
该回答属于典型的列举式基础回答,方向正确但缺乏技术深度、实战背景和原理性解释,容易被追问击穿。
3.1 升级版完整回答
总述: JWT 相比用户名密码更安全的核心在于 “无状态 + 数字签名 + 生命周期控制” 三位一体的安全模型。
分述:安全性增强的具体设计实践
-
密钥由后端严格保管,签名防篡改
使用 HS256/RSA 等算法对 JWT 进行签名,密钥(secret key)仅保存在网关服务和认证中心,不暴露给前端。攻击者即使获取了 token,也无法伪造新 token 或修改 payload(如将普通用户改成管理员),因为没有私钥无法通过验签。
-
Token 设置合理过期时间,降低泄露风险
设置 access_token 较短的有效期(如2小时),refresh_token 较长的有效期(如7天)。结合前端自动刷新机制,在接近过期时静默续签,既保障用户体验又控制安全窗口。
-
敏感信息不存储在 Token 中
JWT 的 payload 是 Base64 编码可读,因此我们只存放非敏感字段,如 userId、role、iss、exp。绝不存放手机号、邮箱、密码等 PII(个人身份信息),防止信息泄露。同时必须全程使用 HTTPS 传输。
-
配合 Redis 实现“伪注销”能力
JWT 天然不支持主动失效。为此我们设计了 Token 黑名单机制:用户登出时,将当前 token 的唯一标识 + 剩余有效期写入 Redis,并设置 TTL。后续请求经过网关时,先校验是否在黑名单中,若存在则拒绝访问,从而弥补 JWT 的短板。
4. 统一认证鉴权核心架构与概念
在微服务项目中,最头疼的就是“每个应用都搞一套登录鉴权”。为了解决这个问题,我们基于 Spring Cloud Gateway(网关)+ Spring Security + JWT,为所有应用搭建一套“统一的认证 + 鉴权体系”。
4.1 核心概念辨析(网关场景)
-
Authentication(认证):网关“验明正身”
就像小区大门口的保安,你进门时要刷业主卡,保安确认这张卡是真的、是你的。网关场景落地:校验请求头里的 JWT 令牌是否合法(没过期、签名正确),确认用户是系统的合法用户。
-
Authorization(鉴权):网关“核对权限”
保安确认你是业主后,还要看你的业主卡权限——比如你只能进自己的单元楼,不能进小区的付费健身房。网关场景落地:认证通过后,网关根据 JWT 里的用户信息,查询该用户的权限列表,判断其能否访问当前请求的应用或接口。
-
JWT 令牌:网关认的“电子业主卡”
代替传统的账号密码/Session,是网关认的“电子凭证”。卡片上有你的身份信息、权限等级、有效期,而且卡片有加密签名,改了就会作废。
4.2 核心痛点与解决方案
痛点:
- 登录状态不共享:用户登录A应用后,访问B应用需重新登录。
- 代码重复:每个应用都写一套JWT校验、权限判断代码。
- 权限管控混乱:容易发生越权访问,存在安全风险。
- 性能瓶颈:每个应用都查数据库验权限,高并发下数据库压力大。
解决方案设计:
把认证、鉴权逻辑全部抽到网关层,所有应用只关注业务,不用管身份和权限。就像小区只设一个总大门,由专业保安统一管理进出,各个楼栋不用再单独设保安。

5. 单体应用简易实现:SpringBoot + JWT + SpringSecurity
如果你的项目是单体应用,可以用 SpringSecurity+JWT 实现简单的 RBAC 权限控制,权限规则存在数据库,改库即生效。
5.1 核心依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<!-- 其他jjwt依赖、mybatis-plus、mysql驱动等 -->
5.2 核心JWT工具类
负责生成Token、解析Token、校验Token合法性。
@Component
public class JwtUtil {
private static final String SECRET = "your-secret-key";
private static final long EXPIRE_TIME = 2 * 60 * 60 * 1000L;
public String generateToken(String username, List<String> roles) {
return Jwts.builder()
.setSubject(username)
.claim("roles", roles)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE_TIME))
.signWith(SignatureAlgorithm.HS256, SECRET.getBytes())
.compact();
}
// 其他方法:getUsernameFromToken, getRolesFromToken, validateToken
}

5.3 实现用户认证(UserDetailsService)
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) {
// 1. 查用户是否存在
SysUser user = userMapper.selectOne(...);
// 2. 查用户关联的角色
List<SysRole> roles = roleMapper.selectRolesByUserId(user.getId());
// 3. 封装成SpringSecurity能识别的User对象
List<GrantedAuthority> authorities = roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getRoleCode()))
.collect(Collectors.toList());
return new User(user.getUsername(), user.getPassword(), authorities);
}
}

5.4 动态鉴权过滤器(JwtAuthFilter)
@Component
public class JwtAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, ...) {
String requestUri = request.getRequestURI();
// 1. 判断是否是公开接口
boolean isPublic = ... // 从数据库查权限表,匹配路径
if (isPublic) {
chain.doFilter(request, response); return;
}
// 2. 非公开接口,校验Token
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ") || !jwtUtil.validateToken(token)) {
response.setStatus(401); return;
}
token = token.substring(7);
// 3. 校验角色权限
List<String> userRoles = jwtUtil.getRolesFromToken(token);
boolean hasPermission = ... // 从数据库查权限表,匹配路径和角色
if (hasPermission) {
chain.doFilter(request, response);
} else {
response.setStatus(403);
}
}
}

6. 高级实战:响应式网关统一认证鉴权(动态RBAC升级)
面对高并发、多应用、响应式微服务等进阶场景,我们需要将架构升级为 响应式网关 + 动态RBAC。
6.1 升级背景与新挑战
- 多应用权限隔离:运营后台的权限不能泄露给商品应用。
- 高并发响应式适配:传统同步过滤器会成为性能瓶颈。
- 动态权限秒级生效:权限调整后需立即生效,不能重启服务。
- 多端权限差异化:同一用户在不同终端可能有不同权限。
6.2 响应式网关 + 动态RBAC 架构
核心思路:以 响应式网关为总入口,整合SpringSecurity响应式认证能力,基于JWT实现无状态身份凭证,通过 Redis缓存动态RBAC权限规则,实现多应用、多终端的统一认证授权。
核心流程如下:

6.3 响应式JWT工具类(适配WebFlux)
@Component
public class ReactiveJwtUtil {
public Mono<String> generateToken(Long userId, String systemCode, String terminalType, Map<String, Object> extraClaims) {
return Mono.fromSupplier(() -> {
// 构建Claims,包含userId, systemCode, terminalType
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expireTime))
.signWith(getSecretKey(), SignatureAlgorithm.HS256)
.compact();
});
}
public Mono<Boolean> validateToken(String token) {
return parseToken(token)
.map(claims -> !claims.getExpiration().before(new Date()))
.onErrorReturn(false);
}
// 其他解析方法...
}

6.4 响应式权限过滤器(核心)
这是实现「响应式权限校验」和「动态RBAC规则匹配」的核心。
@Component
public class ReactiveDynamicRbacFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 提取请求头中的JWT Token和终端类型
// 2. 响应式校验Token有效性
return reactiveJwtUtil.validateToken(token)
.flatMap(valid -> {
if (!valid) { return setUnauthorized(response); }
// 3. 解析Token中的用户ID和应用编码
return reactiveJwtUtil.getUserIdFromToken(token)
.flatMap(userId -> reactiveJwtUtil.getSystemCodeFromToken(token)
.flatMap(systemCode -> {
// 4. 构建Redis Key,查询用户在当前应用+终端下的权限缓存
String redisKey = "user:" + userId + ":" + systemCode + ":" + terminalType;
return reactiveRedisTemplate.opsForValue().get(redisKey)
.cast(List.class)
.flatMap(permissions -> {
// 5. 匹配当前请求的路径和方法是否在权限列表中
boolean hasPermission = ((List<SysPermission>) permissions).stream()
.anyMatch(perm -> pathMatcher.match(perm.getPath(), requestPath));
if (!hasPermission) {
return setForbidden(response);
}
// 6. 有权限,放行请求
return chain.filter(exchange);
});
}));
});
}
}

6.5 动态权限同步机制(Redis缓存刷新)
为实现权限秒级生效,需监听权限变更事件(如通过RabbitMQ),实时刷新Redis缓存。
@Component
public class PermissionRefreshListener {
@RabbitListener(queues = "permission_refresh_queue")
public Mono<Void> refreshPermission(PermissionRefreshMessage message) {
String redisKey = "user:" + userId + ":" + systemCode + ":" + terminalType;
// 1. 从数据库查询最新的权限规则
return sysPermissionRepository.findPermissionsByUserIdAnd...()
// 2. 刷新Redis缓存(覆盖旧数据)
.flatMap(permissions -> reactiveRedisTemplate.opsForValue().set(redisKey, permissions))
.then(Mono.empty());
}
}

6.6 核心校验步骤梳理
响应式网关的权限校验可清晰拆解为三个关键步骤:

总结
通过上述从理论到实践的全面解析,我们构建了一套完整的统一认证鉴权体系:
- 对于单点登录(SSO),采用 OAuth2.0 授权码模式,结合 JWT 和网关,实现了安全、标准的跨系统登录。
- 对于权限隔离,通过“系统维度”字段和责任链鉴权,配合 Redis 缓存,实现了高效、灵活的多应用访问控制。
- 对于 JWT 安全性,通过签名防篡改、短有效期、黑名单机制和 HTTPS 传输,构建了远优于传统用户名密码的安全模型。
- 对于不同架构,无论是单体应用还是微服务,无论是同步还是响应式场景,都有对应的Spring Boot等技术栈落地方案。特别是在微服务场景下,将认证鉴权收口至 Spring Cloud Gateway,是保障系统安全、提升开发效率、实现高并发架构设计的关键实践。
掌握这套方法论,不仅能够应对相关的技术面试,更能为实际企业级项目的身份与访问控制体系建设提供坚实支撑。