学习 spring-cloud-aibaba第七篇,JWT认证授权


特别声明:整理自慕课网大目师兄的微服务视频,链接: https://coding.imooc.com/learn/list/358.html

1.有状态 vs 无状态

1.1 有状态的做法

  • 登录是怎么回事?
    客户端和服务器建立连接的时候,客户端会有一个sessionId,这个sessionId是不会变的,服务端就根据这个sessionId唯一确定你是谁了,你如果做了登录这个动作,服务端就会把你的登录信息存在服务端,登录信息和sessionId是一对一的关系,所以你每次请求,服务端都会找找这个sessionId有没有对应的登录信息,如果有,就表示登录了,如果找不到,就没有登录
  • 服务端用HttpSession保存登录信息
    把登录信息放到HttpSession里是一种很常见的做法,可以设置应用的session过期时间,如果这段时间,用户没有操作,HttpSession过期作废了,用户再来操作就找不到他的登录信息了,所以要重新登录

    弊端: 如果启动了多个应用实例,你说你把用户的登录信息保存到哪个应用好呢?,你保存到实例1里,当用户的请求被分配到实例2实例3处理的时候,实例2实例3没有存放用户登录信息,就会认为用户没有登录,又让用户登录,这用户不是要蒙圈了?

    解决方法:nginx的转发规则配置为粘性会话,即相同ip的请求,总是转发到同一个实例当中,这样就不会找不到登录信息了。但是这种做法也有弊端,如果用户断网重连,ip地址发生改变,把用户请求发送到其它实例,又会发生找不到登录信息的情况
    在这里插入图片描述
  • 用第三方存储登录信息
    如下图,把登录信息存放到Session Store里,可以是Redis,也可以是memberCache,甚至关系型数据库也行啊,开玩笑了,当然是nosql比较好了,redis,membercache都是nosql
    在这里插入图片描述
    评论:这样不管用户的请求被转发到哪个实例,都能从第三方存储里找到他的登录信息了,如果Session Store没有到达存取瓶颈,这样做是可以的,一般都够用了。
    弊端
    1.访问量很大的时候,Session Store也忙不过来了,那么这个方案就还需要改进
    2.Session Store挂了,所有微服务都不能用了
    3.Session Store迁移了,所有微服务都要改连接地址

1.2 无状态的做法

含义:无状态,就是服务端不保存用户的登录状态,既不把用户登录信息保存在内存里,也不把信息保存在第三方存储。用户做完登录,给用户发一个Token,用户每次请求都带上这个Token,放在Header里或者请求参数里,服务端拿到这个Token做解析,如果合法且未失效,就认为是登录的。Token里可以存放一些不敏感的用户信息,例如用户Id,姓名之类的。不要带手机号,这信息很敏感

图示
在这里插入图片描述
弊端token发放出去,服务器端就无法控制这个token了,不能让它马上失效,不能让这个用户立刻处于下线状态,毫无掌控力

1.3 优缺点对比

在这里插入图片描述

2.微服务的认证方案

2.1 处处安全方案

  • 使用协议
    OAuth 2.0,大目师兄推荐的介绍文章:http://ifeve.com/oauth2-tutorial-all/,技术性太强了,我没看下去
  • 代表实现
    Spring Cloud Security、JBoss Keycloak

2.2 网关认证授权,内部裸奔

认证过程:用户的登录授权,Token的解析判断,全网关里实现。后面的微服务不再判断这个请求有没有登录了
在这里插入图片描述
优点:逻辑简单,性能好
缺点:后面微服务毫不设防,有风险

2.3 处处校验Token

逻辑:网关不再关心业务,做自己的正事,过滤和转发。Token由专门的认证中心颁发,每个微服务在被请求的时候,都要校验Token的合法性,不用担心影响性能,现在的算法很快,毫秒级别的
在这里插入图片描述
优点:微服务分工更明确,网关不插手业务
缺点:每个微服务都要校验Token,秘钥到处用,增加泄露风险

其实可行的方案远不止这3种,不管你怎么实现,反正宗旨是能判断出用户有没有登录就行了

3.无状态的JWT (Json Web Token)

3.1 释义

JWT 的表现形式是长长的一串字符串

  • 组成
    在这里插入图片描述
    Signature = Header里的写的签名算法(Base64(Header).Base64(Payload),秘钥)
    例如:HS256(“aaa.bbb”,秘钥)
  • 生成JWT
    Token = Base64(Header).Base64(Payload).Base64(Signature)
    例如:aaa.bbb.ccc

