最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

springboot結(jié)合JWT實(shí)現(xiàn)單點(diǎn)登錄的示例

 更新時(shí)間:2025年01月31日 09:35:16   作者:Sao_E  
本文主要介紹了springboot結(jié)合JWT實(shí)現(xiàn)單點(diǎn)登錄的示例,包括生成Token、驗(yàn)證Token及使用Redis存儲(chǔ)Token,具有一定的參考價(jià)值,感興趣的可以了解一下

JWT實(shí)現(xiàn)單點(diǎn)登錄

  • 登錄流程:
    校驗(yàn)用戶名密碼->生成隨機(jī)JWT Token->返回給前端。之后前端發(fā)請求攜帶該Token就能驗(yàn)證是哪個(gè)用戶了。
  • 校驗(yàn)流程:
    從前端請求的header獲取JWT Token->根據(jù)工具包校驗(yàn)JWT Token->校驗(yàn)成功或失敗

JWT 簡介

結(jié)構(gòu)
Header 頭部信息,主要聲明了JWT的簽名算法等信息
Payload 載荷信息,主要承載了各種聲明并傳遞明文數(shù)據(jù)
Signature 簽名,擁有該部分的JWT被稱為JWS,也就是簽了名的JWT,用于校驗(yàn)數(shù)據(jù)
整體結(jié)構(gòu)是:

header.payload.signature

參考文檔:https://doc.hutool.cn/pages/jwt/

存在問題及解決方案

  • token被解密:如工具包被獲取??赏ㄟ^增加“鹽值”來解決。

  • token被拿到第三方使用:如被包裝到第三方使用(ChatGPT工具),可以通過限流來解決。

登錄流程

后端程序?qū)崿F(xiàn)

封裝hutool工具類:

public class JwtUtil {
    private static final Logger LOG = LoggerFactory.getLogger(JwtUtil.class);

    /**
     * 鹽值很重要,不能泄漏,且每個(gè)項(xiàng)目都應(yīng)該不一樣,可以放到配置文件中
     */
    private static final String key = "xxx";

    public static String createToken(Long id, String mobile) {
        LOG.info("開始生成JWT token,id:{},mobile:{}", id, mobile);
        GlobalBouncyCastleProvider.setUseBouncyCastle(false);
        DateTime now = DateTime.now();
        DateTime expTime = now.offsetNew(DateField.HOUR, 24);
//        DateTime expTime = now.offsetNew(DateField.SECOND, 10);

        Map<String, Object> payload = new HashMap<>();
        // 簽發(fā)時(shí)間
        payload.put(JWTPayload.ISSUED_AT, now);
        // 過期時(shí)間
        payload.put(JWTPayload.EXPIRES_AT, expTime);
        // 生效時(shí)間
        payload.put(JWTPayload.NOT_BEFORE, now);
        // 內(nèi)容
        payload.put("id", id);
        payload.put("mobile", mobile);
        String token = JWTUtil.createToken(payload, key.getBytes());
        LOG.info("生成JWT token:{}", token);
        return token;
    }

    public static boolean validate(String token) {
        LOG.info("開始JWT token校驗(yàn),token:{}", token);
        GlobalBouncyCastleProvider.setUseBouncyCastle(false);
        JWT jwt = JWTUtil.parseToken(token).setKey(key.getBytes());
        // validate包含了verify
        boolean validate = jwt.validate(0);
        LOG.info("JWT token校驗(yàn)結(jié)果:{}", validate);
        return validate;
    }

    public static JSONObject getJSONObject(String token) {
        GlobalBouncyCastleProvider.setUseBouncyCastle(false);
        JWT jwt = JWTUtil.parseToken(token).setKey(key.getBytes());
        JSONObject payloads = jwt.getPayloads();
        payloads.remove(JWTPayload.ISSUED_AT);
        payloads.remove(JWTPayload.EXPIRES_AT);
        payloads.remove(JWTPayload.NOT_BEFORE);
        LOG.info("根據(jù)token獲取原始內(nèi)容:{}", payloads);
        return payloads;
    }

    public static void main(String[] args) {
        createToken(1L, "123");

        String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE3MzY0ODczMDQsIm1vYmlsZSI6IjEyMyIsImlkIjoxLCJleHAiOjE3MzY1NzM3MDQsImlhdCI6MTczNjQ4NzMwNH0.Bui7guCvPEF557eqxRLwmt5tO-W-3oVLnn37H4qOVfA";
        validate(token);

        getJSONObject(token);
    }
}

后端定義登錄業(yè)務(wù):

    public MemberLoginResp login(MemberLoginReq memberLoginReq){
        String mobile = memberLoginReq.getMobile();
        String code = memberLoginReq.getCode();
        Member memberDB = selectByMobile(mobile);

        if (ObjectUtil.isEmpty(memberDB)){
            throw new BusinessException(BusinessExceptionEnum.MEMBER_MOBILE_NOT_EXIST);
        }

        if(!code.equals("8888")){
            throw new BusinessException(BusinessExceptionEnum.MEMBER_MOBILE_CODE_ERROR);
        }

        MemberLoginResp memberLoginResp = new MemberLoginResp();
        memberLoginResp.setId(memberDB.getId());
        memberLoginResp.setMobile(mobile);

        String token = JwtUtil.createToken(memberDB.getId(), memberDB.getMobile());
        memberLoginResp.setToken(token);

        return memberLoginResp;
    }

