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

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ā)請(qǐng)求攜帶該Token就能驗(yàn)證是哪個(gè)用戶了。
  • 校驗(yàn)流程:
    從前端請(qǐng)求的header獲取JWT Token->根據(jù)工具包校驗(yàn)JWT Token->校驗(yàn)成功或失敗

JWT 簡(jiǎn)介

結(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被解密:如工具包被獲取??赏ㄟ^增加“鹽值”來(lái)解決。

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

登錄流程

后端程序?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)主頁(yè)
          router.push("/welcome");
          store.commit("setMember", data.content);
        } else {
          notification.error({ description: data.message });
        }
      })
    };

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

store存放用戶信息后,如果刷新頁(yè)面,那么信息也會(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();

        // 排除不需要攔截的請(qǐng)求
        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為空,請(qǐng)求被攔截" );
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }

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

    }

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

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

}

在這里插入圖片描述

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

在這里插入圖片描述

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

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

在這里插入圖片描述

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

在這里插入圖片描述

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

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

  • 登錄流程:
    校驗(yàn)用戶名密碼->生成隨機(jī)Token->將Token存放到Redis,并返回給前端。
    之后前端發(fā)請(qǐng)求攜帶該Token就能驗(yàn)證是哪個(gè)用戶了。
  • 校驗(yàn)流程:
    從前端請(qǐng)求的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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家! 

相關(guān)文章

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

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

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

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

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

    MyBatis Generator介紹及使用方法

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

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

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

    springboot 在linux后臺(tái)運(yùn)行的方法

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

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

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

    Java?try?catch語(yǔ)句異常處理詳解

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

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

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

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

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

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

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

最新評(píng)論