首页 > 其他分享 >JWT单点登录

JWT单点登录

时间:2024-09-03 14:25:10浏览次数:10  
标签:单点 String 登录 JWT token import org

单点登录



概念:登录某集团的某一产品之后,访问其他产品的网站时就会是登录状态,比如登录QQ之后,进入QQ游戏的时候就是登录过的状态,具体实现方法有以下:

Redis+token实现单点登录:

生成一个随机字符串token,以token为key,用户信息为value存储在redis缓存中,用户访问时带着这个token就可以实现单点登录;

这里的token是无实际意义的,和用户信息无关的,否则用户每次登录的时候token都是同一个,容易被黑;

JWT实现单点登录:

这里jwt维护的也是一个token,但是这个token是有意义的,可以理解成用户信息的加密数据。通过这串加密数据就可以反向解出当前登录的是哪一个用户。

Redis+Token登录和校验流程:

登录->校验用户名密码->生成随机token->将token放入redis中并返回给前端->结束

校验->后端拦截请求,从header中获取token,如果没有就返回错误->根据token在redis中获取数据->如果有数据校验成功登录,否则登录校验失败->结束

JWT单点登录流程

登录->校验用户名密码->JWT工具包随机生成token->将token放入redis(也可以不放),并返回给前端->结束

校验->后端拦截请求,获取header中的token,如果没有就返回错误->使用工具包解密校验token->如果校验成功登录,否则登录校验失败->结束

JWT原理

Hutool参考文档

结构

  • Header 头部信息,主要声明了JWT的签名算法等信息
  • Payload 载荷信息,主要承载了各种声明并传递明文数据
  • Signature 签名,拥有该部分的JWT被称为JWS,也就是签了名的JWS,用于校验数据

整体结构是:

header.payload.signature

使用

JWT模块的核心主要是两个类:

  1. JWT类用于链式生成、解析或验证JWT信息。
  2. JWTUtil类主要是JWT的一些工具封装,提供更加简洁的JWT生成、解析和验证工作

JWT生成

  1. HS265(HmacSHA256)算法

    // 密钥

点击查看代码
      // 密钥
      byte[] key = "1234567890".getBytes();
      
      String token = JWT.create()
          .setPayload("sub", "1234567890")
          .setPayload("name", "looly")
          .setPayload("admin", true)
          .setKey(key)
          .sign();
  byte[] key = "1234567890".getBytes();
  
  String token = JWT.create()
      .setPayload("sub", "1234567890")
      .setPayload("name", "looly")
      .setPayload("admin", true)
      .setKey(key)
      .sign();

生成的内容为:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsIm5hbWUiOiJsb29seSJ9.536690902d931d857d2f47d337ec81048ee09a8e71866bcc8404edbbcbf4cc40
  1. 其他算法
点击查看代码
  
      // 密钥
      byte[] key = "1234567890".getBytes();
      
      // SHA256withRSA
      String id = "rs256";
      JWTSigner signer = JWTSignerUtil.createSigner(id, 
          // 随机生成密钥对,此处用户可自行读取`KeyPair`、公钥或私钥生成`JWTSigner`
          KeyUtil.generateKeyPair(AlgorithmUtil.getAlgorithm(id)));
      
      String token = JWT.create()
          .setPayload("sub", "1234567890")
          .setPayload("name", "looly")
          .setPayload("admin", true)
          .setSigner(signer)
          .sign();
  // 密钥
  byte[] key = "1234567890".getBytes();
  
  // SHA256withRSA
  String id = "rs256";
  JWTSigner signer = JWTSignerUtil.createSigner(id, 
      // 随机生成密钥对,此处用户可自行读取`KeyPair`、公钥或私钥生成`JWTSigner`
      KeyUtil.generateKeyPair(AlgorithmUtil.getAlgorithm(id)));
  
  String token = JWT.create()
      .setPayload("sub", "1234567890")
      .setPayload("name", "looly")
      .setPayload("admin", true)
      .setSigner(signer)
      .sign();
  1. 不签名JWT
