0


Spring boot 整合 JWT

系列文章目录

第一章 Java线程池技术应用
第二章 CountDownLatch和Semaphone的应用
第三章 Spring Cloud 简介
第四章 Spring Cloud Netflix 之 Eureka
第五章 Spring Cloud Netflix 之 Ribbon
第六章 Spring Cloud 之 OpenFeign
第七章 Spring Cloud 之 GateWay
第八章 Spring Cloud Netflix 之 Hystrix
第九章 代码管理gitlab 使用
第十章 SpringCloud Alibaba 之 Nacos discovery
第十一章 SpringCloud Alibaba 之 Nacos Config
第十二章 Spring Cloud Alibaba 之 Sentinel
第十三章 JWT

在这里插入图片描述


文章目录

前言

JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案。JWT是一个开放标准(RFC 7519)定义的方式,用于在网络之间安全地传输信息。JWT是一个紧凑的,URL安全的手段,表示在双方之间转让的声明。这些声明可以被验证和信任,因为它们是数字签名的。JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA公钥/私钥对对进行签名。

1、概念

  • 基于JSON的开发标准
  • 用户信息加密到token里,服务器不保存任何用户信息

2、与Cookie/session的对比

  • 在传统的用户登录认证中,因为http是无状态的,所以都是采用session方式。用户登录成功,服务端会保证一个session,当然会给客户端一个sessionId,客户端会把sessionId保存在cookie中,每次请求都会携带这个sessionId。
  • JWT方式校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录,验证token更为简单。

3、JWT构成与交互流程

第一部分为头部(header),第二部分我们称其为载荷(payload),第三部分是签证(signature)。【中间用 . 分隔】
一个标准的JWT生成的token格式如下:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiI1IiwiaWF0IjoxNTY1NTk3MDUzLCJleHAiOjE1NjU2MDA2NTN9.qesdk6aeFEcNafw5WFm-TwZltGWb1Xs6oBEk5QdaLzlHxDM73IOyeKPF_iN1bLvDAlB7UnSu-Z-Zsgl_dIlPiw

3.1、Jwt的头部承载两部分信息

声明类型,这里是jwt
声明加密的算法,通常直接使用HMACSHA256,就是HS256了
{
“alg”: “HS256”,
“typ”: “JWT”
}
然后将头部进行base64编码构成了第一部分:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

然后我们看第二部分:载荷
载荷,即承载的意思。也就是说这里是承载消息具体内容的地方。
(你在令牌上附带的信息:比如用户的姓名,这样以后验证了令牌之后就可以直接从这里获取信息而不用再查数据库了)
内容又可以分为3种标准
标准中注册的声明
公共的声明
私有的声明
【标准声明】
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
【公共声明】
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密
【私有声明】
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息
对Payload进行Base64加密就得到了JWT第二部分的内容。
JWT的第三部分是一个签证信息,这个签证信息由三部分组成:
header (base64后的)
payload (base64后的)
secret
第三部分需要base64加密后的header和base64加密后的payload使用 . 连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了JWT的第三部分。
(对前两部分的签名,防止数据篡改)
在这里插入图片描述
验证流程:
① 在头部信息中声明加密算法和常量, 然后把header使用json转化为字符串
② 在载荷中声明用户信息,同时还有一些其他的内容;再次使用json 把载荷部分进行转化,转化为字符串
③ 使用在header中声明的加密算法和每个项目随机生成的secret来进行加密, 把第一步分字符串和第二部分的字符串进行加密, 生成新的字符串。词字符串是独一无二的。
④ 解密的时候,只要客户端带着JWT来发起请求,服务端就直接使用secret进行解密。

4、缺点

  • 安全性
  • 性能
  • 一次性

5、Spring boot与JWT整合

5.1、添加模块

authority-service

5.2、添加依赖

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.7.0</version></dependency><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.4.0</version></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><version>2021.0.4.0</version></dependency>

5.3、修改配置文件

server:
  port:7777
spring:
  application:
    name: auth-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848  #Nacos server 的地址
config:
  jwt:
    # 加密密钥
    secret: tigerkey
    # token有效时长
    expire:3600
    # header 名称
    header: token

5.4、添加类 config

