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

文章目录

  • 1.有状态 vs 无状态
    • 1.1 有状态的做法
    • 1.2 无状态的做法
    • 1.3 优缺点对比
  • 2.微服务的认证方案
    • 2.1 处处安全方案
    • 2.2 网关认证授权,内部裸奔
    • 2.3 处处校验Token
  • 3.无状态的JWT (Json Web Token)
    • 3.1 释义
    • 3.2 user-center引入JWT
      • 3.2.1 新建common项目
      • 3.2.2 user-center依赖common
    • 3.3 测试JWT
  • 4.登录发放Token
    • 4.1 后端和前端代码
    • 4.2 测试登录发放Token
  • 5.登录状态验证
    • 5.1 登录状态验证的几种方式
    • 5.2 AOP切面方式的实现
      • 5.2.1 加依赖
      • 5.2.2 编写代码
      • 5.2.3 测试@CheckLogin
  • 6.Feign传递Token
  • 7.关于请求安全,Token安全性个人的一点想法
    • 7.1 签名起了什么作用?
    • 7.2 一般调用接口为什么要加时间戳?
    • 7.3 加了时间戳为什么又要加随机数?

特别声明:整理自慕课网大目师兄的微服务视频,链接: 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地址发生改变,把用户请求发送到其它实例,又会发生找不到登录信息的情况
    学习 spring-cloud-aibaba第七篇,JWT认证授权_第1张图片
  • 用第三方存储登录信息
    如下图,把登录信息存放到Session Store里,可以是Redis,也可以是memberCache,甚至关系型数据库也行啊,开玩笑了,当然是nosql比较好了,redis,membercache都是nosql
    学习 spring-cloud-aibaba第七篇,JWT认证授权_第2张图片
    评论:这样不管用户的请求被转发到哪个实例,都能从第三方存储里找到他的登录信息了,如果Session Store没有到达存取瓶颈,这样做是可以的,一般都够用了。
    弊端
    1.访问量很大的时候,Session Store也忙不过来了,那么这个方案就还需要改进
    2.Session Store挂了,所有微服务都不能用了
    3.Session Store迁移了,所有微服务都要改连接地址

1.2 无状态的做法

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

图示
学习 spring-cloud-aibaba第七篇,JWT认证授权_第3张图片
弊端token发放出去,服务器端就无法控制这个token了,不能让它马上失效,不能让这个用户立刻处于下线状态,毫无掌控力

1.3 优缺点对比

学习 spring-cloud-aibaba第七篇,JWT认证授权_第4张图片

2.微服务的认证方案

2.1 处处安全方案

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

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

认证过程:用户的登录授权,Token的解析判断,全网关里实现。后面的微服务不再判断这个请求有没有登录了
学习 spring-cloud-aibaba第七篇,JWT认证授权_第5张图片
优点:逻辑简单,性能好
缺点:后面微服务毫不设防,有风险

2.3 处处校验Token

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

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

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

3.1 释义

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

  • 组成
    学习 spring-cloud-aibaba第七篇,JWT认证授权_第7张图片
    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项目
    学习 spring-cloud-aibaba第七篇,JWT认证授权_第8张图片
  • pom文件


    4.0.0

    com.zc
    common
    0.0.1-SNAPSHOT

    
        
        
            io.jsonwebtoken
            jjwt-api
            0.10.7
        
        
            io.jsonwebtoken
            jjwt-impl
            0.10.7
            runtime
        
        
            io.jsonwebtoken
            jjwt-jackson
            0.10.7
            runtime
        
    


    
        
            
                org.apache.maven.plugins
                maven-compiler-plugin
                
                    8
                    8
                
            
        
    


  • 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 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 {

    /**
     * 

* BASE64字符串解码为二进制数据 *

* * @param base64 * @return * @throws Exception */ public static byte[] decode(String base64) throws Exception { return Base64.getMimeDecoder().decode(base64); } /** *

* 二进制数据编码为BASE64字符串 *

* * @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
    学习 spring-cloud-aibaba第七篇,JWT认证授权_第9张图片
    学习 spring-cloud-aibaba第七篇,JWT认证授权_第10张图片

3.2.2 user-center依赖common

  • user-sever的pom里添加依赖

            com.zc
            common
            0.0.1-SNAPSHOT
        

3.3 测试JWT

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

        Map 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个小时
学习 spring-cloud-aibaba第七篇,JWT认证授权_第11张图片

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;

/**
 * 

* 前端控制器 *

* * @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 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().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 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); } } }
  • 前端小程序代码
    点击这个按钮,向后台发送login的请求,请求的url是:http://localhost:8040/user-center/login,经过网关访问user-center
    学习 spring-cloud-aibaba第七篇,JWT认证授权_第12张图片

4.2 测试登录发放Token

点击获取用户图像昵称,再点击弹窗上面的 “允许”
学习 spring-cloud-aibaba第七篇,JWT认证授权_第13张图片
学习 spring-cloud-aibaba第七篇,JWT认证授权_第14张图片
服务端日志,token已经发放:
学习 spring-cloud-aibaba第七篇,JWT认证授权_第15张图片

5.登录状态验证

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

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

5.2 AOP切面方式的实现

5.2.1 加依赖


            org.springframework.boot
            spring-boot-starter-aop
        

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().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节下发的,有效期一个星期,还可以用
    学习 spring-cloud-aibaba第七篇,JWT认证授权_第16张图片
    后台日志:
    学习 spring-cloud-aibaba第七篇,JWT认证授权_第17张图片
  • 无效登录测试
    把token最后的s删掉
    学习 spring-cloud-aibaba第七篇,JWT认证授权_第18张图片
    后台日志:
    学习 spring-cloud-aibaba第七篇,JWT认证授权_第19张图片
    登录状态检查完成!

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有效:
    学习 spring-cloud-aibaba第七篇,JWT认证授权_第20张图片
    user-center日志,token也有了,说明token已经传递过来了:
    在这里插入图片描述

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

7.1 签名起了什么作用?

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

  • 作用
    保证请求参数不会被篡改
  • 如何保证?
    请求方在发送请求之前,把请求参数按照参数名从小到大(或者从大到小)顺序排列好,再加上secretKey形成一个新的字符串,然后加密,得到sign值,例如
secretKey=""123456
sign = md5(param1=a¶m2=b¶m3=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?而且我觉得只需要对需要登录操作的请求进行签名验证就可以了,公开接口做这个没有意义

你可能感兴趣的:(spring,cloud,alibaba,java,spring,cloud)