欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot整合JWT的實(shí)現(xiàn)示例

 更新時(shí)間:2022年01月10日 16:27:55   作者:AkiraNicky  
JWT是目前比較流行的跨域認(rèn)證解決方案,本文主要介紹了SpringBoot整合JWT的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

一. JWT簡(jiǎn)介

1. 什么是JWT?

JWT(JSON Web Token)是為了在網(wǎng)絡(luò)應(yīng)用環(huán)境間傳遞聲明而執(zhí)行的一種基于JSON的開放標(biāo)準(zhǔn)。

它將用戶信息加密到token里,服務(wù)器不保存任何用戶信息。服務(wù)器通過使用保存的密鑰驗(yàn)證token的正確性,只要正確即通過驗(yàn)證;應(yīng)用場(chǎng)景如用戶登錄。JWT詳細(xì)講解請(qǐng)見 github:https://github.com/jwtk/jjwt

2. 為什么使用JWT?

隨著技術(shù)的發(fā)展,分布式web應(yīng)用的普及,通過session管理用戶登錄狀態(tài)成本越來越高,因此慢慢發(fā)展成為token的方式做登錄身份校驗(yàn),然后通過token去取redis中的緩存的用戶信息,隨著之后jwt的出現(xiàn),校驗(yàn)方式更加簡(jiǎn)單便捷化,無需通過redis緩存,而是直接根據(jù)token取出保存的用戶信息,以及對(duì)token可用性校驗(yàn),單點(diǎn)登錄更為簡(jiǎn)單。

3. 傳統(tǒng)Cookie+Session與JWT對(duì)比

① 在傳統(tǒng)的用戶登錄認(rèn)證中,因?yàn)閔ttp是無狀態(tài)的,所以都是采用session方式。用戶登錄成功,服務(wù)端會(huì)保證一個(gè)session,當(dāng)然會(huì)給客戶端一個(gè)sessionId,客戶端會(huì)把sessionId保存在cookie中,每次請(qǐng)求都會(huì)攜帶這個(gè)sessionId。

cookie+session這種模式通常是保存在內(nèi)存中,而且服務(wù)從單服務(wù)到多服務(wù)會(huì)面臨的session共享問題,隨著用戶量的增多,開銷就會(huì)越大。而JWT不是這樣的,只需要服務(wù)端生成token,客戶端保存這個(gè)token,每次請(qǐng)求攜帶這個(gè)token,服務(wù)端認(rèn)證解析就可。

② JWT方式校驗(yàn)方式更加簡(jiǎn)單便捷化,無需通過redis緩存,而是直接根據(jù)token取出保存的用戶信息,以及對(duì)token可用性校驗(yàn),單點(diǎn)登錄,驗(yàn)證token更為簡(jiǎn)單。

4. JWT的組成(3部分)

第一部分為頭部(header),第二部分我們稱其為載荷(payload),第三部分是簽證(signature)?!局虚g用 . 分隔】

一個(gè)標(biāo)準(zhǔn)的JWT生成的token格式如下:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiI1IiwiaWF0IjoxNTY1NTk3MDUzLCJleHAiOjE1NjU2MDA2NTN9.qesdk6aeFEcNafw5WFm-TwZltGWb1Xs6oBEk5QdaLzlHxDM73IOyeKPF_iN1bLvDAlB7UnSu-Z-Zsgl_dIlPiw

5. JWT驗(yàn)證流程和特點(diǎn)

驗(yàn)證流程:

① 在頭部信息中聲明加密算法和常量, 然后把header使用json轉(zhuǎn)化為字符串
② 在載荷中聲明用戶信息,同時(shí)還有一些其他的內(nèi)容;再次使用json 把載荷部分進(jìn)行轉(zhuǎn)化,轉(zhuǎn)化為字符串
③ 使用在header中聲明的加密算法和每個(gè)項(xiàng)目隨機(jī)生成的secret來進(jìn)行加密, 把第一步分字符串和第二部分的字符串進(jìn)行加密, 生成新的字符串。詞字符串是獨(dú)一無二的。
④ 解密的時(shí)候,只要客戶端帶著JWT來發(fā)起請(qǐng)求,服務(wù)端就直接使用secret進(jìn)行解密。