3.2 user-center引入JWT

3.2.1 新建common项目

由于JWT每个微服务都要用到,干脆新建个工具类项目吧,微服务们依赖这个common项目就好了。项目gitee地址:https://gitee.com/zengchen2016/common

  • 新建maven项目
    在这里插入图片描述
  • pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zc</groupId>
    <artifactId>common</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <dependencies>
        <!--jwt相关-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.10.7</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.10.7</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.10.7</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
  • JWTUtils类
package com.zengchen.common.utils;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.Map;

public class JWTUtils {


    /**
     * 从token中获取claim
     *
     * @param token  token
     * @param secret secret 密钥
     * @return claim
     */
    public static Claims getClaimsFromToken(String token, String secret) {
        try {
            return Jwts.parser()
                    .setSigningKey(secret.getBytes())
                    .parseClaimsJws(token)
                    .getBody();
        } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | IllegalArgumentException e) {
            throw new IllegalArgumentException("Token invalided.");
        }
    }

    /**
     * 获取token的过期时间
     *
     * @param token token
     * @return 过期时间
     */
    public static Date getExpirationDateFromToken(String token, String secret) {
        return getClaimsFromToken(token, secret)
                .getExpiration();
    }

    /**
     * 判断token是否过期
     *
     * @param token token
     * @return 已过期返回true,未过期返回false
     */
    private static Boolean isTokenExpired(String token, String secret) {
        Date expiration = getExpirationDateFromToken(token, secret);
        return expiration.before(new Date());
    }

    /**
     * 计算token的过期时间
     *
     * @return 过期时间
     */
    private static Date getExpirationTime(Long expirationTimeInSecond) {
        return new Date(System.currentTimeMillis() + expirationTimeInSecond * 1000);
    }

    /**
     * 为指定用户生成token
     *
     * @param claims 用户信息
     * @return token
     */
    public static String generateToken(Map<String, Object> claims, String secret, Long expirationTimeInSecond) {
        Date createdTime = new Date();
        Date expirationTime = getExpirationTime(expirationTimeInSecond);


        byte[] keyBytes = secret.getBytes();
        SecretKey key = Keys.hmacShaKeyFor(keyBytes);

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(createdTime)
                .setExpiration(expirationTime)
                // 你也可以改用你喜欢的算法
                // 支持的算法详见:https://github.com/jwtk/jjwt#features
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();
    }

    /**
     * 判断token是否非法
     *
     * @param token token
     * @return 未过期返回true,否则返回false
     */
    public static Boolean validateToken(String token, String secret) {
        try {
            return !isTokenExpired(token, secret);
        }catch (Exception e){
            return false;
        }

    }

    /**
     * 获取header或者payload
     * @param encodedString
     * @return
     * @throws Exception
     */
    public static String getInfo(String encodedString) throws Exception {
        byte[] info = Base64Utils.decode(encodedString);
        return new String(info);
    }
}

  • Base64Utils 类
package com.zengchen.common.utils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;


public class Base64Utils {

    /**
     * <p>
     * BASE64字符串解码为二进制数据
     * </p>
     *
     * @param base64
     * @return
     * @throws Exception
     */
    public static byte[] decode(String base64) throws Exception {
        return Base64.getMimeDecoder().decode(base64);
    }

    /**
     * <p>
     * 二进制数据编码为BASE64字符串
     * </p>
     *
     * @param bytes
     * @return
     * @throws Exception
     */
    public static String encode(byte[] bytes) throws Exception {
        return Base64.getMimeEncoder().encodeToString(bytes);
    }

    /**
     * 将字符串进行压缩并转换成base64字符
     *
     * @param data (非空字符串)
     * @return 压缩将转换成base64的字符串
     */
    public static String zipBase64(String data) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        GZIPOutputStream gzip = new GZIPOutputStream(bos);
        gzip.write(data.getBytes());
        gzip.finish();
        return Base64.getEncoder().encodeToString(bos.toByteArray());
    }

    /**
     * 将字符串进行base64解码并进行解压
     *
     * @param data 被压缩并转换成base64的字符串(非空)
     * @return
     */
    public static String base64Unzip(String data) throws IOException {
        ByteArrayInputStream bis = new ByteArrayInputStream(Base64.getDecoder().decode(data));
        GZIPInputStream gzip = new GZIPInputStream(bis);
        byte[] buf = new byte[16384];
        int num = -1;
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            while ((num = gzip.read(buf, 0, buf.length)) != -1) {
                bos.write(buf, 0, num);
            }
            bos.flush();
            return new String(bos.toByteArray());
        }
    }
}
  • install common
    在这里插入图片描述
    在这里插入图片描述

