前言

现在的项目都采用前后端分类的方式开发了,前后端的通讯方式都通过API进行传输。我们知道,如果是管理后台的开发,可以通过shiro或springSecurity进行权限控制,进而保证API接口的安全性,但是,当我们在进行APP或小程序开发的时候,因为需要用户长期登录等问题,再采用shiro等方式进行安全控制就显得不是那么合理的。

可是,如何让我们的API接口变得安全点?不至于当其他人通过抓包的方式拿到你的userId或一些重要参数的时候,对你的数据进行破坏。

那么,API接口的签名校验,将会是你阻挡这些破坏的一堵墙。

下面,让我们开始API签名校验之旅吧。

基础准备

首先,我们先要了解一下普通的API接口是如何访问的(以POSTMAN为例)。






spring boot 调用sse 接口 springboot项目接口怎么调用_时间戳


请求/sign/test接口


下面贴一下代码


@RestController
@RequestMapping(value = "/sign")
public class SignController {

<span >/**
 * 验签测试
 *
 * @return
 */</span>
<span >@RequestMapping</span><span >(</span>value <span >=</span> <span >"/test"</span><span >)</span>
<span >public</span> String <span class="token function">test</span><span >(</span>String name<span >)</span> <span >{</span>
    <span >return</span> name<span >;</span>
<span >}</span>

}

代码很简洁,无需多言,我想,在没进行签名校验的时候大多数人的代码都是这样的吧。

正式开始

实现效果

1、前端请求方式

我们需要用到的就是,http请求的 header,将token(令牌) 和 timestamp(时间戳)作为参数,一起发送给我们的后端。然后后端对token和timestamp进行校验,校验通过后,才进行的正式访问。




spring boot 调用sse 接口 springboot项目接口怎么调用_API_02


通过postman设置header


2、后端处理方式


@RestController
@RequestMapping(value = "/sign")
public class SignController {

<span >/**
 * 验签测试
 *
 * @return
 */</span>
<span >@SignatureValidation</span>
<span >@RequestMapping</span><span >(</span>value <span >=</span> <span >"/test"</span><span >)</span>
<span >public</span> String <span class="token function">test</span><span >(</span>String name<span >)</span> <span >{</span>
    <span >return</span> name<span >;</span>
<span >}</span>


}

是的,你没有看错,就多了一个注解(自定义的,不用去百度什么意思了)。


@SignatureValidation

 @SignatureValidation


加上后,看效果




spring boot 调用sse 接口 springboot项目接口怎么调用_API_03


请求被签名拦截了



好了,下面去看怎么实现的吧。

实现原理

1、签名规则

(1)、前后端都要统一一个秘钥(secret),这个秘钥是自己定义的,尽可能复杂点。这个可别泄露哦。
(2)、我们需要准备一个当前时间戳 timeStamp,这个很好获取,要注意的是,这个时间戳最好要精确到毫秒。
(3)、我们要确定自己的加密方式。可以使用MD5进行加密,你想加密几次看心情,让他们猜不出来就行了。
(4)、将秘钥和时间戳拼接字符串,然后通过你们约定的加密方式进行加密,得到TOKEN
伪代码(一次加密为例)


token = MD5(secret+ timeStamp);


思考:如果一个人通过抓包的方式拿到了你的接口(header 中的token 和 timestamp),他如何才能进行破解?

第一:他要知道我们的秘钥(secret),只要你设计的够复杂,靠猜是猜不出来的。
第二:他要知道我们的加密方式,我这里用MD5这种常规加密,你们可以换个加密方式,最好是非对称加密。
第三:他要知道我们的加密次数。
第四:他要知道我们的加密规则

如果不是特别重要的接口或者专门要搞你,大部分会退缩的。当然少部分就会通过反编译你的源码去拿到这些数据,或者意外泄露,那你只能自己去加强相关的防护去呗。混淆文件啦,应用加固啦,用胶带粘住嘴啦,方法很多,自己去慢慢研究。

2、Springboot 实现(大家最喜欢的环节,一步步去复制代码到自己项目中去吧)

1、新建一个文件,定义注解接口




文件位置



package com.xxx.aop;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
 * @author tangn
 * 小程序请求认证
 */
@Retention(value = RetentionPolicy.RUNTIME)
public @interface SignatureValidation {
}

package com.xxx.aop;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
 * @author tangn
 * 小程序请求认证
 */
@Retention(value = RetentionPolicy.RUNTIME)
public @interface SignatureValidation {
}


其中,一定要注意包名(根据你放这个文件位置),这个下面会用到。


package com.xxx.aop;

package com.xxx.aop;


2、使用aspect进行切点拦截(不知道其实现原理,去百度吧)



spring boot 调用sse 接口 springboot项目接口怎么调用_加密方式_04


文件位置(忽略那个HttpAspect)



