JWT -- Json Web token

news2024/12/25 9:10:48

JWT 的背景知识可以看这篇文章: JSON Web Token 入门教程

JWT 由三个部分组成:

  • Header(头部)
  • Payload(负载)
  • Signature(签名)

在分布式系统下,存在跨session的问题,则使用JWT实现分布式下,身份验证的功能

JWT有很多的第三方实现,Java JWT 类库对比及使用 - 简书,

此处我们选择Auth0,此案例实现的功能如下

 认证中心

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.8.3</version>
        </dependency>

配置:

server:
  port: 8080

jwt:
  secret: "123321" #私钥
  expireTime: 1 #JWT有效期,单位是分钟
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "jwt")
public class JwtProperties {
    private String secret;
    private int expireTime;
}

自定义异常

public class TokenUnavailableException extends RuntimeException{
    public TokenUnavailableException(String message) {
        super(message);
    }
}

import com.lb.bean.JwtResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class ExceptionAdvice {

    @ExceptionHandler(TokenUnavailableException.class)
    private ResponseEntity<Object> handlerTokenUnavailableException(TokenUnavailableException e) {
        return new ResponseEntity<>(JwtResponse.error(e.getMessage()), HttpStatus.BAD_REQUEST);
    }
}

JWT - 生成,检验

package com.lb.config;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.lb.bean.JwtResponse;
import com.lb.exception.TokenUnavailableException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.Map;

@Component
public class JwtService {

    @Autowired
    JwtProperties jwtProperties;

    public String createToken(String subject, Map<String, String> claimMap) {
        JWTCreator.Builder builder = JWT.create()
//                .withIssuer("")
//                .withNotBefore(null)
//                .withIssuedAt(null)
//                .withAudience("")
                .withSubject(subject) //主题
                .withExpiresAt(Date.from(
                        LocalDateTime.now().plusMinutes(jwtProperties.getExpireTime())
                                .atZone(ZoneId.systemDefault()).toInstant())
                );
        claimMap.forEach(builder::withClaim);
        return builder.sign(Algorithm.HMAC256(jwtProperties.getSecret()));
    }

    public JwtResponse validateToken(String token) {
        try {
            JWTVerifier jwtVerifier =
                    JWT.require(Algorithm.HMAC256(jwtProperties.getSecret()))
                            .build();
            DecodedJWT decodedJwt = jwtVerifier.verify(token);

            return JwtResponse.ok(decodedJwt.getClaims());
        } catch (Exception e) {
            //校验失败
            throw new TokenUnavailableException(e.getMessage());
        }
    }

    public boolean isNeedUpdate(String token) {

        try {
            Date expiresAt = JWT.require(Algorithm.HMAC256(jwtProperties.getSecret()))
                    .build()
                    .verify(token)
                    .getExpiresAt();
            // 需要更新 时间小于一半
            return (expiresAt.getTime() - System.currentTimeMillis()) < ((jwtProperties.getExpireTime() * 60000) >> 1);
        } catch (TokenExpiredException e) {
            return true;
        } catch (Exception e) {
            //校验失败
            throw new TokenUnavailableException(e.getMessage());
        }
    }
}

controller

@RestController
public class JwtController {
    @Autowired
    private JwtService jwtService;

    @PostMapping("create")
    public ResponseEntity<String> generateJwtToken(@RequestBody JwtRequest request) {
        String token = jwtService.createToken(request.getSubject(), request.getClaimsMap());
        return ResponseEntity.ok(token);
    }

    @PostMapping("validate")
    public ResponseEntity<JwtResponse> validateToken(@RequestParam String token) {
        JwtResponse response = jwtService.validateToken(token);
        return ResponseEntity.ok(response);
    }

    @PostMapping("expire")
    public ResponseEntity<Boolean> expire(@RequestParam String token) {
        return ResponseEntity.ok(jwtService.isNeedUpdate(token));
    }
}

客户端模块

请求认证中心,生成token,验证token,更新token

自定义注解,跳过JWT验证的API

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
    boolean required() default true;
}

自定义拦截器,验证JWT

package com.lb.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.lb.annotation.PassToken;
import com.lb.bean.JwtResponse;
import com.lb.exception.JwtException;
import com.lb.util.JwtHttpEntityUtil;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Map;