特點(diǎn):

① 三部分組成,每一部分都進(jìn)行字符串的轉(zhuǎn)化
② 解密的時(shí)候沒有使用數(shù)據(jù)庫(kù),僅僅使用的是secret進(jìn)行解密
③ JWT的secret千萬(wàn)不能泄密!

6. JWT優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

①. 可擴(kuò)展性好

應(yīng)用程序分布式部署的情況下,Session需要做多機(jī)數(shù)據(jù)共享,通常可以存在數(shù)據(jù)庫(kù)或者Redis里面。而JWT不需要。

②. 無狀態(tài)

JWT不在服務(wù)端存儲(chǔ)任何狀態(tài)。RESTful API的原則之一是無狀態(tài),發(fā)出請(qǐng)求時(shí),總會(huì)返回帶有參數(shù)的響應(yīng),不會(huì)產(chǎn)生附加影響。用戶的認(rèn)證狀態(tài)引入這種附加影響,這破壞了這一原則。另外JWT的載荷中可以存儲(chǔ)一些常用信息,用于交換信息,有效地使用 JWT,可以降低服務(wù)器查詢數(shù)據(jù)庫(kù)的次數(shù)。

缺點(diǎn):

① 安全性:由于JWT的payload是使用Base64編碼的,并沒有加密,因此JWT中不能存儲(chǔ)敏感數(shù)據(jù)。而Session的信息是存在服務(wù)端的,相對(duì)來說更安全。

② 性能:JWT太長(zhǎng)。由于是無狀態(tài)使用JWT,所有的數(shù)據(jù)都被放到JWT里,如果還要進(jìn)行一些數(shù)據(jù)交換,那載荷會(huì)更大,經(jīng)過編碼之后導(dǎo)致JWT非常長(zhǎng),Cookie的限制大小一般是4k,cookie很可能放不下,所以JWT一般放在LocalStorage里面。并且用戶在系統(tǒng)中的每一次Http請(qǐng)求都會(huì)把JWT攜帶在Header里面,Http請(qǐng)求的Header可能比Body還要大。而SessionId只是很短的一個(gè)字符串,因此使用JWT的Http請(qǐng)求比使用Session的開銷大得多。

③ 一次性:無狀態(tài)是JWT的特點(diǎn),但也導(dǎo)致了這個(gè)問題,JWT是一次性的。想修改里面的內(nèi)容,就必須簽發(fā)一個(gè)新的JWT。即缺陷是一旦下發(fā),服務(wù)后臺(tái)無法拒絕攜帶該jwt的請(qǐng)求(如踢除用戶)

(1)無法廢棄:通過JWT的驗(yàn)證機(jī)制可以看出來,一旦簽發(fā)一個(gè)JWT,在到期之前就會(huì)始終有效,無法中途廢棄。例如你在payload中存儲(chǔ)了一些信息,當(dāng)信息需要更新時(shí),則重新簽發(fā)一個(gè)JWT,但是由于舊的jwt還沒過期,拿著這個(gè)舊的JWT依舊可以登錄,那登錄后服務(wù)端從JWT中拿到的信息就是過時(shí)的。為了解決這個(gè)問題,我們就需要在服務(wù)端部署額外的邏輯,例如設(shè)置一個(gè)黑名單,一旦簽發(fā)了新的JWT,那么舊的就加入黑名單(比如存到redis里面),避免被再次使用。

(2)續(xù)簽:如果你使用jwt做會(huì)話管理,傳統(tǒng)的Cookie續(xù)簽方案一般都是框架自帶的,Session有效期30分鐘,30分鐘內(nèi)如果有訪問,有效期被刷新至30分鐘。一樣的道理,要改變JWT的有效時(shí)間,就要簽發(fā)新的JWT。最簡(jiǎn)單的一種方式是每次請(qǐng)求刷新JWT,即每個(gè)HTTP請(qǐng)求都返回一個(gè)新的JWT。這個(gè)方法不僅暴力不優(yōu)雅,而且每次請(qǐng)求都要做JWT的加密解密,會(huì)帶來性能問題。另一種方法是在Redis中單獨(dú)為每個(gè)JWT設(shè)置過期時(shí)間,每次訪問時(shí)刷新JWT的過期時(shí)間。