3.2.2 user-center依赖common

  • user-sever的pom里添加依赖
<dependency>
            <groupId>com.zc</groupId>
            <artifactId>common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

3.3 测试JWT

  • 生成token
    秘钥对长度有限制,太短的会报异常,这里token失效时间是1小时
public static void main(String[] args) throws Exception {
        Long expirationTimeInSecond = 3600L; // 一个小时
        String secret = "aaabbbcccdddeeef1111111111111111111111111111111111111";

        Map<String,Object> payloadMap = new HashMap<>();
        payloadMap.put("id",1);
        payloadMap.put("username","一粒尘埃");
        String token = JWTUtils.generateToken(payloadMap,secret,expirationTimeInSecond);
        log.info("token: "+token);
}

在这里插入图片描述
生成的token分成三段,以 “.” 相连

  • 测试token的有效性,header,payload和失效时间
    token的值是刚才控制台里打印的
String token = "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiLkuIDnspLlsJjln4MiLCJpYXQiOjE1NjcyNjI1NDUsImV4cCI6MTU2NzI2NjE0NX0.MyUgnwEc9SUapRxB7I7rM_1c4oqRnq98XNaXCEp8plU";
        log.info("jwt 有效性: " + JWTUtils.validateToken(token,secret));

        String header = JWTUtils.getInfo(token.split("\\.")[0]);
        log.info("jwt header: " + header);

        String payload = JWTUtils.getInfo(token.split("\\.")[1]);
        log.info("jwt payload: " + payload);

        LocalDateTime createDateTime =LocalDateTime.ofEpochSecond(1567262545,0, ZoneOffset.ofHours(8));
        LocalDateTime expireDateTime =LocalDateTime.ofEpochSecond(1567266145,0, ZoneOffset.ofHours(8));
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        log.info("创建时间:"+ dtf.format(createDateTime));
        log.info("失效时间:"+ dtf.format(expireDateTime));

结果,生成时间和失效时间正好间隔1个小时
在这里插入图片描述

4.登录发放Token

我是用微信小程序登录的,openId是用户标识,这章代码,读者仅能参

4.1 后端和前端代码

  • 后端代码 user-center的 LoginController
    主要就是第 5 步,发放 token
package com.zengchen.user.controller;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zengchen.common.bean.ResponseVO;
import com.zengchen.common.enums.ResponseCode;
import com.zengchen.common.utils.JWTUtils;
import com.zengchen.common.utils.OkhttpUtil;
import com.zengchen.user.entity.Member;
import com.zengchen.user.service.MemberService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * 前端控制器
 * </p>
 *
 * @author zengchen123
 * @since 2019-09-04
 */
@RestController
@Slf4j
public class LoginController {

    @Value("${wx.appid}")
    private String appid;

    @Value("${wx.secret}")
    private String secret;

    @Value("${wx.code2Session_url}")
    private String code2Session_url;

    @Value("${jwt.secret}")
    private String jwtSecret;

    @Value("${jwt.expirationTimeInSecond}")
    private String jwtExpirationTimeInSecond;

    @Autowired
    private MemberService memberService;