public class JwtAuthenticationInterceptor implements HandlerInterceptor {
    private static ObjectMapper MAPPER = new ObjectMapper();
    @Resource(name = "facePlusRestTemplate")
    RestTemplate restTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //如果不是映射到方法直接通过
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        //检查时候包含passtoken注解
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                return true;
            }
        }
        String token = request.getHeader("Authorization");
        if (StringUtils.isEmpty(token)) {
            throw new JwtException(HttpStatus.UNAUTHORIZED, "token 不存在");
        }
        HttpEntity formEntity = JwtHttpEntityUtil.getValidateEntity(token);
        ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity("http://localhost:8080/validate", formEntity, String.class);
        if (stringResponseEntity == null) {
            throw new JwtException(HttpStatus.SERVICE_UNAVAILABLE, "服务不可用,请稍后再试");
        }
        String body = stringResponseEntity.getBody();
        JwtResponse jwtRes = MAPPER.readValue(body, JwtResponse.class);
        if (!jwtRes.isFlag()) {
            throw new JwtException(HttpStatus.UNAUTHORIZED, jwtRes.getMsg());
        }
        Map<String, String> claimsMap = jwtRes.getClaimsMap();
        request.setAttribute("claims", jwtRes.getClaimsMap());

        stringResponseEntity = restTemplate.postForEntity("http://localhost:8080/expire", formEntity, String.class);
        if (stringResponseEntity != null && Boolean.parseBoolean(stringResponseEntity.getBody())) {
            formEntity = JwtHttpEntityUtil.getCreateTokenEntity(claimsMap.get("sub"), claimsMap.get("pwd"));
            token = restTemplate.postForObject("http://localhost:8080/create", formEntity, String.class);
            response.setHeader("token", token);
        }
        return true;
    }
}

注册拦截器

import com.lb.exception.FacePlusThrowErrorHandler;
import com.lb.interceptor.JwtAuthenticationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.nio.charset.StandardCharsets;

@Configuration
public class ApplicationConfig implements WebMvcConfigurer {
    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        return new RestTemplate(factory);
    }

    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(5000);
        factory.setConnectTimeout(15000);
        return factory;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //拦截所有请求
        registry.addInterceptor(jwtAuthenticationInterceptor()).addPathPatterns("/**");
    }

    @Bean
    public JwtAuthenticationInterceptor jwtAuthenticationInterceptor() {
        return new JwtAuthenticationInterceptor();
    }
    
    //自定义处理400,500返回
    @Bean
    public RestTemplate facePlusRestTemplate(ClientHttpRequestFactory factory) {
        RestTemplate restTemplate = new RestTemplate(factory);
        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
        restTemplate.getMessageConverters().add(new FormHttpMessageConverter());
        restTemplate.setErrorHandler(new FacePlusThrowErrorHandler());
        return restTemplate;
    }
}
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.ResponseErrorHandler;

import java.io.IOException;

public class FacePlusThrowErrorHandler  implements ResponseErrorHandler {
    @Override
    public boolean hasError(ClientHttpResponse response) throws IOException {
        return false;
    }

    @Override
    public void handleError(ClientHttpResponse response) throws IOException {
        throw new JwtException(response.getStatusCode(), response.getBody().toString());
    }
}
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

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

public class JwtHttpEntityUtil {
    private static ObjectMapper MAPPER = new ObjectMapper();
    public static HttpEntity<String> getCreateTokenEntity(String userName, String password) throws JsonProcessingException {
        HttpHeaders headers = new HttpHeaders();
        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
        headers.setContentType(type);
        headers.add("Accept", MediaType.APPLICATION_JSON.toString());
        HashMap<String, Object> map = new HashMap<>();
        map.put("subject", userName);
        Map<String, String> calims = new HashMap<>();
        calims.put("pwd", password);
        map.put("claimsMap", calims);
        String stu = MAPPER.writeValueAsString(map);
        return new HttpEntity<String>(stu, headers);
    }

    public static HttpEntity getValidateEntity(String token) throws JsonProcessingException {
        MultiValueMap<String, String> paramMap = new LinkedMultiValueMap<String, String>();
        paramMap.set("token",token);
        //2、添加请求头
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type","application/x-www-form-urlencoded");
        return new HttpEntity(paramMap, headers);
    }
}

客户端Controller 

@RestController
public class MyController {