可以看出想要破解JWT一次性的特性,就需要在服務(wù)端存儲(chǔ)jwt的狀態(tài)。但是引入 redis 之后,就把無狀態(tài)的jwt硬生生變成了有狀態(tài)了,違背了JWT的初衷。而且這個(gè)方案和Session都差不多了。

二. Java實(shí)現(xiàn)JWT(SpringBoot方式整合)

 1. Maven依賴與application.yml配置

<!-- JWT依賴 -->
<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>
server:
  port: 8080
spring:
  application:
    name: springboot-jwt
config:
  jwt:
    # 加密密鑰
    secret: abcdefg1234567
    # token有效時(shí)長(zhǎng)
    expire: 3600
    # header 名稱
    header: token

 2. 編寫JwtConfig

package com.example.config;
 
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
 
/**
 * JWT的token,區(qū)分大小寫
 */
@ConfigurationProperties(prefix = "config.jwt")
@Component
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);//過期時(shí)間
 
        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){
//            e.printStackTrace();
            return null;
        }
    }
    /**
     * 驗(yàn)證token是否過期失效
     * @param expirationTime
     * @return
     */
    public boolean isTokenExpired (Date expirationTime) {
        return expirationTime.before(new Date());
    }
 
    /**
     * 獲取token失效時(shí)間
     * @param token
     * @return
     */
    public Date getExpirationDateFromToken(String token) {
        return getTokenClaim(token).getExpiration();
    }
    /**
     * 獲取用戶名從token中
     */
    public String getUsernameFromToken(String token) {
        return getTokenClaim(token).getSubject();
    }
 
    /**
     * 獲取jwt發(fā)布時(shí)間
     */
    public Date getIssuedAtDateFromToken(String token) {
        return getTokenClaim(token).getIssuedAt();
    }
 
    // --------------------- getter & setter ---------------------
 
    public String getSecret() {
        return secret;
    }
    public void setSecret(String secret) {
        this.secret = secret;
    }
    public long getExpire() {
        return expire;
    }
    public void setExpire(long expire) {
        this.expire = expire;
    }
    public String getHeader() {
        return header;
    }
    public void setHeader(String header) {
        this.header = header;
    }
}

 3. 配置攔截器

package com.example.interceptor;
 
import com.example.config.JwtConfig;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.SignatureException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
@Component
public class TokenInterceptor extends HandlerInterceptorAdapter {
 
    @Resource
    private JwtConfig jwtConfig ;
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws SignatureException {
        /** 地址過濾 */
        String uri = request.getRequestURI() ;
        if (uri.contains("/login")){
            return true ;
        }
        /** Token 驗(yàn)證 */
        String token = request.getHeader(jwtConfig.getHeader());
        if(StringUtils.isEmpty(token)){
            token = request.getParameter(jwtConfig.getHeader());
        }
        if(StringUtils.isEmpty(token)){
            throw new SignatureException(jwtConfig.getHeader()+ "不能為空");
        }
 
        Claims claims = null;
        try{
            claims = jwtConfig.getTokenClaim(token);
            if(claims == null || jwtConfig.isTokenExpired(claims.getExpiration())){
                throw new SignatureException(jwtConfig.getHeader() + "失效,請(qǐng)重新登錄。");
            }
        }catch (Exception e){
            throw new SignatureException(jwtConfig.getHeader() + "失效,請(qǐng)重新登錄。");
        }
 
        /** 設(shè)置 identityId 用戶身份ID */
        request.setAttribute("identityId", claims.getSubject());
        return true;
    }
}

  注冊(cè)攔截器到SpringMvc

package com.example.config;
 
import com.example.interceptor.TokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
 
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Resource
    private TokenInterceptor tokenInterceptor ;
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tokenInterceptor).addPathPatterns("/**");
    }
}

 4. 編寫統(tǒng)一異常處理類

