什么是JWT
廣義上講JWT是一個(gè)標(biāo)準(zhǔn)的名稱;狹義上講JWT指的就是用來(lái)傳遞的那個(gè)token字符串。 JWT的組成JWT含有三個(gè)部分:
頭部(header) { 'typ':'JWT', 'alg':'HS256'} 載荷(payload)
簽證(signature)
JWT特點(diǎn)
如何使用JWT?在身份鑒定的實(shí)現(xiàn)中,傳統(tǒng)的方法是在服務(wù)端存儲(chǔ)一個(gè) 當(dāng)用戶希望訪問(wèn)一個(gè)受保護(hù)的路由或者資源的時(shí)候,通常應(yīng)該在
因?yàn)橛脩舻臓顟B(tài)在 JWT的這些特征使得我們可以完全依賴無(wú)狀態(tài)的特性提供數(shù)據(jù)API服務(wù)。因?yàn)镴WT并不使用Cookie的,所以你可以在任何域名提供你的API服務(wù)而不需要擔(dān)心跨域資源共享問(wèn)題(CORS) 下面的序列圖展示了該過(guò)程: 中文流程介紹:
說(shuō)了這么多JWT到底如何應(yīng)用到我們的項(xiàng)目中,下面我們就使用SpringBoot 結(jié)合 JWT完成用戶的登錄驗(yàn)證。 應(yīng)用流程
搭建SpringBoot JWT工程下面通過(guò)代碼來(lái)實(shí)現(xiàn)用戶認(rèn)證的功能,博主這里主要采用Spring Boot與JWT整合的方式實(shí)現(xiàn)。關(guān)于Spring Boot項(xiàng)目如何搭建與使用本章不做詳細(xì)介紹。
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
@Data@ConfigurationProperties(prefix = 'audience')@Componentpublic class Audience { private String clientId; private String base64Secret; private String name; private int expiresSecond;
} JWT驗(yàn)證主要是通過(guò)過(guò)濾器驗(yàn)證,所以我們需要添加一個(gè)攔截器來(lái)演請(qǐng)求頭中是否包含有后臺(tái)頒發(fā)的
package com.thtf.util;import com.thtf.common.exception.CustomException;import com.thtf.common.response.ResultCode;import com.thtf.model.Audience;import io.jsonwebtoken.*;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import javax.crypto.spec.SecretKeySpec;import javax.xml.bind.DatatypeConverter;import java.security.Key;import java.util.Date;/**
* ========================
* Created with IntelliJ IDEA.
* User:pyy
* Date:2019/7/17 17:24
* Version: v1.0
* ========================
*/public class JwtTokenUtil { private static Logger log = LoggerFactory.getLogger(JwtTokenUtil.class); public static final String AUTH_HEADER_KEY = 'Authorization'; public static final String TOKEN_PREFIX = 'Bearer '; /**
* 解析jwt
* @param jsonWebToken
* @param base64Security
* @return
*/
public static Claims parseJWT(String jsonWebToken, String base64Security) { try {
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(base64Security))
.parseClaimsJws(jsonWebToken).getBody(); return claims;
} catch (ExpiredJwtException eje) {
log.error('===== Token過(guò)期 =====', eje); throw new CustomException(ResultCode.PERMISSION_TOKEN_EXPIRED);
} catch (Exception e){
log.error('===== token解析異常 =====', e); throw new CustomException(ResultCode.PERMISSION_TOKEN_INVALID);
}
} /**
* 構(gòu)建jwt
* @param userId
* @param username
* @param role
* @param audience
* @return
*/
public static String createJWT(String userId, String username, String role, Audience audience) { try { // 使用HS256加密算法
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis); //生成簽名密鑰
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(audience.getBase64Secret());
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); //userId是重要信息,進(jìn)行加密下
String encryId = Base64Util.encode(userId); //添加構(gòu)成JWT的參數(shù)
JwtBuilder builder = Jwts.builder().setHeaderParam('typ', 'JWT') // 可以將基本不重要的對(duì)象信息放到claims
.claim('role', role)
.claim('userId', userId)
.setSubject(username) // 代表這個(gè)JWT的主體,即它的所有人
.setIssuer(audience.getClientId()) // 代表這個(gè)JWT的簽發(fā)主體;
.setIssuedAt(new Date()) // 是一個(gè)時(shí)間戳,代表這個(gè)JWT的簽發(fā)時(shí)間;
.setAudience(audience.getName()) // 代表這個(gè)JWT的接收對(duì)象;
.signWith(signatureAlgorithm, signingKey); //添加Token過(guò)期時(shí)間
int TTLMillis = audience.getExpiresSecond(); if (TTLMillis >= 0) { long expMillis = nowMillis TTLMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp) // 是一個(gè)時(shí)間戳,代表這個(gè)JWT的過(guò)期時(shí)間;
.setNotBefore(now); // 是一個(gè)時(shí)間戳,代表這個(gè)JWT生效的開(kāi)始時(shí)間,意味著在這個(gè)時(shí)間之前驗(yàn)證JWT是會(huì)失敗的
} //生成JWT
return builder.compact();
} catch (Exception e) {
log.error('簽名失敗', e); throw new CustomException(ResultCode.PERMISSION_SIGNATURE_ERROR);
}
} /**
* 從token中獲取用戶名
* @param token
* @param base64Security
* @return
*/
public static String getUsername(String token, String base64Security){ return parseJWT(token, base64Security).getSubject();
} /**
* 從token中獲取用戶ID
* @param token
* @param base64Security
* @return
*/
public static String getUserId(String token, String base64Security){
String userId = parseJWT(token, base64Security).get('userId', String.class); return Base64Util.decode(userId);
} /**
* 是否已過(guò)期
* @param token
* @param base64Security
* @return
*/
public static boolean isExpiration(String token, String base64Security) { return parseJWT(token, base64Security).getExpiration().before(new Date());
}
}
package com.thtf.config;import com.thtf.interceptor.JwtInterceptor;import org.springframework.boot.web.servlet.FilterRegistrationBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.CorsRegistry;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/**
* ========================
* Created with IntelliJ IDEA.
* User:pyy
* Date:2019/7/18 10:37
* Version: v1.0
* ========================
*/@Configurationpublic class WebConfig implements WebMvcConfigurer { /**
* 添加攔截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) { //攔截路徑可自行配置多個(gè) 可用 ,分隔開(kāi)
registry.addInterceptor(new JwtInterceptor()).addPathPatterns('/**');
} /**
* 跨域支持
*
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping('/**')
.allowedOrigins('*')
.allowCredentials(true)
.allowedMethods('GET', 'POST', 'DELETE', 'PUT', 'PATCH', 'OPTIONS', 'HEAD')
.maxAge(3600 * 24);
}
} 這里JWT可能會(huì)有跨域問(wèn)題,配置跨域支持。
沒(méi)有登錄時(shí)候直接訪問(wèn):http://localhost:8080/users 接口: 執(zhí)行登錄: 攜帶生成token再次訪問(wèn):http://localhost:8080/users 接口
|
|