    @Autowired
    RestTemplate restTemplate;

    @PassToken
    @PostMapping("login")
    public String login(String userName, String password, HttpServletResponse response) throws JsonProcessingException {
        //模拟数据库查询用户
        if ("admin".equals(userName) && "admin".equals(password)) {
            HttpEntity<String> formEntity = JwtHttpEntityUtil.getCreateTokenEntity(userName, password);
            String token = restTemplate.postForObject("http://localhost:8080/create", formEntity, String.class);
            response.setHeader("token", token);
            return "登陆成功";
        } else {
            return "用户名密码不正确";
        }
    }

    @GetMapping("test")
    public String test(HttpServletRequest request) {
        return request.getAttribute("claims").toString();
    }
}

以上代码省略了 异常类异常处理类

结果

 create API -- 拦截器放行login API,并返回token

 

 validate 、expire API -- 验证和更新token

 以上案例是为了,实现注册中心是单独服务,如果不需要它是单独的服务,直接在项目内提供工具类直接生成token,以及token的验证。更加简单

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/43446.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[附源码]Python计算机毕业设计Django毕业生就业管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

cdn加速华为云obs桶文件配置过程(详细)

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂。 前言 前面写了一篇文章&#xff0c;jeecg-boot中上传图片到华为云obs云存储中 主要介绍了下&#xff0c;如何使用jeect-boot微服务将文件上传至obs中。 但是上传是没有问题&#xff0c;…

目前看过最全的一线大厂面试题(题 + 详解),你所不知道的都在这

前言 在过 1 个月即将进入 2023&#xff0c;然而面对今年的大环境而言&#xff0c;跳槽成功的难度比往年高了很多&#xff0c;很明显的感受就是&#xff1a;对于今年的 java 开发朋友跳槽面试&#xff0c;无论一面还是二面&#xff0c;都开始考验一个 Java 程序员的技术功底和…

FL Studio2022水果编曲音乐制作软件自带完整插件

FL Studio 2022是一款非常好用的音乐制作软件&#xff0c;又称水果编曲软件&#xff0c;软件集合了录音、混音、编辑等多种功能于一体&#xff0c;能够完成各种各样的音乐编曲工作&#xff0c;强大的音乐制作功能受到了很多用户的喜爱&#xff0c;帮你完成各种类型音乐的编曲制…

《500强高管谈VE》-企业经营与VE活动

文章出处&#xff1a;日本VE协会杂志文章翻译&#xff1a;泰泽项目部 关注泰泽&#xff1a;实现高利润企业 《500强高管谈VE》-企业经营与VE活动 作者&#xff1a;兄弟工业常务董事渡边共祥 由墨西哥货币不稳定引发的此次日元升值&#xff0c;一度跌破80日元&#xff0c;呈现…

Servlet程序及部署方式(Tomcat+Smart Tomcat)

目录 1、Servlet是什么&#xff1f; 2、Servlet程序【例——hello world】 2.1、创建项目 2.2、引入Servlet依赖 2.3、创建目录结构 2.4、编写代码 2.5、打包程序 2.6、部署程序 2.7、验证程序 3、更方便的部署方式——Smart Tomcat 1、Servlet是什么&#xff1f; Se…

初识计算机网络

目录 网络的发展 重新看待计算机结构 大型存储平台 认识 "协议" 网络和OS之间的关系 初识网络协议 协议分层 OSI七层模型 TCP/IP五层(或四层)模型 网络传输基本流程 局域网通信的原理 如果进行跨网络传输 网络通信里面的基本轮廓 数据包封装和分用…

多线程同步

文章目录一、多线程同步竞争与协作互斥的概念同步的概念互斥与同步的实现和使⽤锁信号量⽣产者-消费者问题经典同步问题读者-写者问题一、多线程同步 竞争与协作 在单核 CPU 系统⾥&#xff0c;为了实现多个程序同时运⾏的假象&#xff0c;操作系统通常以时间⽚调度的⽅式&am…

为dev c++配置图形开发环境easyx之mingw32

easyx官方的文档在说明如何配置环境上面不太详细&#xff0c;所以就有了我的那篇博文为dev c配置图形开发环境easyx&#xff0c;默认的是在64位的编译器TDM-gcc下配置的&#xff0c;也就是我们配置的easyx最终都是放在mingw64文件夹下的&#xff0c;5.1版本后的dev c内置的编译…

