系列文章目錄
第一章 Java線程池技術(shù)應(yīng)用
第二章 CountDownLatch和Semaphone的應(yīng)用
第三章 Spring Cloud 簡(jiǎn)介
第四章 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)是目前最流行的跨域身份驗(yàn)證解決方案。JWT是一個(gè)開(kāi)放標(biāo)準(zhǔn)(RFC 7519)定義的方式,用于在網(wǎng)絡(luò)之間安全地傳輸信息。JWT是一個(gè)緊湊的,URL安全的手段,表示在雙方之間轉(zhuǎn)讓的聲明。這些聲明可以被驗(yàn)證和信任,因?yàn)樗鼈兪菙?shù)字簽名的。JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA公鑰/私鑰對(duì)對(duì)進(jìn)行簽名。
1、概念
- 基于JSON的開(kāi)發(fā)標(biāo)準(zhǔn)
- 用戶信息加密到token里,服務(wù)器不保存任何用戶信息
2、與Cookie/session的對(duì)比
- 在傳統(tǒng)的用戶登錄認(rèn)證中,因?yàn)閔ttp是無(wú)狀態(tài)的,所以都是采用session方式。用戶登錄成功,服務(wù)端會(huì)保證一個(gè)session,當(dāng)然會(huì)給客戶端一個(gè)sessionId,客戶端會(huì)把sessionId保存在cookie中,每次請(qǐng)求都會(huì)攜帶這個(gè)sessionId。
- JWT方式校驗(yàn)方式更加簡(jiǎn)單便捷化,無(wú)需通過(guò)redis緩存,而是直接根據(jù)token取出保存的用戶信息,以及對(duì)token可用性校驗(yàn),單點(diǎn)登錄,驗(yàn)證token更為簡(jiǎn)單。
3、JWT構(gòu)成與交互流程
第一部分為頭部(header),第二部分我們稱其為載荷(payload),第三部分是簽證(signature)?!局虚g用 . 分隔】
一個(gè)標(biāo)準(zhǔn)的JWT生成的token格式如下:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiI1IiwiaWF0IjoxNTY1NTk3MDUzLCJleHAiOjE1NjU2MDA2NTN9.qesdk6aeFEcNafw5WFm-TwZltGWb1Xs6oBEk5QdaLzlHxDM73IOyeKPF_iN1bLvDAlB7UnSu-Z-Zsgl_dIlPiw
3.1、Jwt的頭部承載兩部分信息
聲明類型,這里是jwt
聲明加密的算法,通常直接使用HMACSHA256,就是HS256了
{
“alg”: “HS256”,
“typ”: “JWT”
}
然后將頭部進(jìn)行base64編碼構(gòu)成了第一部分:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
然后我們看第二部分:載荷
載荷,即承載的意思。也就是說(shuō)這里是承載消息具體內(nèi)容的地方。
(你在令牌上附帶的信息:比如用戶的姓名,這樣以后驗(yàn)證了令牌之后就可以直接從這里獲取信息而不用再查數(shù)據(jù)庫(kù)了)
內(nèi)容又可以分為3種標(biāo)準(zhǔn)
標(biāo)準(zhǔn)中注冊(cè)的聲明
公共的聲明
私有的聲明
【標(biāo)準(zhǔn)聲明】
iss: jwt簽發(fā)者
sub: jwt所面向的用戶
aud: 接收jwt的一方
exp: jwt的過(guò)期時(shí)間,這個(gè)過(guò)期時(shí)間必須要大于簽發(fā)時(shí)間
nbf: 定義在什么時(shí)間之前,該jwt都是不可用的.
iat: jwt的簽發(fā)時(shí)間
jti: jwt的唯一身份標(biāo)識(shí),主要用來(lái)作為一次性token,從而回避重放攻擊。
【公共聲明】
公共的聲明可以添加任何的信息,一般添加用戶的相關(guān)信息或其他業(yè)務(wù)需要的必要信息.但不建議添加敏感信息,因?yàn)樵摬糠衷诳蛻舳丝山饷?br> 【私有聲明】
私有聲明是提供者和消費(fèi)者所共同定義的聲明,一般不建議存放敏感信息,因?yàn)閎ase64是對(duì)稱解密的,意味著該部分信息可以歸類為明文信息
對(duì)Payload進(jìn)行Base64加密就得到了JWT第二部分的內(nèi)容。
JWT的第三部分是一個(gè)簽證信息,這個(gè)簽證信息由三部分組成:
header (base64后的)
payload (base64后的)
secret
第三部分需要base64加密后的header和base64加密后的payload使用 . 連接組成的字符串,然后通過(guò)header中聲明的加密方式進(jìn)行加鹽secret組合加密,然后就構(gòu)成了JWT的第三部分。
(對(duì)前兩部分的簽名,防止數(shù)據(jù)篡改)
驗(yàn)證流程:
① 在頭部信息中聲明加密算法和常量, 然后把header使用json轉(zhuǎn)化為字符串
② 在載荷中聲明用戶信息,同時(shí)還有一些其他的內(nèi)容;再次使用json 把載荷部分進(jìn)行轉(zhuǎn)化,轉(zhuǎn)化為字符串
③ 使用在header中聲明的加密算法和每個(gè)項(xiàng)目隨機(jī)生成的secret來(lái)進(jìn)行加密, 把第一步分字符串和第二部分的字符串進(jìn)行加密, 生成新的字符串。詞字符串是獨(dú)一無(wú)二的。
④ 解密的時(shí)候,只要客戶端帶著JWT來(lái)發(fā)起請(qǐng)求,服務(wù)端就直接使用secret進(jìn)行解密。
4、缺點(diǎn)
- 安全性
- 性能
- 一次性
5、Spring boot與JWT整合
5.1、添加模塊
authority-service文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-744084.html
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有效時(shí)長(zhǎng)
expire: 3600
# header 名稱
header: token
5.4、添加類 config
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;
/**
* 過(guò)期時(shí)間
*/
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中的注冊(cè)信息
* @param token
* @return
*/
public Claims getTokenClaim(String token){
try{
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}catch (Exception e){
return null;
}
}
/**
* 驗(yàn)證token是否過(guò)期
* @param expirationTime
* @return
*/
public boolean isTokenExpired(Date expirationTime){
if(null == expirationTime){
return true;
}else{
return expirationTime.before(new Date());
}
}
/**
* 獲取token的失效時(shí)間
* @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中發(fā)布時(shí)間
* @param token
* @return
*/
public Date getIssuedDateFromToken(String token){
return this.getTokenClaim(token).getIssuedAt();
}
}
5.5、添加controller
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 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是否過(guò)期
* @param token
* @return
*/
@PostMapping("/isTokenExpiration")
public Boolean isTokenExpiration(@RequestParam String token){
return this.jwtConfig.isTokenExpired(this.jwtConfig.getExpirationDateFromToken(token));
}
}
5.6、gateway工程 改造
5.6.1、新增線程類UrlThread
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<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";
}
}
5.6.2、新增GlobalBeanConf類
@Configuration
public class GlobalBeanConf {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
5.6.3、全局過(guò)濾器改造
//負(fù)載均衡獲取微服務(wù)實(shí)例
private final LoadBalancerClient loadBalancerClient;
//遠(yuǎn)程調(diào)用
private final RestTemplate restTemplate;
public DrfGlobalFilter(LoadBalancerClient loadBalancerClient, RestTemplate restTemplate) {
this.loadBalancerClient = loadBalancerClient;
this.restTemplate = restTemplate;
}
//啟動(dòng)獲取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.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 Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
//如果登錄請(qǐng)求,不用驗(yàn)證token
String path = request.getURI().getPath();
if(!path.contains("login")){
HttpHeaders headers = request.getHeaders();
String token = headers.getFirst("token");
//token為空表示沒(méi)有登錄,否則已經(jīng)登錄
if(StringUtils.isBlank(token)){
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}else{
TokenDTO tokenDTO = new TokenDTO();
tokenDTO.setToken(token);
//token驗(yàn)證不通過(guò),返回給前端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.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<HttpMessageConverter<?>> 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<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);
}
}
總結(jié)
盡管JWT可以提供驗(yàn)證和完整性檢查,但它本身并不提供加密功能。因此,如果您需要在JWT中發(fā)送敏感信息,可能需要額外的加密步驟(使用DES、AES等算法加密數(shù)據(jù))來(lái)確保數(shù)據(jù)的安全性。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-744084.html
到了這里,關(guān)于Spring boot 整合 JWT的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!