package com.example.config;
import io.jsonwebtoken.SignatureException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.yuyi.full.handler.exception.ExceptionInfoBO;
import org.yuyi.full.handler.exception.ResultBO;
import org.yuyi.full.handler.exception.ResultTool;
 
@RestControllerAdvice
public class PermissionHandler {
    @ExceptionHandler(value = { SignatureException.class })
    @ResponseBody
    public ResultBO<?> authorizationException(SignatureException e){
        return ResultTool.error(new ExceptionInfoBO(1008,e.getMessage()));
    }
}

 5.編寫測(cè)試接口

package com.example.controller;
 
import com.alibaba.fastjson.JSONObject;
import com.example.config.JwtConfig;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.yuyi.full.handler.exception.ResultBO;
import org.yuyi.full.handler.exception.ResultTool;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
 
@RestController
public class TokenController {
 
    @Resource
    private JwtConfig jwtConfig ;
 
    /**
     * 用戶登錄接口
     * @param userName
     * @param passWord
     * @return
     */
    @PostMapping("/login")
    public ResultBO<?> login (@RequestParam("userName") String userName,
                           @RequestParam("passWord") String passWord){
        JSONObject json = new JSONObject();
 
        /** 驗(yàn)證userName,passWord和數(shù)據(jù)庫(kù)中是否一致,如不一致,直接return ResultTool.errer(); 【這里省略該步驟】*/
 
        // 這里模擬通過用戶名和密碼,從數(shù)據(jù)庫(kù)查詢userId
        // 這里把userId轉(zhuǎn)為String類型,實(shí)際開發(fā)中如果subject需要存userId,則可以JwtConfig的createToken方法的參數(shù)設(shè)置為L(zhǎng)ong類型
        String userId = 5 + "";
        String token = jwtConfig.createToken(userId) ;
        if (!StringUtils.isEmpty(token)) {
            json.put("token",token) ;
        }
        return ResultTool.success(json) ;
    }
 
    /**
     * 需要 Token 驗(yàn)證的接口
     */
    @PostMapping("/info")
    public ResultBO<?> info (){
        return ResultTool.success("info") ;
    }
 
    /**
     * 根據(jù)請(qǐng)求頭的token獲取userId
     * @param request
     * @return
     */
    @GetMapping("/getUserInfo")
    public ResultBO<?> getUserInfo(HttpServletRequest request){
        String usernameFromToken = jwtConfig.getUsernameFromToken(request.getHeader("token"));
        return ResultTool.success(usernameFromToken) ;
    }
 
    /*
        為什么項(xiàng)目重啟后,帶著之前的token還可以訪問到需要info等需要token驗(yàn)證的接口?
        答案:只要不過期,會(huì)一直存在,類似于redis
     */
 
}

用PostMan測(cè)試工具測(cè)試一下,訪問登錄接口,當(dāng)對(duì)賬號(hào)密碼驗(yàn)證通過時(shí),則返回一個(gè)token給客戶端:

 當(dāng)直接去訪問info接口時(shí),會(huì)返回token為空的自定義異常:

 當(dāng)在請(qǐng)求頭加上正確token時(shí),則攔截器驗(yàn)證通過,可以正常訪問到接口:

 當(dāng)在請(qǐng)求頭加入一個(gè)錯(cuò)誤token,則會(huì)返回token失效的自定義異常:

 接下來測(cè)試一下獲取用戶信息,因?yàn)檫@里存的subject為userId,所以直接返回上面寫死的假數(shù)據(jù)5:

JWT總結(jié)

1. 基于JSON,所以JWT是可以進(jìn)行跨語(yǔ)言支持的,像JAVA,JavaScript,Node.JS,PHP等很多語(yǔ)言都可以使用。

2. payload部分,需要時(shí)JWT可以存儲(chǔ)一些其他業(yè)務(wù)邏輯所必要的非敏感信息。

3. 體積小巧,便于傳輸;JWT的構(gòu)成非常簡(jiǎn)單,字節(jié)占用很小,所以它是非常便于傳輸?shù)?。它不需要在服?wù)端保存會(huì)話信息, 所以它易于應(yīng)用的擴(kuò)展。

