第一章 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公钥/私钥对对进行签名。
第一部分为头部(header),第二部分我们称其为载荷(payload),第三部分是签证(signature)。【中间用 . 分隔】
一个标准的JWT生成的token格式如下:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiI1IiwiaWF0IjoxNTY1NTk3MDUzLCJleHAiOjE1NjU2MDA2NTN9.qesdk6aeFEcNafw5WFm-TwZltGWb1Xs6oBEk5QdaLzlHxDM73IOyeKPF_iN1bLvDAlB7UnSu-Z-Zsgl_dIlPiw
声明类型,这里是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进行解密。
authority-service
io.jsonwebtoken jjwt 0.7.0 com.auth0 java-jwt 3.4.0 com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery 2021.0.4.0
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
package com.xxxx.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(); } }
package com.xxxx.springCloud.auth.controller; import com.xxxx.springCloud.auth.config.JwtConfig; import com.xxxx.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 Maplogin(@RequestBody UserInfo userInfo){ String token = jwtConfig.createToken(userInfo.getUserAccount()); Map map = new HashMap (); 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)); } }
package com.xxxx.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{ 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"; } }
@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的线程 FutureTaskstringFutureTask = new FutureTask (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.xxxx.springCloud.gateway.filter; import com.xxxx.springCloud.common.dto.TokenDTO; import com.xxxx.springCloud.gateway.service.AuthService; import com.xxxx.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 Monofilter(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 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.xxxx.springCloud.gateway.service; import com.xxxx.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.xxxx.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> converters) { return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList())); } }
package com.xxxx.springCloud.gateway.thread; import com.xxxx.springCloud.common.dto.TokenDTO; import com.xxxx.springCloud.gateway.service.AuthService; import java.util.concurrent.Callable; public class ValidateTokenTask implements Callable{ 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); } }
尽管JWT可以提供验证和完整性检查,但它本身并不提供加密功能。因此,如果您需要在JWT中发送敏感信息,可能需要额外的加密步骤(使用DES、AES等算法加密数据)来确保数据的安全性。
上一篇:Spring全面详解(学习总结)