packagecom.xxxx.springCloud.auth.config;importio.jsonwebtoken.Claims;importio.jsonwebtoken.Jwts;importio.jsonwebtoken.SignatureAlgorithm;importlombok.Data;importorg.springframework.boot.context.properties.ConfigurationProperties;importorg.springframework.stereotype.Component;importjava.util.Date;@Component@ConfigurationProperties(prefix ="config.jwt")@DatapublicclassJwtConfig{/**
     * 密钥
     */privateString secret;/**
     * 过期时间
     */privateLong expire;/**
     * 头部
     */privateString header;/**
     * 生成token
     * @param subject
     * @return
     */publicStringcreateToken(String subject){Date nowDate =newDate();Date expireDate =newDate(nowDate.getTime()+ expire *1000);returnJwts.builder().setHeaderParam("typ","JWT").setSubject(subject).setIssuedAt(nowDate).setExpiration(expireDate).signWith(SignatureAlgorithm.HS512,secret).compact();}/**
     * 获取token中的注册信息
     * @param token
     * @return
     */publicClaimsgetTokenClaim(String token){try{returnJwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();}catch(Exception e){returnnull;}}/**
     * 验证token是否过期
     * @param expirationTime
     * @return
     */publicbooleanisTokenExpired(Date expirationTime){if(null== expirationTime){returntrue;}else{return expirationTime.before(newDate());}}/**
     * 获取token的失效时间
     * @param token
     * @return
     */publicDategetExpirationDateFromToken(String token){Claims tokenClaim =this.getTokenClaim(token);if(tokenClaim ==null){returnnull;}else{returnthis.getTokenClaim(token).getExpiration();}}/**
     * 获取token中的用户名
     * @param token
     * @return
     */publicStringgetUserNameFromToken(String token){returnthis.getTokenClaim(token).getSubject();}/**
     * 获取token中发布时间
     * @param token
     * @return
     */publicDategetIssuedDateFromToken(String token){returnthis.getTokenClaim(token).getIssuedAt();}}

5.5、添加controller

packagecom.xxxx.springCloud.auth.controller;importcom.xxxx.springCloud.auth.config.JwtConfig;importcom.xxxx.springCloud.common.entity.UserInfo;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.*;importjava.util.HashMap;importjava.util.Map;@RestController@RequestMapping("/auth")publicclassAuthController{@AutowiredprivateJwtConfig jwtConfig;@PostMapping("/login")publicMap<String,String>login(@RequestBodyUserInfo userInfo){String token = jwtConfig.createToken(userInfo.getUserAccount());Map<String,String> map =newHashMap<String,String>();
        map.put("token",token);return map;}/**
     * token是否过期
     * @param token
     * @return
     */@PostMapping("/isTokenExpiration")publicBooleanisTokenExpiration(@RequestParamString token){returnthis.jwtConfig.isTokenExpired(this.jwtConfig.getExpirationDateFromToken(token));}}

5.6、gateway工程 改造

5.6.1、新增线程类UrlThread

packagecom.xxxx.springCloud.gateway.hread;importorg.springframework.cloud.client.ServiceInstance;importorg.springframework.cloud.client.loadbalancer.LoadBalancerClient;importjava.util.concurrent.Callable;publicclassUrlThreadimplementsCallable<String>{privatefinalLoadBalancerClient loadBalancerClient;publicUrlThread(LoadBalancerClient loadBalancerClient){this.loadBalancerClient = loadBalancerClient;}@OverridepublicStringcall()throwsException{ServiceInstance serviceInstance =this.loadBalancerClient.choose("auth-service");return"http://"+ serviceInstance.getHost()+":"+ serviceInstance.getPort()+"/auth/isTokenExpiration";}}

5.6.2、新增GlobalBeanConf类

@ConfigurationpublicclassGlobalBeanConf{@BeanpublicRestTemplaterestTemplate(){returnnewRestTemplate();}}

5.6.3、全局过滤器改造