到此這篇關(guān)于SpringBoot整合JWT的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)SpringBoot整合JWT內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 微信小程序--Ble藍(lán)牙

    微信小程序--Ble藍(lán)牙

    本文主要介紹了微信小程序--Ble藍(lán)牙的實(shí)現(xiàn)方法。文中附上源碼下載,具有很好的參考價(jià)值。下面跟著小編一起來看下吧
    2017-04-04
  • IntelliJ IDEA中出現(xiàn)

    IntelliJ IDEA中出現(xiàn)"PSI and index do not match"錯(cuò)誤的解決辦法

    今天小編就為大家分享一篇關(guān)于IntelliJ IDEA中出現(xiàn)"PSI and index do not match"錯(cuò)誤的解決辦法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2018-10-10
  • 新手入門Jvm--jvm概覽

    新手入門Jvm--jvm概覽

    JVM是Java Virtual Machine(Java虛擬機(jī))的縮寫,JVM是一種用于計(jì)算設(shè)備的規(guī)范,它是一個(gè)虛構(gòu)出來的計(jì)算機(jī),是通過在實(shí)際的計(jì)算機(jī)上仿真模擬各種計(jì)算機(jī)功能來實(shí)現(xiàn)的
    2021-06-06
  • tk-mybatis整合springBoot使用兩個(gè)數(shù)據(jù)源的方法

    tk-mybatis整合springBoot使用兩個(gè)數(shù)據(jù)源的方法

    單純的使用mybaits進(jìn)行多數(shù)據(jù)配置網(wǎng)上資料很多,但是關(guān)于tk-mybaits多數(shù)據(jù)源配置沒有相關(guān)材料,本文就詳細(xì)的介紹一下如何使用,感興趣的可以了解一下
    2021-12-12
  • jdk7 中HashMap的知識(shí)點(diǎn)總結(jié)

    jdk7 中HashMap的知識(shí)點(diǎn)總結(jié)

    HashMap的原理是老生常談了,不作仔細(xì)解說。一句話概括為HashMap是一個(gè)散列表,它存儲(chǔ)的內(nèi)容是鍵值對(duì)(key-value)映射。這篇文章主要總結(jié)了關(guān)于jdk7 中HashMap的知識(shí)點(diǎn),需要的朋友可以參考借鑒,一起來看看吧。
    2017-01-01
  • 劍指Offer之Java算法習(xí)題精講二叉樹專題篇上

    劍指Offer之Java算法習(xí)題精講二叉樹專題篇上

    跟著思路走,之后從簡(jiǎn)單題入手,反復(fù)去看,做過之后可能會(huì)忘記,之后再做一次,記不住就反復(fù)做,反復(fù)尋求思路和規(guī)律,慢慢積累就會(huì)發(fā)現(xiàn)質(zhì)的變化
    2022-03-03
  • java獲取當(dāng)前時(shí)間戳的方法

    java獲取當(dāng)前時(shí)間戳的方法

    本文主要介紹了java獲取當(dāng)前時(shí)間戳的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • 對(duì)ArrayList和LinkedList底層實(shí)現(xiàn)原理詳解

    對(duì)ArrayList和LinkedList底層實(shí)現(xiàn)原理詳解

    今天小編就為大家分享一篇對(duì)ArrayList和LinkedList底層實(shí)現(xiàn)原理詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-10-10
  • java常見log日志的使用方法解析

    java常見log日志的使用方法解析

    本文主要介紹了java常見log日志的使用方法解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • Java使用建造者模式實(shí)現(xiàn)辦理手機(jī)套餐功能詳解

    Java使用建造者模式實(shí)現(xiàn)辦理手機(jī)套餐功能詳解

    這篇文章主要介紹了Java使用建造者模式實(shí)現(xiàn)辦理手機(jī)套餐功能,較為詳細(xì)的描述了建造者模式的概念、原理并結(jié)合實(shí)例形式分析了Java使用建造者模式實(shí)現(xiàn)的辦理手機(jī)套餐功能具體步驟與相關(guān)操作注意事項(xiàng),需要的朋友可以參考下
    2018-05-05

最新評(píng)論