@Aspect
@Component
public class SignatureValidation {
    /**
     * 时间戳请求最小限制(30s)
    * 设置的越小,安全系数越高,但是要注意一定的容错性
     */
    private static final long MAX_REQUEST = 30 * 1000L;
    /**
     * 秘钥
     */
     private static final long SECRET= "前后端约定的秘钥";

@Aspect
@Component
public class SignatureValidation {
    /**
     * 时间戳请求最小限制(30s)
    * 设置的越小,安全系数越高,但是要注意一定的容错性
     */
    private static final long MAX_REQUEST = 30 * 1000L;
    /**
     * 秘钥
     */
     private static final long SECRET= "前后端约定的秘钥";

<span >/**
 * 验签切点(完整的找到设置的文件地址)
 */</span>
<span >@Pointcut</span><span >(</span><span >"execution(@com.xxx.aop.SignatureValidation * *(..))"</span><span >)</span>
<span >private</span> <span >void</span> <span class="token function">verifyUserKey</span><span >(</span><span >)</span> <span >{</span>
<span >}</span>

<span >/**
 * 开始验签
 */</span>
<span >@Before</span><span >(</span><span >"verifyUserKey()"</span><span >)</span>
<span >public</span> <span >void</span> <span class="token function">doBasicProfiling</span><span >(</span><span >)</span> <span >{</span>
    <span >HttpServletRequest</span> request <span >=</span> <span >(</span><span >(</span><span >ServletRequestAttributes</span><span >)</span> <span >Objects</span><span >.</span><span class="token function">requireNonNull</span><span >(</span><span >RequestContextHolder</span><span >.</span><span class="token function">getRequestAttributes</span><span >(</span><span >)</span><span >)</span><span >)</span><span >.</span><span class="token function">getRequest</span><span >(</span><span >)</span><span >;</span>
    <span >String</span> token <span >=</span> request<span >.</span><span class="token function">getHeader</span><span >(</span><span >"token"</span><span >)</span><span >;</span>
    <span >String</span> timestamp <span >=</span> request<span >.</span><span class="token function">getHeader</span><span >(</span><span >"timestamp"</span><span >)</span><span >;</span>
    <span >try</span> <span >{</span>
        <span >Boolean</span> check <span >=</span> <span class="token function">checkToken</span><span >(</span>token<span >,</span> timestamp<span >)</span><span >;</span>
        <span >if</span> <span >(</span><span >!</span>check<span >)</span> <span >{</span>
          <span >// 自定义异常抛出(开发者自行换成自己的即可)</span>
            <span >throw</span> <span >new</span> <span >MyException</span><span >(</span><span >ResultEnums</span><span >.</span>ERROR<span >,</span> <span >"签名验证错误"</span><span >)</span><span >;</span>
        <span >}</span>
    <span >}</span> <span >catch</span> <span >(</span><span >Throwable</span> throwable<span >)</span> <span >{</span>
        <span >// 自定义异常抛出(开发者自行换成自己的即可)</span>
        <span >throw</span> <span >new</span> <span >PlbException</span><span >(</span><span >ResultEnums</span><span >.</span>ERROR<span >,</span> <span >"签名验证错误"</span><span >)</span><span >;</span>
    <span >}</span>
<span >}</span>

<span >/**
 * 校验token
 *
 * @param token     签名
 * @param timestamp 时间戳
 * @return 校验结果
 */</span>
<span >private</span> <span >Boolean</span> <span class="token function">checkToken</span><span >(</span><span >String</span> token<span >,</span> <span >String</span> timestamp<span >)</span> <span >{</span>
    <span >if</span> <span >(</span><span >StringUtils</span><span >.</span><span class="token function">isAnyBlank</span><span >(</span>token<span >,</span> timestamp<span >)</span><span >)</span> <span >{</span>
        <span >return</span> <span class="token boolean">false</span><span >;</span>
    <span >}</span>
    <span >long</span> now <span >=</span> <span >System</span><span >.</span><span class="token function">currentTimeMillis</span><span >(</span><span >)</span><span >;</span>
    <span >long</span> time <span >=</span> <span >Long</span><span >.</span><span class="token function">parseLong</span><span >(</span>timestamp<span >)</span><span >;</span>
    <span >if</span> <span >(</span>now <span >-</span> time <span >></span> MAX_REQUEST<span >)</span> <span >{</span>
        log<span >.</span><span class="token function">error</span><span >(</span><span >"时间戳已过期[{}][{}][{}]"</span><span >,</span> now<span >,</span> time<span >,</span> <span >(</span>now <span >-</span> time<span >)</span><span >)</span><span >;</span>
        <span >return</span> <span class="token boolean">false</span><span >;</span>
    <span >}</span>
    <span >String</span> crypt <span >=</span> <span >MD5Utils</span><span >.</span><span class="token function">getMD5</span><span >(</span>SECRET<span >+</span> timestamp<span >)</span><span >;</span>
    <span >return</span> <span >StringUtils</span><span >.</span><span class="token function">equals</span><span >(</span>crypt<span >,</span> token<span >)</span><span >;</span>
<span >}</span>

<span >/**
 * 验签切点(完整的找到设置的文件地址)
 */</span>
<span >@Pointcut</span><span >(</span><span >"execution(@com.xxx.aop.SignatureValidation * *(..))"</span><span >)</span>
<span >private</span> <span >void</span> <span class="token function">verifyUserKey</span><span >(</span><span >)</span> <span >{</span>
<span >}</span>

<span >/**
 * 开始验签
 */</span>
<span >@Before</span><span >(</span><span >"verifyUserKey()"</span><span >)</span>
<span >public</span> <span >void</span> <span class="token function">doBasicProfiling</span><span >(</span><span >)</span> <span >{</span>
    <span >HttpServletRequest</span> request <span >=</span> <span >(</span><span >(</span><span >ServletRequestAttributes</span><span >)</span> <span >Objects</span><span >.</span><span class="token function">requireNonNull</span><span >(</span><span >RequestContextHolder</span><span >.</span><span class="token function">getRequestAttributes</span><span >(</span><span >)</span><span >)</span><span >)</span><span >.</span><span class="token function">getRequest</span><span >(</span><span >)</span><span >;</span>
    <span >String</span> token <span >=</span> request<span >.</span><span class="token function">getHeader</span><span >(</span><span >"token"</span><span >)</span><span >;</span>
    <span >String</span> timestamp <span >=</span> request<span >.</span><span class="token function">getHeader</span><span >(</span><span >"timestamp"</span><span >)</span><span >;</span>
    <span >try</span> <span >{</span>
        <span >Boolean</span> check <span >=</span> <span class="token function">checkToken</span><span >(</span>token<span >,</span> timestamp<span >)</span><span >;</span>
        <span >if</span> <span >(</span><span >!</span>check<span >)</span> <span >{</span>
          <span >// 自定义异常抛出(开发者自行换成自己的即可)</span>
            <span >throw</span> <span >new</span> <span >MyException</span><span >(</span><span >ResultEnums</span><span >.</span>ERROR<span >,</span> <span >"签名验证错误"</span><span >)</span><span >;</span>
        <span >}</span>
    <span >}</span> <span >catch</span> <span >(</span><span >Throwable</span> throwable<span >)</span> <span >{</span>
        <span >// 自定义异常抛出(开发者自行换成自己的即可)</span>
        <span >throw</span> <span >new</span> <span >PlbException</span><span >(</span><span >ResultEnums</span><span >.</span>ERROR<span >,</span> <span >"签名验证错误"</span><span >)</span><span >;</span>
    <span >}</span>
<span >}</span>

<span >/**
 * 校验token
 *
 * @param token     签名
 * @param timestamp 时间戳
 * @return 校验结果
 */</span>
<span >private</span> <span >Boolean</span> <span class="token function">checkToken</span><span >(</span><span >String</span> token<span >,</span> <span >String</span> timestamp<span >)</span> <span >{</span>
    <span >if</span> <span >(</span><span >StringUtils</span><span >.</span><span class="token function">isAnyBlank</span><span >(</span>token<span >,</span> timestamp<span >)</span><span >)</span> <span >{</span>
        <span >return</span> <span class="token boolean">false</span><span >;</span>
    <span >}</span>
    <span >long</span> now <span >=</span> <span >System</span><span >.</span><span class="token function">currentTimeMillis</span><span >(</span><span >)</span><span >;</span>
    <span >long</span> time <span >=</span> <span >Long</span><span >.</span><span class="token function">parseLong</span><span >(</span>timestamp<span >)</span><span >;</span>
    <span >if</span> <span >(</span>now <span >-</span> time <span >></span> MAX_REQUEST<span >)</span> <span >{</span>
        log<span >.</span><span class="token function">error</span><span >(</span><span >"时间戳已过期[{}][{}][{}]"</span><span >,</span> now<span >,</span> time<span >,</span> <span >(</span>now <span >-</span> time<span >)</span><span >)</span><span >;</span>
        <span >return</span> <span class="token boolean">false</span><span >;</span>
    <span >}</span>
    <span >String</span> crypt <span >=</span> <span >MD5Utils</span><span >.</span><span class="token function">getMD5</span><span >(</span>SECRET<span >+</span> timestamp<span >)</span><span >;</span>
    <span >return</span> <span >StringUtils</span><span >.</span><span class="token function">equals</span><span >(</span>crypt<span >,</span> token<span >)</span><span >;</span>
<span >}</span>


}

下面说一下用到的工具类:
MD5Utils
每个项目里面都有吧,没有的话网上一搜就行了。
StringUtils
其实很简单,自己写也行,引入第三方的也行,我是引用的apache的,你们也可试试。
pom.xml 文件


<dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>


好了,你又没有看错。这样结束了。这样你就有了自己的签名校验工具类,快拿到你的项目中试试去吧。
然后你以后的API接口,只需要加上这个注解就能进行签名验证了。


@SignatureValidation

 @SignatureValidation