接口鉴权实践

article/2025/9/19 6:01:38

我们知道,做为一个web系统,少不了要调用别的系统的接口或者是提供接口供别的系统调用。从接口的使用范围也可以分为对内和对外两种,对内的接口主要限于一些我们内部系统的调用,多是通过内网进行调用,往往不用考虑太复杂的鉴权操作。但是,对于对外的接口,我们就不得不重视这个问题,外部接口没有做鉴权的操作就直接发布到互联网无疑是在这里插入图片描述
而这不仅有暴露数据的风险,同时还有数据被篡改的风险,严重的甚至是影响到系统的正常运转!在这里插入图片描述
接下来,我将结合实际代码,分享一套接口鉴权实践方法。

方案一 appIdsecret

接口鉴权?那还不简单,给每个应用下发一个appIdsecret,接口调用方每次携带appIdsecret调用接口。但是这样真的安全吗?每次调用都要传输密码,很容易被截获。

方案二 appIdsecret+token

调用方根据接口的URLappIdsecret组合在一起,然后加密生成一个token,服务端接收到对应请求之后按照同样的方法生成一个token,然后校验token的 正确性。但是这种方式每个url拼接上appIdsecret生成的token是一样的,未授权系统截获后还是可以通过重放的方式,伪装成认证系统,调用这个接口。

方案三 appIdsecret+token+时间戳

同方案二类似,token的生成过程中在加入时间戳,校验token正确性之前先校验时间戳是否在一定时间窗口内(比如说1分钟),如果超过一分钟,直接拒绝请求,通过后再校验token

方案四 appId+token+时间戳

相对方案二,方案三的方法相对已经有很大提升了(同样参数不能无限制调用),但是仔细一想,还是有问题,攻击者截获请求以后,还是可以在一定时间窗口内通过重放攻击的方式发送请求。那么,有没有终极大招呢?

实际上,攻防之间没有绝对的安全,我们能做的是尽量提高攻击者的成本。这个方案虽然还有漏洞,但是实现起来简单,而且不会过度影响接口性能。权衡安全性、开发成本以及对系统性能的影响,这个方案算是比较合理的一个了。接下来,我将通过java代码一步一步实现这个鉴权功能。
在这里插入图片描述
首先,抽出一个AuthToken.java,定义了生成AuthToken以及校验token是否过期的方法

