1. 首先、了解什么是会话

会话是指一个终端用户与交互系统进行通讯的过程

2. 会话跟踪的主要技术

2.1 URL重写技术
就是在URL结尾添加一个附加数据以标识该会话,会把会话ID通过URL的信息传递过去,以便在服务端进行识别不同的用户。

2.2 隐藏表单域
将会话ID添加到HTML表单元素中提交到服务器,此表单不再客户端显示。

2.3 cookie
Cookie是web服务器发送给客户端的一小段信息,客户端请求时可以读取该信息发送到服务器端,进而进行用户的识别。对于客户端的每次请求,服务器都会将KCookie发送到客户端,在客户端可以进行保存,以便下次使用。

2.4 session
在服务端会创建一个session对象,产生一个sessionID来标识这个session对象,然后将这个sessionID放入到Cookie中发送到客户端,下一次访问时,sessionID发送到服务器,在服务端进行识别不同的用户。

session是依赖Cookie的,如果Cookie被禁用,那么session也将失效,session默认的会话时长为30分钟。

2.5 Token
token是令牌,访问系统的凭证。
token是服务端生成的一串字符串,客户端首先会请求一个令牌,当第一次登录后,服务器会生成一个token并将此token返回给客户端后,以后客户端只需带上这个token请求数据即可,无需再次带上用户名和密码。

2.6 三者相同点与区别
相同点:都是用于身份验证或鉴权的,都是服务器产生的
区别:
1.cookie是保存在客户端,session是存储服务器
2.session保存在服务器的内存,默认是30分钟,token是保存在服务器的数据库里面,持久。

3. Token 令牌学习

3.1 流程图

在这里插入图片描述
① 客户端使用用户名和密码请求登录
② 服务端收到请求,验证用户名和密码
③ 验证成功后,服务端会签发一个token,再把这个token返回给客户端
④ 客户端收到token后可以把它存储起来,比如放到cookie中
⑤ 客户端每次向服务端请求资源时需要携带服务端签发的token,可以在cookie或者header中携带
⑥ 服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求数据

3.2 token
  1. token加密方式
    对称加密:DES,AES、
    双钥加密:RSA,一般用于金融项目里面做签名
    只加密不解密:MD5,SHA系列
    编码格式:Base64
    2.token有两种
    access_token:时效15分钟到2小时之间
    refresh_token:时效15天
3.3 JWT(JSON web Tokens)Json web 令牌(规范)

这种基于token的认证方式相比传统的session认证方式更节约服务器资源,并且对移动端和分布式更加友好。其优点如下:
① 支持跨域访问:cookie是无法跨域的,而token由于没有用到cookie(前提是将token放到请求头中),所以跨域后不会存在信息丢失问题
② 无状态:token机制在服务端不需要存储session信息,因为token自身包含了所有登录用户的信息,所以可以减轻服务端压力
③ 更适用CDN:可以通过内容分发网络请求服务端的所有资料
④ 更适用于移动端:当客户端是非浏览器平台时,cookie是不被支持的,此时采用token认证方式会简单很多
⑤ 无需考虑CSRF:由于不再依赖cookie,所以采用token认证方式不会发生CSRF,所以也就无需考虑CSRF的防御

3.4 JWT结构

1.Header
JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存
{
“alg”: “HS256”,
“typ”: “JWT”
}

2.Payload
有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT

这些预定义的字段并不要求强制使用。除以上默认字段外,我们还可以自定义私有字段,一般会把包含用户信息的数据放到payload中,如下例:

{
“sub”: “1234567890”,
“name”: “Helen”,
“admin”: true
}
请注意,默认情况下JWT是未加密的,因为只是采用base64算法,拿到JWT字符串后可以转换回原本的JSON数据,任何人都可以解读其内容,因此不要构建隐私信息字段,比如用户的密码一定不能保存到JWT中,以防止信息泄露。JWT只是适合在网络中传输一些非敏感的信息

3.Signature
签名哈希部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密钥(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名
H M A C S H A 256 ( b a s e 64 U r l E n c o d e ( h e a d e r ) + " . " + b a s e 64 U r l E n c o d e ( p a y l o a d ) , s e c r e t ) HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret)
HMACSHA256(base64UrlEncode(header)+“.”+base64UrlEncode(payload),secret)
计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用.分隔,就构成整个JWT对象

3.5 JWT需要的依赖

在这里插入图片描述

3.6 JWT的获取与验证流程
package edu.zhku.fire_ant_project;

import org.apache.commons.codec.binary.Base64;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;

@SpringBootTest
public class FireAntProjectApplicationTest {
   //创建header
    //@Test
    public String getHeader() throws UnsupportedEncodingException {
        String header="{\"arg\":\"HS256\",\"typ\":\"JWT\"}";
        //把header的Json转成Base64URL编码的字符串
        String header64=Base64.encodeBase64URLSafeString(header.getBytes("UTF-8"));
        System.out.println("header64="+header64);
        return header64;
    }

    //创建Payload
   // @Test
    public String getPayload() throws UnsupportedEncodingException {
        String payload="{\"sub\":\"username\",\"id\":\"1001\",\"role\":\"admin\"}";
        String payload64=Base64.encodeBase64URLSafeString(payload.getBytes("UTF-8"));
        System.out.println("play="+payload64);
        return payload64;
    }

    //创建签名Signature
   // @Test
    public void getSignature() throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
        String secret = "h1d444wt65ywj454et45hdh08797hjh1";
        System.out.println(secret.length());

        //创建Mac类
        Mac mac=Mac.getInstance("HmacSHA256");
        //创建安全的密钥对象
        SecretKeySpec spec=new SecretKeySpec(secret.getBytes("UTF-8"),"HmacSHA256");

        //让Mac对象使用指定的密钥对象
        mac.init(spec);