点击查看代码
  
      //eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsIm5hbWUiOiJsb29seSJ9.
      String token = JWT.create()
          .setPayload("sub", "1234567890")
          .setPayload("name", "looly")
          .setPayload("admin", true)
          .setSigner(JWTSignerUtil.none())
          .sign()
  
  //eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsIm5hbWUiOiJsb29seSJ9.
  String token = JWT.create()
      .setPayload("sub", "1234567890")
      .setPayload("name", "looly")
      .setPayload("admin", true)
      .setSigner(JWTSignerUtil.none())
      .sign()

JWT解析

JWT验证

  1. 验证签名
点击查看代码
  
      String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." +
          "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsIm5hbWUiOiJsb29seSJ9." +
          "536690902d931d857d2f47d337ec81048ee09a8e71866bcc8404edbbcbf4cc40";
      
      // 密钥
      byte[] key = "1234567890".getBytes();
      
      // 默认验证HS265的算法
      JWT.of(rightToken).setKey(key).verify()
  
  String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." +
      "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsIm5hbWUiOiJsb29seSJ9." +
      "536690902d931d857d2f47d337ec81048ee09a8e71866bcc8404edbbcbf4cc40";
  
  // 密钥
  byte[] key = "1234567890".getBytes();
  
  // 默认验证HS265的算法
  JWT.of(rightToken).setKey(key).verify()
  1. 详细验证
点击查看代码

    String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." +
        "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsIm5hbWUiOiJsb29seSJ9." +
        "536690902d931d857d2f47d337ec81048ee09a8e71866bcc8404edbbcbf4cc40";
    
    JWT jwt = JWT.of(rightToken);
    
    // JWT
    jwt.getHeader(JWTHeader.TYPE);
    // HS256
    jwt.getHeader(JWTHeader.ALGORITHM);
    
    // 1234567890
    jwt.getPayload("sub");
    // looly
    jwt.getPayload("name");
    // true
    jwt.getPayload("admin");

除了验证签名,Hutool提供了更加详细的验证:validate,主要包括:

  • Token是否正确
  • 生效时间不能晚于当前时间
  • 失效时间不能早于当前时间
  • 签发时间不能晚于当前时间

使用方式如下:

String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJNb0xpIiwiZXhwIjoxNjI0OTU4MDk0NTI4LCJpYXQiOjE2MjQ5NTgwMzQ1MjAsInVzZXIiOiJ1c2VyIn0.L0uB38p9sZrivbmP0VlDe--j_11YUXTu3TfHhfQhRKc";

byte[] key = "1234567890".getBytes();
boolean validate = JWT.of(token).setKey(key).validate(0);

其他自定义详细验证见JWT验证-JWTValidator章节。

JWT存在的问题及解决方案讲解

1、token被解密

加盐值(密钥),每个项目的盐值不能一样

2、token被拿到第三方使用(别人包装你的页面使多个用户的操作走一个用户)

没啥好方法,使用限流

包装成工具类

点击查看代码
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.json.JSONObject;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTPayload;
import cn.hutool.jwt.JWTUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

public class JwtUtil {
    private static final Logger LOG = LoggerFactory.getLogger(JwtUtil.class);

    /**
     * 盐值很重要,不能泄漏,且每个项目都应该不一样,可以放到配置文件中
     */
    private static final String key = "Yubaibai12306";

    /**
     * 生成token
     * @param id
     * @param mobile
     * @return token
     */
    public static String createToken(Long id, String mobile) {
        DateTime now = DateTime.now();
        DateTime expTime = now.offsetNew(DateField.HOUR, 24);
        Map<String, Object> payload = new HashMap<>();
        // 签发时间
        payload.put(JWTPayload.ISSUED_AT, now);
        // 过期时间
        payload.put(JWTPayload.EXPIRES_AT, expTime);
        // 生效时间
        payload.put(JWTPayload.NOT_BEFORE, now);
        // 内容
        payload.put("id", id);
        payload.put("mobile", mobile);
        String token = JWTUtil.createToken(payload, key.getBytes());
        LOG.info("生成JWT token:{}", token);
        return token;
    }

