亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

JWT實現(xiàn)單點登錄

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

JWT 簡介

結(jié)構(gòu)
Header 頭部信息,主要聲明了JWT的簽名算法等信息
Payload 載荷信息,主要承載了各種聲明并傳遞明文數(shù)據(jù)
Signature 簽名,擁有該部分的JWT被稱為JWS,也就是簽了名的JWT,用于校驗數(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);

    /**
     * 鹽值很重要,不能泄漏,且每個項目都應(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ā)時間
        payload.put(JWTPayload.ISSUED_AT, now);
        // 過期時間
        payload.put(JWTPayload.EXPIRES_AT, expTime);
        // 生效時間
        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校驗,token:{}", token);
        GlobalBouncyCastleProvider.setUseBouncyCastle(false);
        JWT jwt = JWTUtil.parseToken(token).setKey(key.getBytes());
        // validate包含了verify
        boolean validate = jwt.validate(0);
        LOG.info("JWT token校驗結(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: '登錄成功!' });
          // 登錄成功,跳到控臺主頁
          router.push("/welcome");
          store.commit("setMember", data.content);
        } else {
          notification.error({ description: data.message });
        }
      })
    };

store存放信息的缺點及解決

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

解決方法:

  • step1. 新增session-storage.js,封裝會話緩存sessionStorage
// 所有的session key都在這里統(tǒng)一定義,可以避免多個功能使用同一個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è)置MEMBER緩存

校驗流程:為gateway增加登錄校驗攔截器

  • 添加依賴
            <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("不需要登錄驗證:{}", path);
            return chain.filter(exchange);
        } else {
            LOG.info("需要登錄驗證:{}", path);
        }
        // 獲取header的token參數(shù)
        String token = exchange.getRequest().getHeaders().getFirst("token");
        LOG.info("會員登錄驗證開始,token:{}", token);
        if (token == null || token.isEmpty()) {
            LOG.info( "token為空,請求被攔截" );
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }

        // 校驗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)用不需要驗證登錄的接口
@RestController
public class TestController {

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

}

在這里插入圖片描述

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

在這里插入圖片描述

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

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

在這里插入圖片描述

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

在這里插入圖片描述

可見成功校驗token,并讀取登錄用戶信息,通過校驗

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

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

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

相關(guān)文章

  • springboot實現(xiàn)打印彩色日志

    springboot實現(xiàn)打印彩色日志

    這篇文章主要介紹了springboot實現(xiàn)打印彩色日志的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • SpringBoot整合JDBC的實現(xiàn)

    SpringBoot整合JDBC的實現(xiàn)

    這篇文章主要介紹了SpringBoot整合JDBC的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • MyBatis Generator介紹及使用方法

    MyBatis Generator介紹及使用方法

    MyBatis Generator 是一款針對 MyBatis 或 iBATIS 設(shè)計的代碼生成器,由 MyBatis 官方提供,這篇文章主要介紹了MyBatis Generator介紹及使用方法,需要的朋友可以參考下
    2023-06-06
  • SpringBoot中實現(xiàn)訂單30分鐘自動取消的三種方案分享

    SpringBoot中實現(xiàn)訂單30分鐘自動取消的三種方案分享

    在電商和其他涉及到在線支付的應(yīng)用中,通常需要實現(xiàn)一個功能:如果用戶在生成訂單后的一定時間內(nèi)未完成支付,系統(tǒng)將自動取消該訂單,本文將詳細(xì)介紹基于Spring Boot框架實現(xiàn)訂單30分鐘內(nèi)未支付自動取消的幾種方案,并提供實例代碼,需要的朋友可以參考下
    2023-10-10
  • springboot 在linux后臺運行的方法

    springboot 在linux后臺運行的方法

    這篇文章主要介紹了springboot 在linux后臺運行的方法,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-06-06
  • Java?for循環(huán)倒序輸出的操作代碼

    Java?for循環(huán)倒序輸出的操作代碼

    在Java中,要實現(xiàn)一個for循環(huán)的倒序輸出,通常我們會使用數(shù)組或集合(如ArrayList)作為數(shù)據(jù)源,然后通過倒序遍歷這個數(shù)組或集合來實現(xiàn),這篇文章主要介紹了Java?for循環(huán)倒序輸出,需要的朋友可以參考下
    2024-07-07
  • Java?try?catch語句異常處理詳解

    Java?try?catch語句異常處理詳解

    這篇文章主要給大家介紹了關(guān)于Java?try?catch語句異常處理的相關(guān)資料,Java中的try-catch用于捕獲和處理異常,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-11-11
  • Mybatis原始執(zhí)行方式Executor代碼實例

    Mybatis原始執(zhí)行方式Executor代碼實例

    這篇文章主要介紹了Mybatis原始執(zhí)行方式Executor代碼實例解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-07-07
  • Java 讀取PDF中的文本和圖片的方法

    Java 讀取PDF中的文本和圖片的方法

    本文將介紹通過Java程序來讀取PDF文檔中的文本和圖片的方法。分別調(diào)用方法extractText()和extractImages()來讀取,需要的朋友可以參考下
    2019-07-07
  • Java基礎(chǔ)之FileInputStream和FileOutputStream流詳解

    Java基礎(chǔ)之FileInputStream和FileOutputStream流詳解

    這篇文章主要介紹了Java基礎(chǔ)之FileInputStream和FileOutputStream流詳解,文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)java基礎(chǔ)的小伙伴們有非常好的幫助,需要的朋友可以參考下
    2021-04-04

最新評論