找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1538

积分

0

好友

193

主题
发表于 7 天前 | 查看: 13| 回复: 0

年底,大厂的招聘机会依然不少。以前的金九银十,现在变成了黄金年底。

一名小伙伴在面试阿里时,遇到了一系列关于统一账号系统与权限控制的基础面试题:

  1. 看到你做了统一账号系统。讲下单点登录的流程?
  2. 统一账号系统后,怎么做到不同系统访问不同权限?
  3. JWT为何相对于用户名、密码会更安全?

面试官认为小伙伴的答案不及格,未予通过。这里,我们对这些问题进行系统化、体系化的梳理,并提供一份足以应对高规格面试的参考答案。

1. 单点登录(SSO)流程详解

首先,我们先复盘一个基础但存在问题的回答。

问题原始答案:

  1. 第三方用户发起请求调用统一账号系统返回验证码。
  2. 返回验证前端重定向到第三方回调接口。
  3. 第三方回调接口带着验证码请求统一账号系统获取token
  4. 使用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>),实现无感通行各系统。

SSO单点登录与OAuth2.0授权码模式交互流程

核心令牌解析
  • 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 的方案。用户登录后,生成仅包含userIdroleType的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 相比用户名密码更安全的核心在于 “无状态 + 数字签名 + 生命周期控制” 三位一体的安全模型。

分述:安全性增强的具体设计实践

  1. 密钥由后端严格保管,签名防篡改
    使用 HS256/RSA 等算法对 JWT 进行签名,密钥(secret key)仅保存在网关服务和认证中心,不暴露给前端。攻击者即使获取了 token,也无法伪造新 token 或修改 payload(如将普通用户改成管理员),因为没有私钥无法通过验签。

  2. Token 设置合理过期时间,降低泄露风险
    设置 access_token 较短的有效期(如2小时),refresh_token 较长的有效期(如7天)。结合前端自动刷新机制,在接近过期时静默续签,既保障用户体验又控制安全窗口。

  3. 敏感信息不存储在 Token 中
    JWT 的 payload 是 Base64 编码可读,因此我们只存放非敏感字段,如 userIdroleissexp。绝不存放手机号、邮箱、密码等 PII(个人身份信息),防止信息泄露。同时必须全程使用 HTTPS 传输。

  4. 配合 Redis 实现“伪注销”能力
    JWT 天然不支持主动失效。为此我们设计了 Token 黑名单机制:用户登出时,将当前 token 的唯一标识 + 剩余有效期写入 Redis,并设置 TTL。后续请求经过网关时,先校验是否在黑名单中,若存在则拒绝访问,从而弥补 JWT 的短板。

4. 统一认证鉴权核心架构与概念

在微服务项目中,最头疼的就是“每个应用都搞一套登录鉴权”。为了解决这个问题,我们基于 Spring Cloud Gateway(网关)+ Spring Security + JWT,为所有应用搭建一套“统一的认证 + 鉴权体系”。

4.1 核心概念辨析(网关场景)

  1. Authentication(认证):网关“验明正身”
    就像小区大门口的保安,你进门时要刷业主卡,保安确认这张卡是真的、是你的。网关场景落地:校验请求头里的 JWT 令牌是否合法(没过期、签名正确),确认用户是系统的合法用户。

  2. Authorization(鉴权):网关“核对权限”
    保安确认你是业主后,还要看你的业主卡权限——比如你只能进自己的单元楼,不能进小区的付费健身房。网关场景落地:认证通过后,网关根据 JWT 里的用户信息,查询该用户的权限列表,判断其能否访问当前请求的应用或接口。

  3. JWT 令牌:网关认的“电子业主卡”
    代替传统的账号密码/Session,是网关认的“电子凭证”。卡片上有你的身份信息、权限等级、有效期,而且卡片有加密签名,改了就会作废。

4.2 核心痛点与解决方案

痛点:

  1. 登录状态不共享:用户登录A应用后,访问B应用需重新登录。
  2. 代码重复:每个应用都写一套JWT校验、权限判断代码。
  3. 权限管控混乱:容易发生越权访问,存在安全风险。
  4. 性能瓶颈:每个应用都查数据库验权限,高并发下数据库压力大。

解决方案设计:
把认证、鉴权逻辑全部抽到网关层,所有应用只关注业务,不用管身份和权限。就像小区只设一个总大门,由专业保安统一管理进出,各个楼栋不用再单独设保安。

基于Spring Cloud Gateway的统一认证鉴权核心流程

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
}

JWT工具类生成与解析Token时序图

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);
        }
    }
}

JWT鉴权过滤器处理流程

6. 高级实战:响应式网关统一认证鉴权(动态RBAC升级)

面对高并发、多应用、响应式微服务等进阶场景,我们需要将架构升级为 响应式网关 + 动态RBAC

6.1 升级背景与新挑战

  • 多应用权限隔离:运营后台的权限不能泄露给商品应用。
  • 高并发响应式适配:传统同步过滤器会成为性能瓶颈。
  • 动态权限秒级生效:权限调整后需立即生效,不能重启服务。
  • 多端权限差异化:同一用户在不同终端可能有不同权限。

6.2 响应式网关 + 动态RBAC 架构

核心思路:以 响应式网关为总入口,整合SpringSecurity响应式认证能力,基于JWT实现无状态身份凭证,通过 Redis缓存动态RBAC权限规则,实现多应用、多终端的统一认证授权。

核心流程如下:
响应式网关统一认证鉴权与动态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);
    }
    // 其他解析方法...
}

响应式JWT工具类生成与验证流程

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);
                                                });
                                    }));
                });
    }
}

响应式动态RBAC过滤器处理时序图

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 核心校验步骤梳理

响应式网关的权限校验可清晰拆解为三个关键步骤:
响应式网关权限校验三步流程

总结

通过上述从理论到实践的全面解析,我们构建了一套完整的统一认证鉴权体系:

  1. 对于单点登录(SSO),采用 OAuth2.0 授权码模式,结合 JWT 和网关,实现了安全、标准的跨系统登录。
  2. 对于权限隔离,通过“系统维度”字段和责任链鉴权,配合 Redis 缓存,实现了高效、灵活的多应用访问控制。
  3. 对于 JWT 安全性,通过签名防篡改、短有效期、黑名单机制和 HTTPS 传输,构建了远优于传统用户名密码的安全模型。
  4. 对于不同架构,无论是单体应用还是微服务,无论是同步还是响应式场景,都有对应的Spring Boot等技术栈落地方案。特别是在微服务场景下,将认证鉴权收口至 Spring Cloud Gateway,是保障系统安全、提升开发效率、实现高并发架构设计的关键实践。

掌握这套方法论,不仅能够应对相关的技术面试,更能为实际企业级项目的身份与访问控制体系建设提供坚实支撑。




上一篇:React 内存泄漏剖析:为何你的useEffect清理了连接却仍占用2GB内存
下一篇:二手平台涌现R5-2500U 8+256无屏幕笔记本,性能可对标i5-8250U
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-10 13:11 , Processed in 0.240738 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表