
图:题图,科幻风格与数字身份概念相呼应。
本文通过图解和代码相结合的方式,详细介绍一套完整的用户登录验证流程与技术实现。内容涵盖用户注册、登录验证、如何获取操作用户信息,以及黑名单、匿名接口免验证等配套功能的实现思路。理解这些内容,有助于你构建一个安全、健壮的用户认证体系。
业务图解
对于用户登录来说,它涉及用户注册和登录验证几个环节。下图通过流程图展示了系统如何处理新用户和老用户的登录场景。

图1:用户登录(含自动注册)核心业务流程图。
流程解读
客户端-登录界面(通常为手机验证码登录)
- 填写手机号
- 发送验证码
- 填写验证码
- 勾选“新用户自动注册”选项
服务端-用户验证
- 验证手机号与验证码是否匹配正确
- 验证用户是否存在(若不存在,则初始化用户信息)
- 验证通过后,生成访问令牌(Token)
- 将Token返回给客户端
用户信息设计
用户核心信息表设计可参考下表,具体字段需根据自身业务进行增删。

图2:用户基础信息表字段设计示例。
验证流程图解

图3:手机验证码登录的详细流程与数据交互图。
登录验证流程涉及两个接口和两种缓存:
- 获取验证码接口:向手机号发送验证码,并将验证码以手机号为Key存入缓存,并设置合理的过期时间(如5分钟)。
- 登录接口:用户提交手机号及验证码,服务端读取对应缓存进行匹配验证。验证成功则生成Token并返回客户端,登录即告成功。此后,客户端在请求头中携带此Token即可进行各项业务请求。
关于token过期时间
通常,Token的过期时间会根据客户端类型动态设置。例如,APP客户端的Token有效期可以设置得更长(如一周),而Web端的Token则以小时为单位(如两小时)。实现上,可以将Web登录和APP登录拆分为两个独立接口以进行分流,或者在同一登录接口中,根据请求头信息(如User-Agent)判断客户端类型,并设置相应的过期时间。
关于业务请求token验证
登录成功后,客户端每次请求都需在Header中携带Token。通常,我们会在请求到达业务系统之前,通过一个网关(Gateway)来进行统一的Token验证。其核心原理是:登录成功时,将Token作为Key、用户基本信息作为Value写入缓存;网关验证时,只需检查缓存中是否存在此Token即可。流程如下图所示:

图4:网关层校验Token并传递用户信息的流程。
验证成功后,网关会重写内部请求头,将用户ID、账号、昵称等信息放入(如X-User-Id),这样下游的所有业务系统都能方便地获取当前操作用户信息,进而进行权限控制、数据过滤等操作。
关于登出操作
用户携带Token请求登出接口。服务端接收到请求后,直接删除Token对应的用户缓存,然后返回401状态码。客户端收到401后,应自动跳转至登录页面。
关于匿名请求(免登录)
对于无需登录即可访问的接口(如公开信息查询),通常有两种放行方案:
- 授权Token:为特定调用方生成授权Token,并限制其单位时间内的请求次数。
- 路径正则匹配:在网关配置匿名接口的路径规则,符合规则的请求直接放行。
方案1:授权token,限制单位时间请求次数
此方案的优点是,即便是匿名接口,其操作也可追溯至具体调用方,并且请求次数可控,能有效防止接口被恶意滥用。缺点是需要额外的编码和配置管理工作。
技术实现要点
- 提供一个管理页面,用于管理授权Token的使用者、Token值以及单位时间访问次数(如每分钟60次)。
- 对授权Token进行增删改查,并将其存储于缓存中。可使用Hash结构,Key为固定的键名,Field为Token,Value为每分钟的访问次数上限。
- 设立单位时间计数缓存,用于记录每个Token在当前时间窗口内的已请求次数,该缓存Key的过期时间设置为1分钟。
此时,我们需要对之前的网关验证流程图进行升级,加入授权Token的校验环节。

图5:集成授权Token校验与请求频率控制的网关验证流程。
请求次数检查代码实现
以下是使用Spring Boot和RedisTemplate实现请求次数检查的一个示例:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* 授权token请求限制缓存
*
* @author 热黄油啤酒
* @since 2021-11-01
*/
@Component
public class AuthTokenRequestLimitCache {
@Autowired
private RedisTemplate<String, Integer> redisTemplate;
private static final String AUTH_TOKEN_LIMIT_KEY_PREFIX = "auth_token_limit";
/**
* 请求次数+1并检查是否超限
*
* @param token
* @return 是否放行
*/
public boolean incrementWithCheck(String token) {
// 1.获取token请求次数限制,获取为null代表授权配置已被修改,此token已经不具备权限
Integer limit = getLimit(token);
if (limit == null) {
return false;
}
// 2.组装缓存key,读取缓存
String key = String.join(":", AUTH_TOKEN_LIMIT_KEY_PREFIX, token);
Integer count = redisTemplate.opsForValue().get(key);
// 3.没有值代表一分钟内没有请求产生了
if (count == null) {
// 初始化值
redisTemplate.opsForValue().increment(key);
// 设置过期时间
redisTemplate.expire(key, 1L, TimeUnit.MINUTES);
return true;
}
// 自增并获取当前值 大于限制的话 返回false 网关过滤器返回提示信息(如请求过于频繁)
Long inc = redisTemplate.opsForValue().increment(key);
return inc <= limit;
}
/**
* 获取限值
*
* @param token
* @return
*/
public Integer getLimit(String token) {
Object limit = redisTemplate.opsForHash().get("auth_token_limit", token);
return limit == null ? null : (Integer) limit;
}
}
对于授权接口,通常建议只允许GET这类幂等操作,禁止数据提交或更新,但这最终取决于具体的系统安全设计。
方案2:请求路径正则校验
在网关的配置文件中(如application.yml)添加匿名接口的路径规则列表。当请求到达网关时,首先检查其请求路径是否匹配任何一条匿名规则。如果匹配,则直接放行;如果不匹配,再执行正常的Token校验流程。此方案实现相对简单,只需在网关过滤器中进行路径判断即可。
关于黑名单
黑名单是系统安全防护的最后一道关卡。为了应对问题用户,我们需要实现黑名单功能。
实现思路
- 在用户管理页面提供“拉黑”操作。执行后,将被拉黑用户的ID存入一个缓存Set集合中。
- 在用户登录时,检查其ID是否存在于黑名单Set中。若存在,则拒绝登录并给出相应提示。
- 如果用户已在登录状态后被拉黑,网关在Token鉴权通过后,需再次检查用户是否在黑名单中。若在,则立即删除其Token对应的用户缓存,并返回401状态码。客户端收到401后会跳转至登录页,从而在步骤2被拦截。
总结
用户系统是大多数应用的基石,但并非所有开发者都有机会深入参与其完整的设计与开发。希望通过本文的图解与讲解,你能对用户登录验证的全流程及其配套的网关鉴权、安全控制机制有一个系统性的了解。
如果你想深入探讨分布式系统、高并发设计或更多后端架构实践,欢迎访问云栈社区的 后端 & 架构 板块,与更多开发者交流学习。