Spring boot如何整合JWT呢?

重生 SpringBoot 发布时间:2023-12-29 22:32:41 阅读数:687 1
下文笔者讲述SpringBoot整合JWT的方法及示例分享,如下所示

JWT的简介

(JWT):
   是JSON Web Token的缩写  
   是目前最流行的跨域身份验证解决方案
    JWT是一个开放标准(RFC 7519)定义的方式
      用于在网络之间安全地传输信息
    JWT是一个紧凑的
        URL安全的手段,双方在通信时这些信息可被验证和信任,
        这些信息都是经过数字签名

    JWT可以使用秘密(使用HMAC算法)
     或
    使用RSA或ECDSA公钥/私钥对对进行签名
    这些签名后的信息,不保存在服务器上,都保存在客户端的Cookie中

JWT与Cookie/session的对比

 
传统的Cookie和Session模式:
  在传统的用户登录认证中
   由于http是无状态的
     所以都是采用session方式
   用户登录成功
      服务端会保证一个session
      客户端会保存一个sessionId
       客户端会把sessionId保存在cookie中
       每次请求都会携带这个sessionId。

JWT方式:
    JWT方式校验方式更加简单便捷化
       无需通过redis缓存
       而是直接根据token取出保存的用户信息

JWT构成与交互流程

第一部分为头部(header)
第二部分我们称其为载荷(payload)
第三部分是签证(signature)
   【中间用 . 分隔】

例:标准JWT

eyJraWQiOiJ4ZTVhNkhmdnRzdWxNTm4zaXZlaGh2Y1Y1cnVhbjljZiIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0.
eyJzdWIiOiI2MTYzNjk0Mzk5MTQzNDMyOTYiLCJjbGllbnRfdHlwZSI6MCwiZXhwIjoxNzAzODc0MDU2LCJpYXQiOjE3MDM4NTk2NTZ9.
4pkvR3V1XzjCpEtNjcX65js37kcE5kLxIzyciwA85dQ

Jwt的头部承载两部分信息

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

第二部分:载荷
  载荷:
   即承载的意思
     也就是说这里是承载消息具体内容的地方。
(你在令牌上附带的信息:
  如
    用户的姓名,这样以后验证令牌之后
       就可以直接从这里获取信息而不用再查数据库)
    内容又可以分为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的第三部分。
   签名后可防止数据被修改

服务端数据验证过程

验证流程:
 1.在头部信息中声明加密算法和常量,把header使用json转化为字符串
 2.在载荷中声明用户信息,同时还有一些其他的内容
     再次使用json 把载荷部分进行转化,转化为字符串
 3.使用在header中声明的加密算法和每个项目随机生成的secret来进行加密
     把第一步分字符串和第二部分的字符串进行加密
      生成新的字符串。词字符串是独一无二的。
 4.解密
     只要客户端带着JWT来发起请求
      服务端就直接使用secret进行解密

JWT缺点

安全性
性能
一次性

Spring boot与JWT整合示例

新建maven项目

    例:jwttest

添加maven依赖

<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>
 

Springboot配置文件设置

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

添加类 config

package com.java265.springCloud.auth.config;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;

@Component
@ConfigurationProperties(prefix = "config.jwt")
@Data
public class JwtConfig {
    /**
     * 密钥
     */
    private String secret;
    /**
     * 过期时间
     */
    private Long expire;
    /**
     * 头部
     */
    private String header;

    /**
     * 生成token
     * @param subject
     * @return
     */
    public String createToken(String subject){
        Date nowDate = new Date();
        Date expireDate = new Date(nowDate.getTime() + expire * 1000);

        return Jwts.builder()
                .setHeaderParam("typ","JWT")
                .setSubject(subject)
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512,secret)
                .compact();

    }

    /**
     * 获取token中的注册信息
     * @param token
     * @return
     */
    public Claims getTokenClaim(String token){
        try{
            return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        }catch (Exception e){
            return null;
        }

    }

    /**
     * 验证token是否过期
     * @param expirationTime
     * @return
     */
    public boolean isTokenExpired(Date expirationTime){
        if(null == expirationTime){
            return true;
        }else{
            return expirationTime.before(new Date());
        }
    }

    /**
     * 获取token的失效时间
     * @param token
     * @return
     */
    public Date getExpirationDateFromToken(String token){
        Claims tokenClaim = this.getTokenClaim(token);
        if(tokenClaim == null){
            return null;
        }else{
            return this.getTokenClaim(token).getExpiration();
        }

    }

    /**
     * 获取token中的用户名
     * @param token
     * @return
     */
    public String getUserNameFromToken(String token){
        return this.getTokenClaim(token).getSubject();
    }

    /**
     * 获取token中发布时间
     * @param token
     * @return
     */
    public Date getIssuedDateFromToken(String token){
        return this.getTokenClaim(token).getIssuedAt();
    }

}