什么是分层架构

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;微微的猪食小窝 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 微微的猪食小窝 原创 1、什么是架构分层&#xff1f; 分层架构是将软件模块按照水平切分的方式分成多个层&#xff0c;一个系统由多层组成…

同时安装Vue2和Vue3

背景 当我们的工作中使用的还是脚手架还是基于Vue2.x的版本&#xff0c;那么我们想要学习和使用Vue3怎么办&#xff1f;直接升级脚手架的话&#xff0c;会影响到我们现在的项目&#xff0c;那就需要去处理下关于Vue3的东西了。 下载安装Vue3的脚手架 任意磁盘根目录新建一个文件…

【深入理解C++】new/delete和new[]/delete[]探秘

文章目录1.operator new()和operator delete()2.new记录分配的内存大小供delete使用3.new[]/delete[]申请和释放一个数组3.1 基本数据类型&#xff08;内置类型&#xff09;3.2 自定义类型&#xff08;类类型&#xff09;4.new/delete和new[]/delete[]要配对使用1.operator new…

开发 Chrome 扩展 之 Hello World 心血来潮

开发 Chrome 扩展 Hello, World 项目加载未打包的扩展icon刷新引入 JS 与错误处理 开发 Chrome 扩展 开发 Chrome 扩展除了需要基本的 HTML, CSS, JS 之外, 还可以使用 Chrome 额外提供的 API. 除了需要的 .html, .css 和 .js 文件之外呢, 扩展还包括不同类型的文件, 具体可…

杨辉三角形(Java版)

不为失败找理由&#xff0c;只为成功找方法。所有的不甘&#xff0c;因为还心存梦想&#xff0c;所以在你放弃之前&#xff0c;好好拼一把&#xff0c;只怕心老&#xff0c;不怕路长。 文章目录1. 什么是杨辉三角形2. 实现思路&#xff08;方式&#xff09;2.1 递归方式2.2 递归…

Nginx简单使用

安装龙蜥操作系统 镜像文件在这里下载就行 下载之后新建虚拟机 ISO选择刚才下载文件即可 具体配置可以照我来 也可自定义 基本工具安装 安装一下最基本的网络工具 yum install net-tools openssh-server wget tar make vim -y测试一下ssh连接 方便后期操作 修改主机名 …

Jedis 使用教程总结

一、Redis 常用命令 1 连接操作命令 quit&#xff1a;关闭连接&#xff08;connection&#xff09;auth&#xff1a;简单密码认证help cmd&#xff1a; 查看 cmd 帮助&#xff0c;例如&#xff1a;help quit 2 持久化 save&#xff1a;将数据同步保存到磁盘bgsave&#xff…

设计模式之原型模式

文章目录1.前言概念使用场景2.原型模式核心组成UML图3.浅拷贝与深拷贝基本类型与引用类型浅拷贝代码演示深拷贝代码演示4.原型模式的优点与缺点1.前言 概念 原型模式&#xff08;Prototype Pattern&#xff09;是用于创建重复的对象&#xff0c;同时又能保证性能。这种类型的…

Cpp知识点系列-类型转换

前言 在做题的时候发现了需要用到类型转换&#xff0c;于是在这里进行了简单的记录。 历史原因&#xff0c;慢慢整理着发现类型转换也能写老大一篇文章了。又花了时间来梳理一下就成了本文了。 cpp 之前使用的环境是DEV-C 5.4&#xff0c;而对应的GCC版本太低了。支持c11需要…

【CSS】重点知识梳理,这样上手无压力

推荐前端学习路线如下&#xff1a; HTML、CSS、JavaScript、noodJS、组件库、JQuery、前端框架&#xff08;Vue、React&#xff09;、微信小程序和uniapp、TypeScript、webpack 和 vite、Vue 和 React 码源、NextJS、React Native、后端内容。。。。。。 CSS定义&#xff1a; …

docker入门到精通一文搞定

文章目录前言一、Docker概述1.Docker为什么会出现&#xff1f;2.Docker相比VM技术3.Docker 能做什么&#xff1f;3.1 比较Docker和虚拟机技术的不同&#xff1a;3.2 DevOps (开发、运维)&#xff1a;4个特点二、Docker安装1.dokcer架构图&#xff1a;2.Docker基本组成&#xff…