//负载均衡获取微服务实例privatefinalLoadBalancerClient loadBalancerClient;//远程调用privatefinalRestTemplate restTemplate;publicDrfGlobalFilter(LoadBalancerClient loadBalancerClient,RestTemplate restTemplate){this.loadBalancerClient = loadBalancerClient;this.restTemplate = restTemplate;}
//启动获取url的线程FutureTask<String> stringFutureTask =newFutureTask<String>(newUrlThread(this.loadBalancerClient));newThread(stringFutureTask).start();String url = stringFutureTask.get();Boolean aBoolean =this.restTemplate.postForObject(url+"?token="+token,null,Boolean.class);
packagecom.xxxx.springCloud.gateway.filter;importcom.xxxx.springCloud.common.dto.TokenDTO;importcom.xxxx.springCloud.gateway.service.AuthService;importcom.xxxx.springCloud.gateway.thread.ValidateTokenTask;importlombok.SneakyThrows;importorg.apache.commons.lang3.StringUtils;importorg.springframework.cloud.client.ServiceInstance;importorg.springframework.cloud.client.loadbalancer.LoadBalancerClient;importorg.springframework.cloud.gateway.filter.GatewayFilterChain;importorg.springframework.cloud.gateway.filter.GlobalFilter;importorg.springframework.core.Ordered;importorg.springframework.http.HttpHeaders;importorg.springframework.http.HttpStatus;importorg.springframework.http.server.reactive.ServerHttpRequest;importorg.springframework.http.server.reactive.ServerHttpResponse;importorg.springframework.stereotype.Component;importorg.springframework.web.server.ServerWebExchange;importreactor.core.publisher.Mono;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;importjava.util.concurrent.FutureTask;@ComponentpublicclassDrfGlobalFilterimplementsGlobalFilter,Ordered{privatefinalAuthService authService;privateExecutorService executorService;publicDrfGlobalFilter(AuthService authService){this.authService = authService;this.executorService =Executors.newFixedThreadPool(5);}@SneakyThrows@OverridepublicMono<Void>filter(ServerWebExchange exchange,GatewayFilterChain chain){ServerHttpRequest request = exchange.getRequest();//如果登录请求,不用验证tokenString path = request.getURI().getPath();if(!path.contains("login")){HttpHeaders headers = request.getHeaders();String token = headers.getFirst("token");//token为空表示没有登录,否则已经登录if(StringUtils.isBlank(token)){ServerHttpResponse response = exchange.getResponse();
                response.setStatusCode(HttpStatus.UNAUTHORIZED);return response.setComplete();}else{TokenDTO tokenDTO =newTokenDTO();
                tokenDTO.setToken(token);//token验证不通过,返回给前端401FutureTask<Boolean> booleanFutureTask =newFutureTask<>(newValidateTokenTask(this.authService, token));this.executorService.submit(booleanFutureTask);Boolean aBoolean = booleanFutureTask.get();if(aBoolean){ServerHttpResponse response = exchange.getResponse();
                    response.setStatusCode(HttpStatus.UNAUTHORIZED);return response.setComplete();}}}return chain.filter(exchange);}@OverridepublicintgetOrder(){return0;}}
packagecom.xxxx.springCloud.gateway.service;importcom.xxxx.springCloud.common.dto.TokenDTO;importorg.springframework.cloud.openfeign.FeignClient;importorg.springframework.web.bind.annotation.PostMapping;importorg.springframework.web.bind.annotation.RequestBody;@FeignClient(value ="auth-service")publicinterfaceAuthService{@PostMapping("/auth/validateToken")publicBooleanvalidateToken(TokenDTO tokenDTO);}packagecom.xxxx.springCloud.gateway.config;importorg.springframework.beans.factory.ObjectProvider;importorg.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;importorg.springframework.boot.autoconfigure.http.HttpMessageConverters;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.http.converter.HttpMessageConverter;importjava.util.stream.Collectors;@ConfigurationpublicclassGlobalConf{@Bean@ConditionalOnMissingBeanpublicHttpMessageConvertersmessageConverters(ObjectProvider<HttpMessageConverter<?>> converters){returnnewHttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));}}
packagecom.xxxx.springCloud.gateway.thread;importcom.xxxx.springCloud.common.dto.TokenDTO;importcom.xxxx.springCloud.gateway.service.AuthService;importjava.util.concurrent.Callable;publicclassValidateTokenTaskimplementsCallable<Boolean>{privatefinalAuthService authService;privatefinalString token;publicValidateTokenTask(AuthService authService,String token){this.authService = authService;this.token = token;}@OverridepublicBooleancall()throwsException{TokenDTO tokenDTO =newTokenDTO();
        tokenDTO.setToken(this.token);returnthis.authService.validateToken(tokenDTO);}}

总结

尽管JWT可以提供验证和完整性检查,但它本身并不提供加密功能。因此,如果您需要在JWT中发送敏感信息,可能需要额外的加密步骤(使用DES、AES等算法加密数据)来确保数据的安全性。

标签: spring boot java 后端

本文转载自: https://blog.csdn.net/s445320/article/details/134197122
版权归原作者 青花锁 所有, 如有侵权,请联系我们删除。

“Spring boot 整合 JWT”的评论:

还没有评论