    /**
     * 验证token
     * @param token
     * @return 校验结果
     */
    public static boolean validate(String token) {
        JWT jwt = JWTUtil.parseToken(token).setKey(key.getBytes());
        // validate包含了verify
        boolean validate = jwt.validate(0);
        LOG.info("JWT token校验结果:{}", validate);
        return validate;
    }

    /**
     * 解密token对应的内容
     * @param token
     * @return token对应内容对象
     */
    public static JSONObject getJSONObject(String token) {
        JWT jwt = JWTUtil.parseToken(token).setKey(key.getBytes());
        JSONObject payloads = jwt.getPayloads();
        payloads.remove(JWTPayload.ISSUED_AT);
        payloads.remove(JWTPayload.EXPIRES_AT);
        payloads.remove(JWTPayload.NOT_BEFORE);
        LOG.info("根据token获取原始内容:{}", payloads);
        return payloads;
    }

    public static void main(String[] args) {
        createToken(1L, "123");

        String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE3MjUyNjQ1MzMsIm1vYmlsZSI6IjEyMyIsImlkIjoxLCJleHAiOjE3MjUzNTA5MzMsImlhdCI6MTcyNTI2NDUzM30.lSeDNb5QAp_CH1A-nF5Xw5Qk2zyvd4L3KrDmw97gQvM";
        validate(token);

        getJSONObject(token);
    }
}

前端使用vuex保存登录信息

vuex或称store,用于存储全局变量,可用于各页面传递参数,或放置项目全局信息

state:定义一个全局变量
getters:获取变量时,做些额外的转换,如日期格式化
mutations:相当于java的setter,用于修改变量
actions:发起异步任务
modules:项目较大,变量较多时,可以模块化

缺点:页面刷新后,数据会丢失
vuex配合h5的session解决浏览器刷新问题

前端小技巧,使用:|| {} 为变量赋值,可防止空指针异常

演示gateway拦截器的使用

登录校验两个步骤:
前端请求带上token,放在header里
后端校验token有效性,在gateway里统一校验
gateway有多个拦截器时,使用order来确定拦截器的顺序