        //准备数据
        String data=getHeader()+"."+getPayload();

        //生成签名值
        byte[] bytes=mac.doFinal(data.getBytes("UTF-8"));

        //把bytes转为String
        String signMsg=Base64.encodeBase64URLSafeString(bytes);

        System.out.println("sign="+signMsg);

        //组成完整的jwt
        String jwt = data+"."+signMsg;
        System.out.println("jwt="+jwt);


    }
    //验证签名值
    @Test
    public void checkSign() throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
        //密钥
        String secret="h1d444wt65ywj454et45hdh08797hjh1";
        //jwt数据
        String jwt="eyJhcmciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +
                "." +
                "eyJzdWIiOiJ1c2VybmFtZSIsImlkIjoiMTAwMSIsInJvbGUiOiJhZG1pbiJ9" +
                "." +
                "vD3nhQz_gVlqV6FLfxW357Mi313bThc2ajyhVic1XNQ";

        //验证签名:使用和创建时一样的算法,再计算出一个签名值,和jwt比较
        String data =jwt.substring(0,jwt.lastIndexOf("."));
        //获取jwt中的签名
        String signature=jwt.substring(jwt.lastIndexOf(".")+1);
//        System.out.println("data="+data);
//        System.out.println("signature="+signature);
        //重新计算签名值

        Mac mac =Mac.getInstance("HmacSHA256");
        SecretKeySpec spec=new SecretKeySpec(secret.getBytes("UTF-8"),"HmacSHA256");
        mac.init(spec);
        byte[] bytes=mac.doFinal(data.getBytes("UTF-8"));
        String sign=Base64.encodeBase64URLSafeString(bytes);
        System.out.println("signature=sign:"+(signature.equals(sign)));
    }
}

3.7JWT的使用方式
  1. 首先用户登录校验后,服务器会返回jwt token
  2. 客户端获取jwt token,使用cookie或localStorage存储jwt token,但这样不能跨域,采用的方式放在请求header的Authorization中,也可以放在get或者post请求中
  3. 客户端访问其他api接口,传递token给服务器,服务器认证token后,返回给请求的数据。

Authorization:Bearer

3.8 jjwt的使用(创建JWT方式)
1. jjwt需要的依赖

在这里插入图片描述

2.测试类
 //jjwt的使用
    @Test
    public void test01(){
        //创建一个key使用HS256算法
        Key key= Keys.secretKeyFor(SignatureAlgorithm.HS256);
        //使用jjwt创建jwt
        String jws=Jwts.builder().setSubject("username").signWith(key).compact();
        System.out.println("jws:\n"+jws);

        jws="eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmaXJlX2FudF9wcm9qZWN0X2FkbWluIn0.ecJI1-6rOtyuPvK8ZxsxddE0nly1CQXxoNfWi9sIhdc";
        //验证JWT,key已刷新,则会抛出异常
        Claims body=Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(jws).getBody();
        String subject=body.getSubject();
        System.out.println("subject:\n"+subject);
    }
3.RSA算法支持
   //RSA算法支持,私钥创建jwt,公钥验证jwt
    @Test
    public void test02(){
        KeyPair keyPair =Keys.keyPairFor(SignatureAlgorithm.RS256);
        PrivateKey aPrivate=keyPair.getPrivate();
        String jws=Jwts.builder().setSubject("username").signWith(aPrivate).compact();
        System.out.println("jws:\n"+jws);

        jws="eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJmaXJlX2FudF9wcm9qZWN0X2FkbWluIn0.JiCzArgVdQNGkpFLaxa0h11KGJsJrj";
        //验签,使用公钥
        try {
            String subject = Jwts.parserBuilder().setSigningKey(keyPair.getPublic()).build().parseClaimsJws(jws).getBody().getSubject();
            System.out.println("subject:\n" + subject);
        }catch (SignatureException se){
            se.printStackTrace();
            System.out.println("false");
        }catch (Exception e){
            e.printStackTrace();
        }
4.使用自己的key
    //使用自己的key
    @Test
    public void privateKey(){
        String secret = UUID.randomUUID().toString().replaceAll("-","");
        //安全的密钥对象
        SecretKey secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
        String token = Jwts.builder().setSubject("username")
                .signWith(secretKey,SignatureAlgorithm.HS256)
                .compact();
        System.out.println(secret.length());
        System.out.println(token);
        //验证JWT,key已刷新,则会抛出异常
        Claims body=Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token).getBody();
        String subject=body.getSubject();
        System.out.println("subject:\n"+subject);

    }

//规定token时间

**   public void privateKey(){
        Date date = new Date(System.currentTimeMillis() + 60000);
//        String secret = UUID.randomUUID().toString().replaceAll("-","");
//        //安全的密钥对象
       String secret ="dd9b84d01c0940c6a5723a744d0d66a8";
        SecretKey secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
//      String token = Jwts.builder().setSubject("fire_ant_project_admin")
//                .signWith(secretKey,SignatureAlgorithm.HS256)
//                .setExpiration(date)
//                .compact();
       String token ="eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmaXJlX2FudF9wcm9qZWN0X2FkbWluIiwiZXhwIjoxNjQyOTA5NTI0fQ.tHfJm-MsEaaqlakOFoRvWMog-weCeSGlJVlGV3YyVPs";
        System.out.println(token);
        //验证JWT,key已刷新,则会抛出异常
        Claims body=Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token).getBody();
        String subject=body.getSubject();
        System.out.println("subject:\n"+subject);
    }

    //**

3.9 前端如何使用token,并且加入请求头

1.可以通过cookie存储
document.cookie=token;
2.加入请求头Authoriziation
① headers:{“Authoriziation”:token}
存储在这里插入图片描述获取:
在这里插入图片描述

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