    @PostMapping("login")
    public Object login(@RequestBody String requestBody) {
        Map<String,Object> responseData = new HashMap<>();
        try {
            log.info("login with requestBody={}", requestBody);
            // 1,获取请求参数
            JSONObject requestOjt = JSON.parseObject(requestBody);
            String tempCode = requestOjt.getString("code");
            JSONObject userInfo = requestOjt.getJSONObject("userInfo");

            // 2,检查请求参数
            if (StringUtils.isBlank(tempCode) || StringUtils.isBlank(userInfo.getString("gender"))) {
                return ResponseVO.fail(ResponseCode.ERROR_PARAMS);
            }

            // 3.调用auth.code2Session,获取openid
            String targetUrl = code2Session_url.replace("APPID", appid)
                    .replace("SECRET", secret).replace("JSCODE", tempCode);
            log.info("auth.code2Session targetUrl = {}", targetUrl);
            String response = OkhttpUtil.get(targetUrl);
            log.info("auth.code2Session response = {}", response);
            JSONObject responseOjt = JSON.parseObject(response);
            String openid = responseOjt.getString("openid");
            if (StringUtils.isBlank(openid)) {
                String errMsg = responseOjt.getString("errmsg");
                return ResponseVO.fail(ResponseCode.ERROR_LOGIN.getCode(), errMsg);
            }

            // 4.检查用户是否已经注册,未注册的先注册
            log.info("检查是否注册,openid = {}",openid);
            Member member = memberService.getOne(new QueryWrapper<Member>().eq("wx_id", openid));
            if(null == member){
                log.info("用户未注册");
                member = new Member();
                member.setWxId(openid);
                member.setSex(userInfo.getString("gender"));
                member.setHeadUrl(userInfo.getString("avatarUrl"));
                member.setNickname(userInfo.getString("nickName"));
                member.setCreateTime(new Date());
                member.setUpdateTime(member.getCreateTime());
                memberService.save(member);
            }

            // 5.发放 Token
            Map<String,Object> claims = new HashMap<>();
            claims.put("userId",member.getId());
            claims.put("userName",member.getNickname());
            claims.put("headUrl",member.getHeadUrl());
            String token = JWTUtils.generateToken(claims,jwtSecret,Long.valueOf(jwtExpirationTimeInSecond));
            log.info("发放 token = {}",token);
            responseData.put("token",token);
            return ResponseVO.success(responseData);
        } catch (Exception e) {
            log.error("登录异常", e);
            return ResponseVO.fail(ResponseCode.ERROR_LOGIN);
        }
    }

}

4.2 测试登录发放Token

点击获取用户图像昵称,再点击弹窗上面的 “允许”
在这里插入图片描述
在这里插入图片描述
服务端日志,token已经发放:
在这里插入图片描述

5.登录状态验证

5.1 登录状态验证的几种方式

  • Servlet过滤器
    登录验证可以用过滤器来实现,从请求的Header里取出Token,进而判断这个Token是否有效
  • 拦截器
    这种方式和过滤器类似,优势是可以操作springmvc相关的api
  • Spring AOP
    自定义一个注解,在需要验证登录状态的方法上加上这个注解,我比较喜欢这种方式

5.2 AOP切面方式的实现

5.2.1 加依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

5.2.2 编写代码

  • 定义注解@CheckLogin
package com.zengchen.user.auth;

public @interface CheckLogin {
}
  • 定义切面CheckLoginAspect.java
package com.zengchen.user.auth;

import com.zengchen.common.bean.ServerException;
import com.zengchen.common.utils.JWTUtils;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

@Aspect
@Component
@Slf4j
public class CheckLoginAspect {

    @Value("${jwt.secret}")
    private String jwtSecret;

    @Before("@annotation(com.zengchen.user.auth.CheckLogin)")
    public void checkLogin(){
        log.info("checkLogin start.......");
        // 1.从请求的Header里取出token
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = attributes.getRequest();
        String token = request.getHeader("WX-TOKEN");
        log.info("checkLogin token = {}",token);
        // 2.检查token是否有效
        Boolean isValid = JWTUtils.validateToken(token, jwtSecret);
        if (!isValid) {
            log.info("checkLogin token 无效,未登录!");
            throw new ServerException("0001");
        }
        log.info("checkLogin token 有效,已登录!");
        // 3.将用户id存放到 Attribute 里
        Claims claimsFromToken = JWTUtils.getClaimsFromToken(token, jwtSecret);
        request.setAttribute("userId", claimsFromToken.get("userId"));
    }
}
  • 写测试接口
@RestController
@Slf4j
@RequestMapping("/member")
public class MemberController {

    @Autowired
    private MemberService memberService;

    @GetMapping("/checkLogin")
    @CheckLogin
    public Object getMember(HttpServletRequest request) {
        Integer userId = (Integer)request.getAttribute("userId");
        log.info("从attribute里取出 userId = {}",userId.intValue());
        Member member = memberService.getOne(new QueryWrapper<Member>().eq("id", userId));
        log.info("根据userId查询用户信息,{}",member);
        return ResponseVO.success(member);
    }
}

5.2.3 测试@CheckLogin

http://localhost:8082/member/checkLogin,Header里传 WX-TOKEN,查询用户信息

  • 有效登录测试
    使用的Token是第4节下发的,有效期一个星期,还可以用
    在这里插入图片描述
    后台日志:
    在这里插入图片描述
  • 无效登录测试
    把token最后的s删掉
    在这里插入图片描述
    后台日志:
    在这里插入图片描述
    登录状态检查完成!

6.Feign传递Token

