惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News | PayPal Newsroom
Security Archives - TechRepublic
Security Archives - TechRepublic
Hacker News: Ask HN
Hacker News: Ask HN
H
Hacker News: Front Page
Apple Machine Learning Research
Apple Machine Learning Research
TaoSecurity Blog
TaoSecurity Blog
Help Net Security
Help Net Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
V
V2EX
Hugging Face - Blog
Hugging Face - Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
人人都是产品经理
人人都是产品经理
博客园 - 三生石上(FineUI控件)
Security Latest
Security Latest
Cloudbric
Cloudbric
WordPress大学
WordPress大学
S
SegmentFault 最新的问题
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Know Your Adversary
Know Your Adversary
A
Arctic Wolf
L
LangChain Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
The GitHub Blog
The GitHub Blog
P
Proofpoint News Feed
W
WeLiveSecurity
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
M
MIT News - Artificial intelligence
Google DeepMind News
Google DeepMind News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The Cloudflare Blog
小众软件
小众软件
NISL@THU
NISL@THU
云风的 BLOG
云风的 BLOG
P
Privacy & Cybersecurity Law Blog
S
Security @ Cisco Blogs
博客园 - 【当耐特】
I
InfoQ
Vercel News
Vercel News
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
P
Proofpoint News Feed
O
OpenAI News
Google DeepMind News
Google DeepMind News
N
News and Events Feed by Topic
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
K
Kaspersky official blog
T
Threat Research - Cisco Blogs
量子位
宝玉的分享
宝玉的分享

游牧血液

2026 年春节江西之旅 让 AI 写代码,它派了支咨询团队来 在 AI 时代,应该选择什么编程语言? 在 AI 时代,博客该怎么写? 深入理解 ISO 8601 时间间隔表示法 在 CentOS 7 上,使用 Let's Encrypt 安装免费的 HTTPS 证书 可组合数据系统之路:对过去 15 年和未来的思考 交换友情链接 MacOS 下解决 Edge 浏览器侧边栏无法关闭的问题 你使用什么作为数据库的 ID? HTTP API 的错误响应标准 只需要一个文件,在本机跑起 ChatGPT 聊一聊 Node 技术栈 个人网站迁移到 Astro 静态网站生成器 Gridsome 介绍 编程命名规范 升级到 iOS 14.2 后,Google Authenticator 挂了 SaaS 数据库架构选择 GitHub 宣布私有仓库支持无限协作者,核心功能全部免费 新一代通信协议:RSocket Let’s Encrypt 撤销 300 万个证书,检查你的证书是否在其中 使用 BFG Repo-Cleaner 修改 Git 提交历史 | 游牧血液 为 HTTP API 接口增加统一请求日志 | 游牧血液 我编程 20 年的指导原则 | 游牧血液 什么是移动原生业务软件(MNBA:Mobile Native Business Applications) | 游牧血液 JDK 的另一个选择:OpenJDK with Eclipse OpenJ9 分享开发多个线上项目后总结的 HTTP 接口设计方案 Flutter 集成友盟统计 Hugo 简明教程 Flutter 开发时,idevice_id 不能执行问题的解决 另一个静态网站生成器:Hugo
如何做 HTTP 接口的访问控制 | 游牧血液
2020-03-05 · via 游牧血液
Cover