package com.info.examples.authentication;import cn.hutool.core.map.MapUtil;
import cn.hutool.crypto.digest.DigestAlgorithm;
import cn.hutool.crypto.digest.Digester;
import lombok.Getter;
import lombok.ToString;import java.nio.charset.StandardCharsets;
import java.util.Map;@ToString
public class AuthToken {// 默认超时时间 1 分钟private static final long DEFAULT_EXPIRE_TIME_INTERVAL = 3 * 60 * 1000;@Getterprivate String token;private long createTime;@Getterprivate long expiredTimeInterval = DEFAULT_EXPIRE_TIME_INTERVAL;private static final Digester MD5 = new Digester(DigestAlgorithm.MD5);public AuthToken(String token, long createTime) {this.token = token;this.createTime = createTime;}public AuthToken(String token, long createTime, long expiredTimeInterval) {this.token = token;this.createTime = createTime;this.expiredTimeInterval = expiredTimeInterval;}public static AuthToken createToken(String appId, String secret, long createTime, String baseUrl, Map<String, String> params) {String original = appId + secret + baseUrl + MapUtil.sortJoin(params, "&", "=", true) + createTime;String token = MD5.digestHex(original, StandardCharsets.UTF_8);return new AuthToken(token, createTime);}public boolean isExpired() {return System.currentTimeMillis() < this.createTime + DEFAULT_EXPIRE_TIME_INTERVAL;}}

服务端需要存放已经下发给客户端的appId以及对应的secret,因为这个secret可以有多种存放方式,比如说内存rediszookeeper等多种方式,所以我们抽象出一个用于存放和获取secret的接口

package com.info.examples.authentication;public interface CreditService {/*** 根据 appId 获取对应的 secret** @param appId* @return*/String getCreditByAppId(String appId);/*** 添加appId、secret* @param appId* @param secret*/void addSecret(String appId,String secret);
}

这里作为演示代码,实现一个基于内存存储的

package com.info.examples.authentication;import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import java.util.HashMap;
import java.util.Map;@Service
public class InmemoryCreditServiceImpl implements CreditService {private static final Map<String, String> CREDIT_MAP = new HashMap<>(1 << 2);static {// 这里做测试就直接添加一个appIdCREDIT_MAP.put("testAppId", "secretTest");}@Overridepublic String getCreditByAppId(String appId) {return CREDIT_MAP.get(appId);}@Overridepublic void addSecret(String appId, String secret) {if (!StringUtils.hasLength(appId) || !StringUtils.hasLength(secret)) {return;}CREDIT_MAP.put(appId, secret);}
}

接下来我们实现服务端再接收到请求以后做鉴权

package com.info.examples.authentication;import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;@Slf4j
@RestController
public class AuthenticationController {private final CreditService creditService;public AuthenticationController(CreditService creditService) {this.creditService = creditService;}@GetMapping("/auth")public String auth(@RequestParam Map<String, String> params, HttpServletRequest request) {final String appId = params.get("appId");if (StringUtils.isEmpty(appId)) {return "authentication failed";}final String secret = creditService.getCreditByAppId(appId);final long createTime = Long.parseLong(params.get("createTime"));final String baseurl = request.getRequestURI();final String token = params.get("token");params.remove("token");params.put("secret", secret);AuthToken authToken = AuthToken.createToken(appId, secret, createTime, baseurl, params);log.info(authToken.toString());if (!authToken.isExpired()) {return "authentication failed";}if (!ObjectUtils.nullSafeEquals(token, authToken.getToken())) {return "authentication failed";}// 执行具体业务逻辑params.forEach((k, v) -> log.info("{} = {}", k, v));return "success";}
}

下面我们写一个接口来测试

package com.info.examples.authentication;import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;import java.util.HashMap;
import java.util.Map;@Slf4j
@RestController
public class TestAuthController {@GetMapping("/testAuth")public String testAuth() {RestTemplate template = new RestTemplate();Map<String, String> parameterMap = new HashMap<>(1 << 2);String name = "zhangsan";int age = 18;String baseUrl = "/auth";String appId = "testAppId";long createTime = System.currentTimeMillis();String secret = "secretTest";parameterMap.put("name", name);parameterMap.put("age", String.valueOf(age));parameterMap.put("appId", appId);parameterMap.put("createTime", String.valueOf(createTime));parameterMap.put("secret", secret);AuthToken authToken = AuthToken.createToken(appId, secret, createTime, baseUrl, parameterMap);log.info(authToken.toString());String requestUrl = "http://localhost:8080/auth?name=" + name + "&age=" + age +"&appId=" + appId + "&createTime=" + createTime + "&token=" + authToken.getToken();final String result = template.getForObject(requestUrl, String.class, parameterMap);return result;}
}

打开浏览器,访问http://localhost:8080/test,发现我们已经实现了鉴权的效果,但是每个接口前面都有一大堆鉴权的逻辑,这代码太那啥了在这里插入图片描述
怎么处理呢?很简单,遇到这种情况我们可以使用注解+AOP的方式来优化代码,开始改造…
定义一个注解

package com.info.examples.authentication.annotation;import java.lang.annotation.*;@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Authentication {
}

通过切面实现验证token的功能

package com.info.examples.authentication.aop;import com.info.examples.authentication.AuthToken;
import com.info.examples.authentication.CreditService;
import com.info.examples.authentication.annotation.Authentication;
import com.info.examples.authentication.exception.AuthenticationException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;/*** @desc 使用数字信封方式对输入字段解密,对输出数据加密*/@Slf4j
@Aspect
@Component
public class AuthenticationAop {private final CreditService creditService;public AuthenticationAop(CreditService creditService) {this.creditService = creditService;}@Pointcut("@annotation(com.info.examples.authentication.annotation.Authentication)")public void pointCutMethodBefore() {}@Before("pointCutMethodBefore()")public void doBefore(JoinPoint point) {MethodInvocationProceedingJoinPoint mjp = (MethodInvocationProceedingJoinPoint) point;MethodSignature signature = (MethodSignature) mjp.getSignature();Method method = signature.getMethod();Authentication annotation = method.getAnnotation(Authentication.class);if (annotation != null) {HttpServletRequest request = getRequest(point.getArgs());if (StringUtils.isEmpty(request)) {throw new AuthenticationException("【鉴权失败】,获取HttpServletRequest");}String appId = request.getParameter("appId");if (StringUtils.isEmpty(appId)) {throw new AuthenticationException("【鉴权失败】,appId不存在");}final String secret = creditService.getCreditByAppId(appId);final String token = request.getParameter("token");final long createTime = Long.parseLong(request.getParameter("createTime"));Map<String, String> params = new HashMap<>(1 << 2);final String baseurl = request.getRequestURI();request.getParameterMap().forEach((k, v) -> params.put(k, v[0]));params.remove("token");params.put("secret", secret);AuthToken authToken = AuthToken.createToken(appId, secret, createTime, baseurl, params);log.info(authToken.toString());if (!authToken.isExpired()) {throw new AuthenticationException("【鉴权失败】,token已过期");}if (!ObjectUtils.nullSafeEquals(token, authToken.getToken())) {throw new AuthenticationException("【鉴权失败】,token错误");}}}private HttpServletRequest getRequest(Object[] args) {for (Object o : args) {if (o instanceof HttpServletRequest) {return (HttpServletRequest) o;}}return null;}
}

这个时候,controller层就不用关注鉴权的逻辑了,只需添加一个@Authentication注解即可。

package com.info.examples.authentication;import com.info.examples.authentication.annotation.Authentication;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;
import java.util.Map;@Slf4j
@RestController
public class AuthenticationController {@GetMapping("/auth")@Authenticationpublic String auth(@RequestParam Map<String, String> params, HttpServletRequest request) {// 执行具体业务逻辑params.forEach((k, v) -> log.info("{} = {}", k, v));return "success";}}

附上自定义异常代码

package com.info.examples.authentication.exception;import lombok.*;@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class AuthenticationException extends RuntimeException {private String msg;private String code;public AuthenticationException(String msg) {this.code = "5000";this.msg = msg;}}

现在的代码是不是清爽多了
在这里插入图片描述
今天的分享就到这里了,晚安各位!


http://chatgpt.dhexx.cn/article/9qxOdq6E.shtml

相关文章

登录鉴权

注册登录鉴权 1.1.用户注册 前台需要给我们传递用户名、密码、手机号、手机验证码。验证用户前台传过来的数据是否符合规范&#xff0c;我们使用的Hibernate Validator框架实现的服务端表单校验。短信验证码这块&#xff0c;我们采用的阿里的大于短信接口来做的&#xff0c;我…

登陆鉴权方案设计

一、概述 登陆和认证是什么&#xff1f;都是在鉴别用户的身份。如何鉴定识别出这是哪个用户&#xff1f;或者说&#xff0c;有什么方式只有用户自己知道&#xff08;够安全&#xff09;&#xff0c;又能说出这是他自己&#xff1f;于是就有了"用户名密码"、"用…

鉴权html5服务器,前端鉴权知识学习

1、Cookie 指某些网站为了辨别用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)。 HTTP是一种无状态传输协议&#xff0c;它不能以状态来区分和管理请求和响应。也就是说&#xff0c;服务器单从网络连接上无从知道客户身份。于是给客户端发布一个通行证—cook…

接口鉴权功能的实现

一、背景 随着系统的发展&#xff0c;单体应用逐渐演化成微服务架构。系统微服务化之后&#xff0c;若干个微服务之间会有调用。同个部门内实现的服务会被内部调用&#xff0c;一般风险是可控的。但是如果服务提供给别的部门使用之后&#xff0c;在不了解对方的使用场景&#…

JWT鉴权

文章目录 一、什么是JWT二、JWT能做什么三、JWT介绍以及和传统Session的区别1)基于传统的Session认证2)基于JWT认证 四、JWT的构成和认证流程五、JWT的优缺点 一、什么是JWT JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained …

API签名鉴权设计

鉴权作用 在实际的业务中&#xff0c;必然会存在和其他平台系统进行数据传输。这个时候出于对数据的保密要求&#xff0c;都会对接口&#xff08;API&#xff09;添加鉴权机制&#xff0c;识别调用方的真实身份&#xff0c;对未通过鉴权的请求不做任何业务处理&#xff0c;以帮…

ak和sk怎么认证 海康威视_aksk鉴权

简介 鉴权功能的位置处于基础服务的接入网关中。 1. 认证简介 本鉴权方案是在api层面上进行,通过使用Access Key/Secret Key加密的方法来对验证某个请求的调用者身份。 当接入网关接收到业务调用方的请求时,将使用相同的SK和同样的认证机制生成认证字符串,并与用户请求中包含…

Kafka鉴权

1.SecurityProtocol 参见官方介绍 如图第一个是无需加密&#xff0c;无需鉴权的 第二个是使用sasl鉴权&#xff0c;不加密 该参数需要在服务端进行配置&#xff0c;client端也需要进行相应的配置 2.sasl.mechanism 消息收发的机制&#xff0c;默认为PLAIN。具体介绍参见该文档…

后端认证鉴权

之前我们把redis缓存工具模块做好了、下面结合RBAC权限模型&#xff0c;我们来进行用户的认证鉴权设计。关于RBAC权限模型在之前的文章跟网上都有很多很详细的描述&#xff0c;这里就简单说一下、就是通过用户关联角色&#xff08;多对多&#xff09;、角色关联权限&#xff08…

Postman鉴权

Ok, 今天我们来学习 一下 鉴权 鉴权&#xff08;authentication&#xff09; 是指验证用户是否拥有访问系统的权利。传统的鉴权是通过密码来验证的。这种方式的前提是&#xff0c;每个获得密码的用户都已经被授权。在建立用户时&#xff0c;就为此用户分配一个密码&#xff0c;…

详解 http 鉴权

详解 http 鉴权 【总结分享】10种常用前后端鉴权方法&#xff0c;让你不再迷惘 &#x1f31f;&#x1f31f;&#x1f31f; 前端开发登录鉴权方案完全梳理 &#x1f31f;&#x1f31f;&#x1f31f; 实践出真知&#xff0c;聊聊 HTTP 鉴权那些事 注&#xff1a;此处主要讲的是…

文件服务器鉴权,服务鉴权

使用kmse实现服务的权限校验 通过一个简单的实例说明开发者如何通过kmse进行服务间的权限校验。 一、准备客户端和服务端两个demo 这里演示如何快速实践服务鉴权功能。假如现在有两个微服务 auth-client 和 auth-server&#xff0c;想实现 auth-client 调用 auth-server 时&…

前后端常见的几种鉴权方式

最近在重构公司以前产品的前端代码,摈弃了以前的session-cookie鉴权方式,采用token鉴权,忙里偷闲觉得有必要对几种常见的鉴权方式整理一下。 目前我们常用的鉴权有四种: HTTP Basic Authenticationsession-cookieToken 验证OAuth(开放授权)一.HTTP Basic Authentication 这…

鉴权的4种基本方法

一、基于服务器常出现的问题 Seesions&#xff1a; 每次认证用户发起请求时&#xff0c;服务器需要去创建一个记录来存储信息。当越来越多的用户发请求时&#xff0c;内存的开销也会不断增加。 可扩展性&#xff1a; 由于sessions存放在服务器内存中&#xff0c;伴随而来的是可…

什么是鉴权?一篇文章带你了解postman的多种方式

目录 一、什么是鉴权&#xff1f; 二、postman鉴权方式 一、什么是鉴权&#xff1f; 鉴权也就是身份认证&#xff0c;就是验证您是否有权限从服务器访问或操作相关数据。发送请求时&#xff0c;通常必须包含相应的检验参数以确保请求具有访问权限并返回所需数据。通俗的讲就…

10 分钟带你了解鉴权那些事

前言&#xff1a; 鉴权是自动化测试路上的拦路虎&#xff0c;相信大部分同学都深有体会&#xff0c;今天我们就讲一讲这个鉴权到底是怎么回事。 一、什么是鉴权&#xff0c;为什么要鉴权 鉴权&#xff1a;是指是指验证用户是否拥有访问系统的权利。为什么要鉴权&#xff1a;…

常见的鉴权方式简述

一、什么是鉴权&#xff0c;为什么要鉴权 鉴权&#xff1a;是指验证用户是否有访问系统的权利。 为什么要鉴权 &#xff1a;对用户进行鉴权&#xff0c;防止非法用户占用网络资源&#xff0c;非法用户接入网络&#xff0c;被骗取关键信息 二、鉴权方式 Basic Auth basic au…

笔记——什么是鉴权

前言 鉴权是自动化测试路上的拦路虎&#xff1b;故以此记录鉴权到底是怎么回事。 一、什么是鉴权&#xff0c;为什么要鉴权 鉴权&#xff1a;是指验证用户是否有访问系统的权利。为什么要鉴权 &#xff1a;对用户进行鉴权&#xff0c;防止非法用户占用网络资源&#xff0c;非…

ftok()函数解析

ftok 消息队列、信号灯、共享内存常用在Linux服务端编程的进程间通信环境中。而此三类编程函数在实际项目中都是用System V IPC函数实现的。System V IPC函数名称和说明如下表15-1所示。 表15-1 System V IPC函数 消息队列 信号灯 共享内存区 头文件 <sys/msg.h> …

ftok()函数的使用

在上一篇文章中&#xff0c;Mayuyu讲述了共享内存的原理以及使用方法。在创建共享内存之前&#xff0c;必须指定一个ID值&#xff0c;而这个ID值通常是通过现在要讲的ftok()函数得到。ftok()函数原型如下 其中参数fname是指定的文件名&#xff0c;这个文件必须是存在的而且可以…