点击查看代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class Test1Filter implements GlobalFilter, Ordered {
    private static final Logger LOG = LoggerFactory.getLogger(Test1Filter.class);
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        LOG.info("Test1Filter");
        return chain.filter(exchange);
//        return exchange.getResponse().setComplete();
    }

    @Override
    public int getOrder() {
        return 1;
    }
}
点击查看代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class Test2Filter implements GlobalFilter, Ordered {
    private static final Logger LOG = LoggerFactory.getLogger(Test2Filter.class);
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        LOG.info("Test2Filter");
        return chain.filter(exchange);
//        return exchange.getResponse().setComplete();
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

测试结果:

结果
33:11.638 INFO  c.g.t.g.config.Test2Filter    :17   reactor-http-nio-2                    Test2Filter
33:11.638 INFO  c.g.t.g.config.Test1Filter    :17   reactor-http-nio-2                    Test1Filter

为gateway增加登录校验拦截器

登录拦截器
import com.guaigen.train.gateway.util.JwtUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class LoginMemberFilter implements GlobalFilter, Ordered {
    private static final Logger LOG = LoggerFactory.getLogger(LoginMemberFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getURI().getPath();

        if (path.contains("/admin")
            || path.contains("/hello")
            || path.contains("/member/member/login")
            || path.contains("/member/member/sendCode")) {
            LOG.info("不需要登录验证:{}", path);
            return chain.filter(exchange);
        }

        String token = exchange.getRequest().getHeaders().getFirst("token");
        LOG.info("会员登录验证开始, token:{}", token);
        if (token == null || token.isEmpty()) {
            LOG.info("token为空请求被拦截");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }

        // 验证token是否有效
        boolean validate = JwtUtil.validate(token);
        if (validate) {
            LOG.info("token有效,请求放行");
            return chain.filter(exchange);
        } else {
            LOG.info("token无效,请求拦截");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

标签:单点,String,登录,JWT,token,import,org
From: https://www.cnblogs.com/yubaibaibubai/p/18392885

相关文章

  • mssql windows 账户登录不了 要登录sa创建出来 才可以本地登录
    SQLSERVER登陆错误:18456 SQLWindows身份登录失败,错误码:18456先用sa进入EXECxp_instance_regreadN'HKEY_LOCAL_MACHINE',N'SOFTWARE\Microsoft\MicrosoftSQLServer\MSSQLServer',N'LoginMode'--启用TCP/IP协议EXECxp_instance_regwriteN'H......
  • 阿里云邮箱异地登录报警
    阿里云邮箱异地登录报警个人博客个人博客直达地址网站不断完善中里面拥有大量的脚本,并且源码完全开放欢迎纯白嫖。关注公众私信可免费写脚本效果展示功能概述这段Python代码用于从阿里云邮箱的API获取最近十分钟的用户登录活动记录,并将这些记录存储到MySQL数......
  • 华为防火墙SSL_VPN异地登录报警
    华为防火墙SSL_VPN异地登录报警脚本功能此Python脚本的主要功能是从Elasticsearch中检索登录成功的日志,检查用户的登录信息是否发生变化,并将相关信息存储到MySQL数据库中。如果检测到用户的登录IP地址或地理位置发生变化,则发送钉钉通知警告。如不知道es如何采集华为防火......
  • cas服务端自动登录
    应用场景在信任的第三方系统登录成功后,无需用户账号密码登录,实现后台cas自动登录具体流程1、第三方系统登录成功后,回调cas指定的方法;2、cas根据携带的用户信息,生成票据;3、带着票据进行cas登录;代码实现CASServer采用CASServer3.5.2版本,使用cas-server-core-3.5.2.jar包pr......
  • zdppy+vue3+onlyoffice文档管理系统实战 20240901 上课笔记 基于验证码登录功能基本完
    遗留的问题1、点击切换验证码2、1分钟后自动切换验证码点击切换验证码实现步骤:1、点击事件2、调用验证码接口3、更新验证码的值点击事件给图片添加点击事件:<img:src="'data:image/png;base64,'+captchaImg"style="width:100%;height:50px;margin-top:10......
  • 使用公钥登录 Linux 服务器
    使用公钥登录Linux服务器‍Linux上使用公钥登录在客户端上通过ssh-copy_id​将公钥写入到服务器的authorized_keys:[root@VM-4-11-centos~]#[email protected]/usr/bin/ssh-copy-id:INFO:Sourceofkey(s)tobeinstalled:"/root/.ssh/id_rsa.pub"/usr......
  • 使用公钥登录 Linux 服务器
    使用公钥登录Linux服务器‍Linux上使用公钥登录在客户端上通过ssh-copy_id​将公钥写入到服务器的authorized_keys:[root@VM-4-11-centos~]#[email protected]/usr/bin/ssh-copy-id:INFO:Sourceofkey(s)tobeinstalled:"/root/.ssh/id_rsa.pub"/usr......
  • 使用公钥登录 Linux 服务器
    使用公钥登录Linux服务器‍Linux上使用公钥登录在客户端上通过ssh-copy_id​将公钥写入到服务器的authorized_keys:[root@VM-4-11-centos~]#[email protected]/usr/bin/ssh-copy-id:INFO:Sourceofkey(s)tobeinstalled:"/root/.ssh/id_rsa.pub"/usr......
  • jwt
    publicclassTokenInterceptorimplementsHandlerInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{/*MethodHandlemethodHandle=handlerinstanceof......
  • JWT
    1、介绍JWT是由Header、Payload、Signature拼接组成。JWT的三个部分依次如下。Header(头部):描述JWT的元数据Payload(负载):用来存放实际需要传递的数据Signature(签名):对前两部分的签名,防止数据篡改JWT特点:JWT默认是不加密,但也是可以加密的。生成原始Token以后,可以用密钥再加密一次......