通過調(diào)用封裝的JwtUtil生成token并返回前端

在這里插入圖片描述

成功返回Token結(jié)果

前端保存Token

Vuex全局保存Token到store中

import { createStore } from 'vuex'

const MEMBER = "MEMBER";

export default createStore({
  state: {
    member: {}
  },
  getters: {
  },
  mutations: {
    setMember (state, _member) {
      state.member = _member;
    }
  },
  actions: {
  },
  modules: {
  }
})

    const login = () => {
      axios.post("/member/member/login", loginForm).then((response) => {
        let data = response.data;
        if (data.success) {
          notification.success({ description: '登錄成功!' });
          // 登錄成功,跳到控臺(tái)主頁
          router.push("/welcome");
          store.commit("setMember", data.content);
        } else {
          notification.error({ description: data.message });
        }
      })
    };

store存放信息的缺點(diǎn)及解決

store存放用戶信息后,如果刷新頁面,那么信息也會(huì)消失!store可以理解為緩存,一旦重新加載,則緩存全都沒了。

解決方法:

  • step1. 新增session-storage.js,封裝會(huì)話緩存sessionStorage
// 所有的session key都在這里統(tǒng)一定義,可以避免多個(gè)功能使用同一個(gè)key
SESSION_ORDER = "SESSION_ORDER";
SESSION_TICKET_PARAMS = "SESSION_TICKET_PARAMS";

SessionStorage = {
    get: function (key) {
        var v = sessionStorage.getItem(key);
        if (v && typeof(v) !== "undefined" && v !== "undefined") {
            return JSON.parse(v);
        }
    },
    set: function (key, data) {
        sessionStorage.setItem(key, JSON.stringify(data));
    },
    remove: function (key) {
        sessionStorage.removeItem(key);
    },
    clearAll: function () {
        sessionStorage.clear();
    }
};

  • step2. 在index.html中引入該js
<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" rel="external nofollow" >
        <!-- 引入js -->
      <script src="<%= BASE_URL %>js/session-storage.js"></script>
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>
  • step3. 修改store的index.js
const MEMBER = "MEMBER";

