session登录、企业级Token登录实现流程、JWT概念详解、JwtUtil工具类

众所周知,http请求是无状态的,每次发送请求的时候,对方都不知道是谁发是请求。
实际情况,在登入系统后,需要系统记住是谁登入了,方便系统的权限管理,保证系统的安全性。

1、Session实现登入逻辑

Session是一块保存在服务器端的内存空间,一般用于保存用户的会话信息!
Session原理:
1、Session会为每一次会话分配一个Session对象
2、同一个浏览器发起的多次请求,同属于一次会话(Session)
3、首次使用到Session时,服务器会自动创建Session,并创建Cookie存储SessionId发送回客户端

S e s s i o n 登录实现流程: \color{red} {Session登录实现流程:} Session登录实现流程:

  • 1、用户输入用户名密码点击登入按钮,服务端匹配用户名密码,然后执行request.getSession()会自动创建session对象(同时会创建一个cookie,cookie的name是JSESSIONID),然后把用户信息存入session中,后面根据session中是否有用户信息来判断该用户是否登录。
  • 2、服务端自动返回给浏览器一个sessionID,存入浏览器的cookie中。
    每次请求任意接口,浏览器都会带着这个cookie中的sessionID,服务器自动通过sessionID去寻找对应的session,如果session中已经有了这个用户的登陆信息,则说明用户已经登陆过了。
  • 3、request.getSession()方法,如果不存在session,会自动创建session。如果存在session,会自动根据带来的sessionID,返回对应的session。
  • 4、实际实现中,会写个拦截器,每次拦截session是否为空,判断是否登录过。

代码实现: \color{red} {代码实现:} 代码实现:

@RestController
@RequestMapping("session")
public class SessionController {

    @RequestMapping("/login")
    public String login(HttpServletRequest request, String userName, String passWord) {
        if ("admin".equals(userName) && "123456".equals(passWord)) {
            System.out.println("login success");
            //如果不存在session,会自动创建session
            HttpSession session = request.getSession();
            //后面可以用session中是否存在username来判断用户是否登录。
            session.setAttribute("username", userName);
            if (session.isNew()) {
                System.out.println("Session create success:" + session.getId());
            } else {
                System.out.println("Session is exit:" + session.getId());
            }
            return "success";
        } else {
            System.out.println("login fail");
            return "fail";
        }
    }
}

S e s s i o n 作为登入的弊处: \color{red} {Session作为登入的弊处:} Session作为登入的弊处:

  • 1、服务器压力大,通常Session是存储在内存中的,每个用户通过认证之后都会将session数据保存在服务器的内存中,而当用户量增大时,服务器的压力增大。
  • 2、重启服务,session清空,需要重新登入。
  • 3、Session共享:现在很多应用都是分布式集群,需要我们做额外的操作进行Session共享。

session基本已经不会应用到企业级登录了


2、Token实现登入逻辑

Token是现在最常用的方式。

T o k e n 登录实现流程: \color{red} {Token登录实现流程:} Token登录实现流程:

  • 1、用户发出登录请求,带着用户名和密码到服务器进行验证,服务器验证成功就在后台生成一个token返回给客户端。token就是一个加密后的字符串(比如用JWT生成的token字符串)。

  • 2、客户端将token存储到cookie中,服务端将token存储到redis中,可以设置存储token的有效期。

  • 3、后续客户端的每次请求资源都必须携带token,服务端接收到请求首先校验是否携带token,以及token是否和redis中的匹配,若不存在或不匹配直接拦截返回错误信息(如未认证)。

  • 4、其实我个人认为,用jwt生成的token和用MD5(或者其他加密方式) 生成的token,最近认证时的逻辑都是对比前端传过来的token是否一致。 要说JWT有啥优势,可能就是JWT中不包含用户密码吧,还有人说用JWT能减轻服务器压力,那可能就是减轻加密的压力,或者和数据库比对的压力。

而JWT就是上述流程当中token的一种具体实现方式,其全称是JSON Web Token。

3、JWT概念详解

JWT 是由三段信息构成的,将这三段信息文本用 . 连接一起就构成了 JWT 字符串。JWT 的三个部分依次为头部:Header,负载:Payload 和签名:Signature。

Header
Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。

{
  "alg": "HS256",
  "typ": "JWT"
}

上面代码中,alg 属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ 属性表示这个令牌(token)的类型(type),JWT 令牌统一写为 JWT。

最后,将上面的 JSON 对象使用 Base64URL 算法转成字符串。

Payload

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的有效信息。可以把用户信息,token过期时间存进去,但是不要写用户密码写进去。

Signature
Signature 部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

token的组成是这样的:header(头).payload(负载).signature(签名),header和payload都是base64编码,可以直接使用base64工具解析。JWT在线解析
在这里插入图片描述