添加controller

package com.java265.springCloud.auth.controller;

import com.java265.springCloud.auth.config.JwtConfig;
import com.java265.springCloud.common.entity.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/auth")
public class AuthController {
    @Autowired
    private JwtConfig jwtConfig;
    @PostMapping("/login")
    public Map<String,String> login(@RequestBody UserInfo userInfo){
        String token = jwtConfig.createToken(userInfo.getUserAccount());
        Map<String, String> map = new HashMap<String, String>();
        map.put("token",token);
        return map;
    }

    /**
     * token是否过期
     * @param token
     * @return
     */
    @PostMapping("/isTokenExpiration")
    public Boolean isTokenExpiration(@RequestParam String token){
        return this.jwtConfig.isTokenExpired(this.jwtConfig.getExpirationDateFromToken(token));
    }
}

gateway工程

新增线程类UrlThread

package com.java265.springCloud.gateway.hread;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import java.util.concurrent.Callable;

public class UrlThread implements Callable<String> {

    private final LoadBalancerClient loadBalancerClient;

    public UrlThread(LoadBalancerClient loadBalancerClient) {
        this.loadBalancerClient = loadBalancerClient;
    }

    @Override
    public String call() throws Exception {
        ServiceInstance serviceInstance = this.loadBalancerClient.choose("auth-service");
        return  "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/auth/isTokenExpiration";
    }

}

新增GlobalBeanConf类

@Configuration
public class GlobalBeanConf {
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

全局过滤器改造

//负载均衡获取微服务实例
private final   LoadBalancerClient loadBalancerClient;
//远程调用
private final RestTemplate restTemplate;


public DrfGlobalFilter(LoadBalancerClient loadBalancerClient, RestTemplate restTemplate) {
    this.loadBalancerClient = loadBalancerClient;
    this.restTemplate = restTemplate;
}
 
//启动获取url的线程
FutureTask<String> stringFutureTask = new FutureTask<String>(new UrlThread(this.loadBalancerClient));
new Thread(stringFutureTask).start();
String url = stringFutureTask.get();

Boolean aBoolean = this.restTemplate.postForObject(url+"?token="+token, null,Boolean.class);


package com.java265.springCloud.gateway.filter;

import com.java265.springCloud.common.dto.TokenDTO;
import com.java265.springCloud.gateway.service.AuthService;
import com.java265.springCloud.gateway.thread.ValidateTokenTask;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

@Component
public class DrfGlobalFilter implements GlobalFilter, Ordered {

    private final AuthService authService;
    private ExecutorService executorService;

    public DrfGlobalFilter(AuthService authService) {
        this.authService = authService;
        this.executorService = Executors.newFixedThreadPool(5);
    }


    @SneakyThrows
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        //如果登录请求,不用验证token
        String 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 = new TokenDTO();
                tokenDTO.setToken(token);
                //token验证不通过,返回给前端401
                FutureTask<Boolean> booleanFutureTask = new FutureTask<>(new ValidateTokenTask(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);
    }

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

 
package com.java265.springCloud.gateway.service;

import com.java265.springCloud.common.dto.TokenDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

@FeignClient(value = "auth-service")
public interface AuthService {
    @PostMapping("/auth/validateToken")
    public Boolean validateToken(TokenDTO tokenDTO);
}

package com.java265.springCloud.gateway.config;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;

import java.util.stream.Collectors;

@Configuration
public class GlobalConf {
    @Bean
    @ConditionalOnMissingBean
    public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
        return new HttpMessageConverters(converters.orderedStream().collect(Collectors.tolist()));
    }

}

 
package com.java265.springCloud.gateway.thread;

import com.java265.springCloud.common.dto.TokenDTO;
import com.java265.springCloud.gateway.service.AuthService;

import java.util.concurrent.Callable;

public class ValidateTokenTask implements Callable<Boolean> {
    private final AuthService authService;
    private final String token;

    public ValidateTokenTask(AuthService authService, String token) {
        this.authService = authService;
        this.token = token;
    }

    @Override
    public Boolean call() throws Exception {
        TokenDTO tokenDTO = new TokenDTO();
        tokenDTO.setToken(this.token);
        return this.authService.validateToken(tokenDTO);
    }
}
版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

本文链接: https://www.Java265.com/JavaFramework/SpringBoot/202312/7609.html

最近发表

热门文章

好文推荐

Java265.com

https://www.java265.com

站长统计|粤ICP备14097017号-3

Powered By Java265.com信息维护小组

使用手机扫描二维码

关注我们看更多资讯

java爱好者