export default createStore({
  state: {
    member: window.SessionStorage.get(MEMBER) || {} # 讀取
  },
  getters: {
  },
  mutations: {
    setMember (state, _member) {
      state.member = _member;
      window.SessionStorage.set(MEMBER, _member); # 設(shè)置
    }
  },

不再是把member定義為{},而是首先在緩存中獲取,如果沒有則設(shè)置為{}。同時(shí)避免空指針
同時(shí)在用戶登錄后設(shè)置MEMBER緩存

校驗(yàn)流程:為gateway增加登錄校驗(yàn)攔截器

  • 添加依賴
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>5.8.10</version>
            </dependency>
  • 攔截器類
@Component
public class LoginMemberFilter implements Ordered, GlobalFilter {

    private static final Logger LOG = LoggerFactory.getLogger(LoginMemberFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getURI().getPath();

        // 排除不需要攔截的請求
        if (path.contains("/admin")
                || path.contains("/redis")
                || path.contains("/test")
                || path.contains("/member/member/login")
                || path.contains("/member/member/send-code")) {
            LOG.info("不需要登錄驗(yàn)證:{}", path);
            return chain.filter(exchange);
        } else {
            LOG.info("需要登錄驗(yàn)證:{}", path);
        }
        // 獲取header的token參數(shù)
        String token = exchange.getRequest().getHeaders().getFirst("token");
        LOG.info("會(huì)員登錄驗(yàn)證開始,token:{}", token);
        if (token == null || token.isEmpty()) {
            LOG.info( "token為空,請求被攔截" );
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }

        // 校驗(yàn)token是否有效,包括token是否被改過,是否過期
        boolean validate = JwtUtil.validate(token);
        if (validate) {
            LOG.info("token有效,放行該請求");
            return chain.filter(exchange);
        } else {
            LOG.warn( "token無效,請求被攔截" );
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }

    }

    /**
     * 優(yōu)先級設(shè)置  值越小  優(yōu)先級越高
     *
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}
  • 測試結(jié)果:
  • 直接調(diào)用不需要驗(yàn)證登錄的接口
@RestController
public class TestController {

    @GetMapping("/test")
    public String test(){
        return "test";
    }

}

在這里插入圖片描述

調(diào)用需要登錄的接口方法(未登錄)

在這里插入圖片描述

同時(shí)服務(wù)器端沒有打印,表示請求已被攔截

調(diào)用login登陸后再次執(zhí)行上述請求
login打印日志:

在這里插入圖片描述

調(diào)用請求打印日志:

在這里插入圖片描述

可見成功校驗(yàn)token,并讀取登錄用戶信息,通過校驗(yàn)

另一種單點(diǎn)登錄方法:Token+Redis實(shí)現(xiàn)單點(diǎn)登錄

  • 登錄流程:
    校驗(yàn)用戶名密碼->生成隨機(jī)Token->將Token存放到Redis,并返回給前端。
    之后前端發(fā)請求攜帶該Token就能驗(yàn)證是哪個(gè)用戶了。
  • 校驗(yàn)流程:
    從前端請求的header獲取Token->根據(jù)Token到Redis獲取用戶數(shù)據(jù)->若有數(shù)據(jù)則登錄校驗(yàn)通過,否則失敗

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

相關(guān)文章

  • 在MyBatisPlus中使用@TableField完成字段自動(dòng)填充的操作

    在MyBatisPlus中使用@TableField完成字段自動(dòng)填充的操作

    這篇文章主要介紹了在MyBatisPlus中使用@TableField完成字段自動(dòng)填充的操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-02-02
  • mybatis處理枚舉類的簡單方法

    mybatis處理枚舉類的簡單方法

    這篇文章主要給大家介紹了關(guān)于mybatis處理枚舉類的簡單方法,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用mybatis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05
  • java中對List分段操作的實(shí)例

    java中對List分段操作的實(shí)例

    這篇文章主要介紹了java中對List分段操作的實(shí)例的相關(guān)資料,希望通過本文大家能夠掌握list的分段實(shí)現(xiàn)方法,需要的朋友可以參考下
    2017-09-09
  • JAVA String轉(zhuǎn)化成java.sql.date和java.sql.time方法示例

    JAVA String轉(zhuǎn)化成java.sql.date和java.sql.time方法示例

    這篇文章主要給大家分享了關(guān)于JAVA String轉(zhuǎn)化成java.sql.date和java.sql.time的方法,文中給出了詳細(xì)的示例代碼,相信對大家具有一定的參考價(jià)值,需要的朋友們下面來一起看看吧。
    2017-03-03
  • 淺談Spring?中?@EnableXXX?注解的套路

    淺談Spring?中?@EnableXXX?注解的套路

    本文主要介紹了淺談Spring?中?@EnableXXX?注解的套路,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • 關(guān)于泛型擦除問題的解決--Mybatis查詢類型轉(zhuǎn)換

    關(guān)于泛型擦除問題的解決--Mybatis查詢類型轉(zhuǎn)換

    這篇文章主要介紹了關(guān)于泛型擦除問題的解決--Mybatis查詢類型轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • 使用SpringCache操作Redis緩存數(shù)據(jù)的示例代碼

    使用SpringCache操作Redis緩存數(shù)據(jù)的示例代碼

    SpringCache是一個(gè)框架,實(shí)現(xiàn)了基于注解的緩存功能,只需要簡單的加一個(gè)注解,就能實(shí)現(xiàn)緩存功能,本文給大家介紹了如何使用SpringCache操作Redis緩存數(shù)據(jù),文中有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下
    2024-01-01
  • java通過isAccessAllowed方法實(shí)現(xiàn)訪問控制

    java通過isAccessAllowed方法實(shí)現(xiàn)訪問控制

    在Web應(yīng)用開發(fā)中,使用Apache Shiro框架的isAccessAllowed方法可以有效管理用戶的訪問權(quán)限,本文詳細(xì)解析了該方法的實(shí)現(xiàn)過程,包括用戶身份驗(yàn)證、權(quán)限判斷和安全性分析,下面就一起來了解一下
    2024-09-09
  • Java的Socket網(wǎng)絡(luò)編程基礎(chǔ)知識入門教程

    Java的Socket網(wǎng)絡(luò)編程基礎(chǔ)知識入門教程

    這篇文章主要介紹了Java的Socket網(wǎng)絡(luò)編程基礎(chǔ)知識入門教程,包括基于TCP/IP和UDP協(xié)議的簡單實(shí)例程序講解,需要的朋友可以參考下
    2016-01-01
  • SpringBoot中讀取配置文件的6種方式總結(jié)

    SpringBoot中讀取配置文件的6種方式總結(jié)

    這篇文章主要為大家詳細(xì)介紹了SpringBoot中讀取配置文件的六種方法,包括通過Environment,@Value,@ConfigurationPropertie等,感興趣的小伙伴可以了解下
    2026-03-03

最新評論

南安市| 石阡县| 宁化县| 曲阜市| 渑池县| 定远县| 锡林郭勒盟| 黄浦区| 普兰店市| 江达县| 图们市| 曲水县| 凤翔县| 祁连县| 奈曼旗| 军事| 方山县| 阜平县| 东方市| 贺兰县| 河源市| 东兴市| 察隅县| 永安市| 灵宝市| 滦平县| 项城市| 哈密市| 墨玉县| 特克斯县| 惠水县| 镇赉县| 许昌县| 固阳县| 双城市| 淮阳县| 宿松县| 滨州市| 隆林| 基隆市| 保德县|