JWT 的使用方式

客户端收到服务器返回的 JWT 之后需要在本地做保存。保存到浏览器的key叫Authorization,value就是token,记得前缀手动拼接字符串 Bearer 空格 。

Authorization: Bearer <token>

JWT 的特性

1、JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。项目中我们也很少对JWT进行二次加密

2、JWT 不加密的情况下,不能将秘密数据写入 JWT。

4、java工具类生成JWTToken

maven引入jwt包

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

JwtUtil工具类

import io.jsonwebtoken.*;
import org.apache.tomcat.util.codec.binary.Base64;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @author zhanggang
 * @since 2023/11/21 10:29
 */

public class JwtUtil {

    public static final String JWT_ID = UUID.randomUUID().toString();
    /**
     * jwt 加密解密密钥(可自行填写Base64加密)
     * 必须使用最少88位的Base64对该令牌进行编码
     */
    private static final String JWT_SECRET = "ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI=";
    /**
     * 创建一个SimpleDateFormat对象,定义日期和时间的格式
     */
    private static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /**
     * 过期时间,单位为秒
     **/
    private static final long EXPIRATION = 30L;

    private static SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

    public static void main(String[] args) {
        //jwt中的Payload部分,切记不要存放密码
        HashMap<String, Object> claimsMap = new HashMap<>();
        claimsMap.put("userId", "1");
        claimsMap.put("userName", "zhangsan");
        //生成jwtToken
        String jwtToken = createJwt(claimsMap);
        System.out.println(jwtToken);
        //验证并解析token
        Claims claims = verifyToken(jwtToken);
        System.out.println("签发时间:"+ formatter.format(claims.getIssuedAt()));
        System.out.println("到期时间:"+ formatter.format(claims.getExpiration()));

        //延期token
        String updateJwtTokenExpiration = updateJwtTokenExpiration(jwtToken, 120L);
        Claims claimsYq = verifyToken(updateJwtTokenExpiration);
        System.out.println("签发时间:"+ formatter.format(claims.getIssuedAt()));
        System.out.println("到期时间:"+ formatter.format(claimsYq.getExpiration()));

        System.out.println(updateJwtTokenExpiration);
    }

    /**
     * @param claims
     * @return
     */
    public static String createJwt(Map<String, Object> claims) {
        //指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
        //采用什么算法是可以自己选择的,不一定非要采用HS256
        Date expireDate = new Date(System.currentTimeMillis() + EXPIRATION * 1000);

        SecretKey secretKey = generalKey();
        //jwt中的Header部分
        Map<String, Object> headerMap = new HashMap<>();
        headerMap.put("alg", "HS256");
        headerMap.put("typ", "JWT");

        //下面就是在为payload添加各种标准声明和私有声明了,new一个JwtBuilder,设置jwt的body
        JwtBuilder builder = Jwts.builder()
                .setHeader(headerMap)
                //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
                .setId(JWT_ID)
                //iat: jwt的签发时间
                .setIssuedAt(new Date())
                //设置过期时间
                .setExpiration(expireDate)
                //设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, secretKey);
        return builder.compact();
    }

    /**
     * 验证并解析jwt
     *
     * @param token token
     * @return 结果集
     */
    public static Claims verifyToken(String token) {
        //签名秘钥,和生成的签名的秘钥一模一样
        SecretKey key = generalKey();
        Claims claims;
        try {
            //得到DefaultJwtParser
            claims = Jwts.parser()
                    //设置签名的秘钥
                    .setSigningKey(key)
                    .parseClaimsJws(token).getBody();
        } catch (Exception e) {
            System.out.println("解析token失败:"+e.getMessage());
            e.printStackTrace();
            claims = null;
        }
        return claims;
    }

    /**
     * 刷新token并设置过期时间
     *
     * @param token                 旧的token
     * @param newExpirationInMillis 过期时间,单位秒
     * @return 生成新的jwt token
     */
    public static String updateJwtTokenExpiration(String token, Long newExpirationInMillis) {
        newExpirationInMillis = 1000 * newExpirationInMillis;
        SecretKey secretKey = generalKey();
        Claims claims = Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token)
                .getBody();

        claims.setExpiration(new Date(System.currentTimeMillis() + newExpirationInMillis));
        return Jwts.builder()
                .setClaims(claims)
                .setId(JWT_ID)
                .signWith(signatureAlgorithm, secretKey)
                .compact();
    }

    /**
     * 由字符串生成加密key
     *
     * @return 结果
     */
    public static SecretKey generalKey() {
        byte[] keyBytes = Base64.decodeBase64(JWT_SECRET);
        return new SecretKeySpec(keyBytes, signatureAlgorithm.toString());
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北漂IT民工_程序员_ZG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值