之前分享的(HTTP API 接口设计方案)和示例代码(https://github.com/flmn/http-api-demo),收到了十分积极的反馈,所以深受鼓舞,这次加入访问控制的功能。

目录

展开目录

概述

这个方案有点类似于 Servlet 容器的 Session,但是比较简化,全流程都由自己控制,也比较好扩展和定制。

客户端通过登录接口首先获取一个 accessToken,后续的请求将这个 accessToken 放入 HTTP Header 中;服务端使用 Spring 框架的 HandlerInterceptor 进行统一处理,将用户信息放入请求上下文,供 Controller 使用。

流程

  1. 客户端使用用户名和密码(此处可以是手机号/验证码,小程序的登录 code 等)调用服务端/app/account/login 接口;
  2. 服务端使用客户端提交的凭证,检查用户合法性,如果通过,使用随机数产生一个 accessToken,并使用这个 accessToken 作为 Redis key 的一部分,将用户信息保存在 Redis 的 Hash 数据结构里,并设置过期时间,并将 accessToken 返回给客户端
  3. 后续请求,客户端将这个 accessToken 放到 HTTP Header 中,名称为:X-Access-Token
  4. 服务端使用 HandlerInterceptor 拦截每一个请求,根据从 HTTP Header X-Access-Token 中取得的 accessToken,如果没有 accessToken 为空,向客户端返回 401 错误。如果获得了 accessToken,服务端使用 accessToken 构造 Key 到 Redis 中查询用户信息,如果查不到,可能是登录已过期或者已注销,向客户端返回 401 错误。如果查到信息,将用户信息存入 Request 对象的 Attribute;
  5. 服务端 Controller 可以通过 @RequestAttribute 标记的参数,获取第 4 步 HandlerInterceptor 保存在 Request 对象的 Attribute 中的用户信息;
  6. 客户端调用 /app/account/logout 接口注销,服务端从 Redis 中删除 accessToken Key。

上述是整个过程的描述,在实际开发时,有一些需要注意的细节,请看下面的代码讲解。

代码

登录接口

代码:src/main/java/tech/jitao/httpapidemo/api/app/account/Login.java

...
@RestController(Login.PATH)
@CrossOrigin
@NoAuth
public class Login {
    static final String PATH = "/app/account/login";

    @Autowired
    private AccountService accountService;
...

这里的重点是 @NoAuth 标记,因为调用登录接口时,用户处在未登录状态,所以,通过这个标记,告诉 HandlerInterceptor 跳过这个接口的验证。@NoAuth 标记也可以用在其他无需访问控制的接口上,比如微信支付/支付宝的回调请求。

@NoAuth 标记的代码:src/main/java/tech/jitao/httpapidemo/config/auth/NoAuth.java

另外,来自 Spring 框架的 @CrossOrigin 标记,使这个接口可以跨域调用。关于跨域请求,可以参考阮一峰老师的这篇文章:跨域资源共享 CORS 详解

登录处理

代码:src/main/java/tech/jitao/httpapidemo/service/AccountService.java

... 检查用户

String token = RandomStringUtils.randomAlphanumeric(8) + UuidHelper.gen() + RandomStringUtils.randomAlphanumeric(8);
String keyToken = String.format(RedisKeys.SESSION, token);
HashOperations<String, String, String> hashOperations = redis.opsForHash();
hashOperations.put(keyToken, "userId", String.valueOf(user.getId()));
redis.expire(keyToken, LOGIN_TTL, TimeUnit.DAYS);
logger.info("User {}({}) login with token {}", user.getUsername(), user.getId(), token);

... 向客户端返回数据

这里比较简单,生成一个随机串作为 accessToken,将 userId 保存到 Redis。

拦截器

代码太多,可以直接看 src/main/java/tech/jitao/httpapidemo/config/auth/AuthInterceptor.java

要点如下:

  • PREFIXES,可以设置只对哪些路径开头的请求进行拦截。
  • 如果是跨域请求,即使是返回 401,也要返回 Access-Control-Allow-Origin 头,否则浏览器报的错误是 404。

拦截器的注册

代码:src/main/java/tech/jitao/httpapidemo/config/TheWebMvcConfigurer.java

...
@Configuration
public class TheWebMvcConfigurer implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor());
    }

    @Bean
    public AuthInterceptor authInterceptor() {
        return new AuthInterceptor();
    }
    ...
}
...

TheWebMvcConfigurer 要实现 WebMvcConfigurer,然后 Override addInterceptors 方法注册 AuthInterceptor

Controller 获取用户信息

代码:src/main/java/tech/jitao/httpapidemo/api/web/document/CreateDocument.java

...
@PostMapping(PATH)
public ApiResult process(@Validated @RequestBody Request request,
                         @RequestAttribute(RequestAttributes.USER_ID) long userId) {
    ...
}
...

标记 @RequestAttribute 的参数能够自动获取 AuthInterceptor 中使用 request.setAttribute(RequestAttributes.USER_ID, userId); 设置的信息。

注销处理

代码:src/main/java/tech/jitao/httpapidemo/api/app/account/Logout.java

@RestController(Logout.PATH)
@CrossOrigin
@NoAuth
public class Logout {
    static final String PATH = "/app/account/logout";

    @Autowired
    private AccountService accountService;

    @PostMapping(PATH)
    public ApiResult process(@RequestHeader(value = RequestHeaders.ACCESS_TOKEN, required = false) String accessToken) {
        accountService.logout(accessToken);

        return ApiResult.ok();
    }
}

代码:src/main/java/tech/jitao/httpapidemo/service/AccountService.java

public void logout(String accessToken) {
    if (!Strings.isNullOrEmpty(accessToken)) {
        String keyToken = String.format(RedisKeys.SESSION, accessToken);
        redis.delete(keyToken);

        logger.info("User with access token {} logout.", accessToken);
    }
}

这里做了些容错处理,首先 logout 接口也标记为 @NoAuth,accessToken 直接从 HTTP Header 中取,如果 accessToken 不合法,则略过。原则就是不给前端返回错误,前端会有界面跳转和清存储的动作,也不会注销失败。

本文的示例代码可在 https://github.com/flmn/http-api-demo 获得。

玩得愉快。