我们选择的认证方式是每个微服务都对Token进行验证,如果Feign调用的时候,不带上Token,下一个微服务用什么去验证呢?下面使用FeignRequestInterceptor拦截器实现这个功能

  • 定义拦截器
public class TokenTransferInterceptor implements RequestInterceptor {
    /**
     * Called for every request. Add data using methods on the supplied {@link RequestTemplate}.
     *  将 WX-TOKEN 传递到下一个请求里
     * @param template
     */
    @Override
    public void apply(RequestTemplate template) {
        // 1.从请求的Header里取出token
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = attributes.getRequest();
        String token = request.getHeader("WX-TOKEN");
        // 2.存入token
        if(StringUtils.isNotBlank(token)){
            template.header("WX-TOKEN",token);
        }
    }
}
  • 配置拦截器
    配置项是 requestInterceptors
feign:
  sentinel:
    # 开启 sentinel 支持
    enabled: true
  client:
    config:
      # 全局配置
      default:
        loggerLevel: full #basic
        requestInterceptors:
          - com.zengchen.content.feignclient.interceptor.TokenTransferInterceptor
  • 测试
    测试逻辑:在user-center的方法上加上@CheckLogin注解,content-center使用Feign访问user-center的这个方法,测试token是否传递到了user-center
    content-center日志,token有效:
    在这里插入图片描述
    user-center日志,token也有了,说明token已经传递过来了:
    在这里插入图片描述

7.关于请求安全,Token安全性个人的一点想法

7.1 签名起了什么作用?

所谓签名就是加密,例如:https://www.xxxx.com/s?param1=a&param2=b&param3=c&sign=asfdljgljoixcnogn,三个参数param1param2param3,再加上一个sign签名,sign就是三个参数+secretKey的加密字符串

  • 作用
    保证请求参数不会被篡改
  • 如何保证?
    请求方在发送请求之前,把请求参数按照参数名从小到大(或者从大到小)顺序排列好,再加上secretKey形成一个新的字符串,然后加密,得到sign值,例如
secretKey=""123456
sign = md5(param1=a&param2=b&param3=c&secretKey=""123456)

服务端在收到请求之后,也按照相同的拼装顺序凭借参数和secretKey,再次md5得到一个加密字符串,然后比较加密字符串和请求sign是否相同,相同说明请求在传输的过程中,参数没有被篡改,如果对不上,不是参数被改了,就是签名被改了,就是非法请求。
破坏分子改了我的请求参数,他没办法得到一个正确的sign,因为他不知道我的secretKey,知道secretKey的只有请求方和服务方,早商量好的

7.2 一般调用接口为什么要加时间戳?

按理说,上节7.1的签名机制已经可以保证参数的安全性了,为什么还要加上时间戳呢?

因为破坏分子不通过修改你的请求参数了来恶心你了,他可以抓取你整个的请求数据包,原封不动的多次发送请求搞破坏。这叫重放攻击

我看其他的文章里说,破坏分子要完成这个操作,花费的时间要远超过60s,这时候加上时间戳就很有必要了,时间戳也是请求参数之一,也是签名加密的一部分,所以时间戳也不会被修改,这样服务端就可以拿到这个请求的时间戳和当前时间做比较,如果当前时间比时间戳大60s,那说明这个请求不正常,因为一般请求也不会从发出,到接收请求花这么长时间的,所以超过60s的就算非法请求,时间戳的作用就体现出来了。你也可以定义成50s40s

7.3 加了时间戳为什么又要加随机数?

上节7.2通过时间戳比较来拦截请求,毕竟有个60s的空档,这是一个60s的很大的漏洞,随机数就可以补好这个漏洞,随机数怎么发挥它的作用呢?
每次发送请求,出了时间戳,再带上一个很大的随机数,例如0~10000000,请求一旦发送出去,破坏分子也没法修改这个随机数,随机数也是签名加密的一部分,如果改了,签名就对不上
服务端收到请求之后,先判断时间戳有没有大于60s,大于肯定就拒绝,如果时间戳没问题,再查一下Redis里有没有sign这个签名,如果没有就把sign存放到Redis中,超时时间设置为60s,和时间戳的临界值保持一致,并且给这次请求正常的返回。如果查到Redis已存在sign这个签名,说明这个请求已经在60s内请求一次了,属于非法请求

整个第4节都是个人所思所想,不能作为准则。App,小程序之类的客户端还好,毕竟代码不可见,浏览器端就不行了,js是公开的啊,怎么保密secretKey?而且我觉得只需要对需要登录操作的请求进行签名验证就可以了,公开接口做这个没有意义

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值