8 SpringBootWeb(下):登錄效驗、異步任務和多線程、SpringBoot中的事務管理@Transactional

文章目錄

  • 案例-登錄認證
  • 1. 登錄功能
    • 1.1 需求
    • 1.2 接口文檔
    • 1.3 思路分析
    • 1.4 功能開發
    • 1.5 測試
  • 2. 登錄校驗
    • 2.1 問題分析
    • 2.2 會話技術
      • 2.2.1 會話技術介紹
      • 2.2.2 會話跟蹤方案
        • 2.2.2.1 方案一 - Cookie
        • 2.2.2.2 方案二 - Session
        • 2.2.2.3 方案三 - 令牌技術
      • 2.2.3 JWT令牌(Token)
        • 2.2.3.1 介紹
        • 2.2.3.2 生成和校驗
        • 2.2.3.3 登錄下發令牌
    • 2.3 統一攔截技術
      • 2.3.1 方案一:過濾器Filter
        • 2.3.1.1 快速入門
        • 2.3.1.2 Filter詳解
          • 2.3.1.2.1 執行流程
          • 2.3.1.2.2 攔截路徑
          • 2.3.1.2.3 過濾器鏈
        • 2.3.1.3 登錄校驗-Filter
          • 2.3.1.3.1 分析
          • 2.3.1.3.2 具體流程
          • 2.3.1.3.3 代碼實現
      • 2.3.2 方案二:攔截器Interceptor(SpringBoot框架提供的)
        • 2.3.2.1 快速入門
        • 2.3.2.2 Interceptor詳解
          • 2.3.2.2.1 攔截路徑(可以指定攔截路徑,也可以指定哪些路徑不攔截)
          • 2.3.2.2.2 執行流程
        • 2.3.2.3 登錄校驗- Interceptor
  • 3 異步任務和多線程(重要!!!)
    • 1 多線程(這里主要是講的是SpringBoot項目部署后服務端的線程池問題)
    • 2 異步任務和同步任務
    • 3 異步任務和多線程的區別
  • 4. 事務管理(重要!!!)
    • 4.1 事務回顧
    • 4.2 Spring事務管理
      • 4.2.1 案例
      • 4.2.2 原因分析
      • 4.2.3 Transactional注解
    • 4.3 事務進階
      • 4.3.1 rollbackFor屬性(很重要這個屬性一定要加上):配置事務的回滾
      • 4.3.3 propagation屬性:配置事務的傳播行為
        • 4.3.3.1 介紹
        • 4.3.3.2 應用場景
          • 4.3.3.2.1 案例1 :轉賬操作(REQUIRED)?扣款和入賬只要有一個出錯,因異常導致整個事務都要回滾
          • 4.3.3.2.2 案例2:用戶注冊,異步郵件通知(重要!!)
          • 4.3.3.2.3 案例3: propagation = Propagation.REQUIRES_NEW在日志操作上的應用(重要!!)
            • 4.3.3.2.3.1 默認情況:propagation = Propagation.REQUIRED(不符合需求要求)
            • 4.3.3.2.3.2 使用Propagation.REQUIRES_NEW:@Transactional(propagation = Propagation.REQUIRES_NEW)


案例-登錄認證

在前面的課程中,我們已經實現了部門管理、員工管理的基本功能,但是大家會發現,我們并沒有登錄,就直接訪問到了Tlias智能學習輔助系統的后臺。 這是不安全的,所以我們今天的主題就是登錄認證。 最終我們要實現的效果就是用戶必須登錄之后,才可以訪問后臺系統中的功能。

在這里插入圖片描述


1. 登錄功能

1.1 需求

在這里插入圖片描述

在登錄界面中,我們可以輸入用戶的用戶名以及密碼,然后點擊 “登錄” 按鈕就要請求服務器,服務端判斷用戶輸入的用戶名或者密碼是否正確。如果正確,則返回成功結果,前端跳轉至系統首頁面。

1.2 接口文檔

我們參照接口文檔來開發登錄功能

  • 基本信息

    請求路徑:/login請求方式:POST接口描述:該接口用于員工登錄Tlias智能學習輔助系統,登錄完畢后,系統下發JWT令牌。 
    
  • 請求參數

    參數格式:application/json

    參數說明:

    名稱類型是否必須備注
    usernamestring必須用戶名
    passwordstring必須密碼

    請求數據樣例:

    {"username": "jinyong","password": "123456"
    }
    
  • 響應數據

    參數格式:application/json

    參數說明:

    名稱類型是否必須默認值備注其他信息
    codenumber必須響應碼, 1 成功 ; 0 失敗
    msgstring非必須提示信息
    datastring必須返回的數據 , jwt令牌

    響應數據樣例:

    {"code": 1,"msg": "success","data": "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi6YeR5bq4IiwiaWQiOjEsInVzZXJuYW1lIjoiamlueW9uZyIsImV4cCI6MTY2MjIwNzA0OH0.KkUc_CXJZJ8Dd063eImx4H9Ojfrr6XMJ-yVzaWCVZCo"
    }
    

1.3 思路分析

在這里插入圖片描述

登錄服務端的核心邏輯就是:接收前端請求傳遞的用戶名和密碼 ,然后再根據用戶名和密碼查詢用戶信息,如果用戶信息存在,則說明用戶輸入的用戶名和密碼正確。如果查詢到的用戶不存在,則說明用戶輸入的用戶名和密碼錯誤。

1.4 功能開發

LoginController

@RestController
public class LoginController {@Autowiredprivate EmpService empService;@PostMapping("/login")public AjaxResult login(@RequestBody Emp emp){Emp e = empService.login(emp);return  e != null ? AjaxResult.success():AjaxResult.error("用戶名或密碼錯誤");}
}

EmpService

public interface EmpService {/*** 用戶登錄* @param emp* @return*/public Emp login(Emp emp);//省略其他代碼...
}

EmpServiceImpl

@Slf4j
@Service
public class EmpServiceImpl implements EmpService {@Autowiredprivate EmpMapper empMapper;@Overridepublic Emp login(Emp emp) {//調用dao層功能:登錄Emp loginEmp = empMapper.getByUsernameAndPassword(emp);//返回查詢結果給Controllerreturn loginEmp;}   //省略其他代碼...
}

EmpMapper

@Mapper
public interface EmpMapper {@Select("select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time " +"from emp " +"where username=#{username} and password =#{password}")public Emp getByUsernameAndPassword(Emp emp);//省略其他代碼...
}

1.5 測試

功能開發完畢后,我們就可以啟動服務,打開postman進行測試了。

發起POST請求,訪問:http://localhost:8080/login

在這里插入圖片描述

postman測試通過了,那接下來,我們就可以結合著前端工程進行聯調測試。

先退出系統,進入到登錄頁面:
在登錄頁面輸入賬戶密碼:
在這里插入圖片描述
登錄成功之后進入到后臺管理系統頁面:
在這里插入圖片描述

2. 登錄校驗

2.1 問題分析

我們已經完成了基礎登錄功能的開發與測試,在我們登錄成功后就可以進入到后臺管理系統中進行數據的操作。

但是當我們在瀏覽器中新的頁面上輸入地址:http://localhost:9528/#/system/dept,發現沒有登錄仍然可以進入到后端管理系統頁面。

在這里插入圖片描述

而真正的登錄功能應該是:登陸后才能訪問后端系統頁面,不登陸則跳轉登陸頁面進行登陸。

為什么會出現這個問題?其實原因很簡單,就是因為針對于我們當前所開發的部門管理、員工管理以及文件上傳等相關接口來說,我們在服務器端并沒有做任何的判斷,沒有去判斷用戶是否登錄了。所以無論用戶是否登錄,都可以訪問部門管理以及員工管理的相關數據。所以我們目前所開發的登錄功能,它只是徒有其表。而我們要想解決這個問題,我們就需要完成一步非常重要的操作:登錄校驗。

在這里插入圖片描述

什么是登錄校驗?

  • 所謂登錄校驗,指的是我們在服務器端接收到瀏覽器發送過來的請求之后,首先我們要對請求進行校驗。先要校驗一下用戶登錄了沒有,如果用戶已經登錄了,就直接執行對應的業務操作就可以了;如果用戶沒有登錄,此時就不允許他執行相關的業務操作,直接給前端響應一個錯誤的結果,最終跳轉到登錄頁面,要求他登錄成功之后,再來訪問對應的數據。

了解完什么是登錄校驗之后,接下來我們分析一下登錄校驗大概的實現思路。

首先我們在宏觀上先有一個認知:

前面在講解HTTP協議的時候,我們提到HTTP協議是無狀態協議。什么又是無狀態的協議?

所謂無狀態,指的是每一次請求都是獨立的,下一次請求并不會攜帶上一次請求的數據。而瀏覽器與服務器之間進行交互,基于HTTP協議也就意味著現在我們通過瀏覽器來訪問了登陸這個接口,實現了登陸的操作,接下來我們在執行其他業務操作時,服務器也并不知道這個員工到底登陸了沒有。因為HTTP協議是無狀態的,兩次請求之間是獨立的,所以是無法判斷這個員工到底登陸了沒有。

在這里插入圖片描述

那應該怎么來實現登錄校驗的操作呢?具體的實現思路可以分為兩部分:

  1. **會話技術:**在員工登錄成功后,需要將用戶登錄成功的信息存起來,記錄用戶已經登錄成功的標記。參考視頻
    簡單來說就是,前端瀏覽器第一次向服務端發送了登錄的請求,服務端存一個已經登錄的標記。然后,前端在發送任何請求時,先會要求后端先返回登錄標記,如果有登錄標記那就放行正常訪問,如果登錄標記,就會提示請登錄并退回到登錄界面。
  2. 統一攔截技術:在瀏覽器發起請求時,需要在服務端進行統一攔截,攔截后進行登錄校驗。

想要判斷員工是否已經登錄,我們需要在員工登錄成功之后,存儲一個登錄成功的標記,接下來在每一個接口方法執行之前,先做一個條件判斷,判斷一下這個員工到底登錄了沒有。如果是登錄了,就可以執行正常的業務操作,如果沒有登錄,會直接給前端返回一個錯誤的信息,前端拿到這個錯誤信息之后會自動的跳轉到登錄頁面。

我們程序中所開發的查詢功能、刪除功能、添加功能、修改功能,都需要使用以上套路進行登錄校驗。此時就會出現:相同代碼邏輯,每個功能都需要編寫,就會造成代碼非常繁瑣。

為了簡化這塊操作,我們可以使用一種技術:統一攔截技術。

通過統一攔截的技術,我們可以來攔截瀏覽器發送過來的所有的請求,攔截到這個請求之后,就可以通過請求來獲取之前所存入的登錄標記,在獲取到登錄標記且標記為登錄成功,就說明員工已經登錄了。如果已經登錄,我們就直接放行(意思就是可以訪問正常的業務接口了)。

我們要完成以上操作,會涉及到web開發中的兩個技術:

  1. 會話技術
  2. 統一攔截技術

而統一攔截技術現實方案也有兩種:

  1. Servlet規范中的Filter過濾器
  2. Spring提供的interceptor攔截器

下面我們先學習會話技術,然后再學習統一攔截技術。

2.2 會話技術

介紹了登錄校驗的大概思路之后,我們先來學習下會話技術。

2.2.1 會話技術介紹

參考視頻

什么是會話?

  • 在我們日常生活當中,會話指的就是談話、交談。

  • 在web開發當中,會話指的就是瀏覽器與服務器之間的一次連接,我們就稱為一次會話。

    在用戶打開瀏覽器第一次訪問服務器的時候,這個會話就建立了,直到有任何一方斷開連接,此時會話就結束了。在一次會話當中,是可以包含多次請求和響應的。

    比如:打開了瀏覽器來訪問web服務器上的資源(瀏覽器不能關閉、服務器不能斷開)

    • 第1次:訪問的是登錄的接口,完成登錄操作
    • 第2次:訪問的是部門管理接口,查詢所有部門數據
    • 第3次:訪問的是員工管理接口,查詢員工數據

    只要瀏覽器和服務器都沒有關閉,以上3次請求都屬于一次會話當中完成的。

在這里插入圖片描述

需要注意的是:會話是和瀏覽器關聯的,當有三個瀏覽器客戶端和服務器建立了連接時,就會有三個會話。同一個瀏覽器在未關閉之前請求了多次服務器,這多次請求是屬于同一個會話。比如:1、2、3這三個請求都是屬于同一個會話。當我們關閉瀏覽器之后,這次會話就結束了。而如果我們是直接把web服務器關了,那么所有的會話就都結束了。

知道了會話的概念了,接下來我們再來了解下會話跟蹤。

會話跟蹤:一種維護瀏覽器狀態的方法,服務器需要識別多次請求是否來自于同一瀏覽器,以便在同一次會話的多次請求間共享數據。

服務器會接收很多的請求,但是服務器是需要識別出這些請求是不是同一個瀏覽器發出來的。比如:1和2這兩個請求是不是同一個瀏覽器發出來的,3和5這兩個請求不是同一個瀏覽器發出來的。如果是同一個瀏覽器發出來的,就說明是同一個會話。如果是不同的瀏覽器發出來的,就說明是不同的會話。而識別多次請求是否來自于同一瀏覽器的過程,我們就稱為會話跟蹤。

我們使用會話跟蹤技術就是要完成在同一個會話中,多個請求之間進行共享數據

為什么要共享數據呢?

由于HTTP是無狀態協議,在后面請求中怎么拿到前一次請求生成的數據呢?此時就需要在一次會話的多次請求之間進行數據共享

這里我們就可以總結出會話技術的關鍵作用:共享一次會話中所有請求都會用到的通用數據(比較典型的就是用戶的身份信息用于身份效驗)

會話跟蹤技術有三種:

  1. Cookie(客戶端會話跟蹤技術)
    • 數據存儲在客戶端瀏覽器當中
  2. Session(服務端會話跟蹤技術)
    • 數據存儲在儲在服務端
  3. 令牌技術(目前最主流的)

在這里插入圖片描述

2.2.2 會話跟蹤方案

上面我們介紹了什么是會話,什么是會話跟蹤,并且也提到了會話跟蹤 3 種常見的技術方案。接下來,我們就來對比一下這 3 種會話跟蹤的技術方案,來看一下具體的實現思路,以及它們之間的優缺點。

2.2.2.1 方案一 - Cookie

參考視頻

cookie 是客戶端會話跟蹤技術,它是存儲在客戶端瀏覽器的,我們使用 cookie 來跟蹤會話,我們就可以在瀏覽器第一次發起請求來請求服務器的時候,我們在服務器端來設置一個cookie。
·
比如第一次請求了登錄接口,登錄接口執行完成之后,我們就可以設置一個cookie,在 cookie 當中我們就可以來存儲用戶相關的一些數據信息。比如我可以在 cookie 當中來存儲當前登錄用戶的用戶名,用戶的ID。

服務器端在給客戶端在響應數據的時候,會自動的將 cookie 響應給瀏覽器,瀏覽器接收到響應回來的 cookie 之后,會自動的將 cookie 的值存儲在瀏覽器本地。接下來在后續的每一次請求當中,都會將瀏覽器本地所存儲的 cookie 自動地攜帶到服務端。

在這里插入圖片描述

接下來在服務端我們就可以獲取到 cookie 的值。我們可以去判斷一下這個 cookie 的值是否存在,如果不存在這個cookie,就說明客戶端之前是沒有訪問登錄接口的;如果存在 cookie 的值,就說明客戶端之前已經登錄完成了。這樣我們就可以基于 cookie 在同一次會話的不同請求之間來共享數據。

我剛才在介紹流程的時候,用了 3 個自動:

  • 服務器會 自動 的將 cookie 響應給瀏覽器。

  • 瀏覽器接收到響應回來的數據之后,會 自動 的將 cookie 存儲在瀏覽器本地。

  • 在后續的請求當中,瀏覽器會 自動 的將 cookie 攜帶到服務器端。

Cookie總結:

  • cookie是存在客戶端(注意不是服務端,f服務端只是在第一次登錄時生成cookie,不會保存)
  • 步驟:一次會話中
    • step1: 客戶端第一次向發送用戶登錄請求,服務端生成一個 cookie(比如往這個cookie里面封裝一些用戶信息)返回給前端;前端將這個cookie存在客戶端的(內存或者硬盤中)
    • step2: 今后客戶端在這次會話中發送任何請求,就會自動將保存下來的這次會話的cookie封裝在HTTP 協議的請求頭中,服務端就可以通過這個解析拿到cookie里面的信息進行邏輯處理

為什么這一切都是自動化進行的?

是因為 cookie 它是 HTP 協議當中所支持的技術,而各大瀏覽器廠商都支持了這一標準。在 HTTP 協議官方給我們提供了一個響應頭和請求頭:

  • 響應頭 Set-Cookie :設置Cookie數據的

  • 請求頭 Cookie:攜帶Cookie數據的

在這里插入圖片描述

代碼測試

@Slf4j
@RestController
public class SessionController {//設置Cookie@GetMapping("/c1")public AjaxResult cookie1(HttpServletResponse response){response.addCookie(new Cookie("login_username","itheima")); //設置Cookie/響應Cookiereturn AjaxResult.success();}//獲取Cookie@GetMapping("/c2")public AjaxResult cookie2(HttpServletRequest request){Cookie[] cookies = request.getCookies();for (Cookie cookie : cookies) {if(cookie.getName().equals("login_username")){System.out.println("login_username: "+cookie.getValue()); //輸出name為login_username的cookie}}return AjaxResult.success();}
}    

A. 訪問c1接口,設置Cookie,http://localhost:8080/c1
在這里插入圖片描述

我們可以看到,后端服務器生成的的cookie,通過響應頭Set-Cookie響應給瀏覽器,并且瀏覽器會將Cookie,存儲在瀏覽器端。
在這里插入圖片描述

B. 訪問c2接口 http://localhost:8080/c2,此時瀏覽器會自動的將Cookie攜帶到服務端,是通過請求頭Cookie,攜帶的。

在這里插入圖片描述

補充:在瀏覽器端可以看到客戶端將cookie存在了這個地方
在這里插入圖片描述

優缺點

  • 優點:HTTP協議中支持的技術(像Set-Cookie 響應頭的解析以及 Cookie 請求頭數據的攜帶,都是瀏覽器自動進行的,是無需我們手動操作的)
  • 缺點:
    • 移動端APP(Android、IOS)中無法使用Cookie
    • 不安全,用戶可以自己禁用Cookie
    • Cookie不能跨域

跨域介紹:

? 在這里插入圖片描述

  • 現在的項目,大部分都是前后端分離的,前后端最終也會分開部署,前端部署在服務器 192.168.150.200 上,端口 80,后端部署在 192.168.150.100上,端口 8080
  • 我們打開瀏覽器直接訪問前端工程,訪問url:http://192.168.150.200/login.html
  • 然后在該頁面發起請求到服務端,而服務端所在地址不再是localhost,而是服務器的IP地址192.168.150.100,假設訪問接口地址為:http://192.168.150.100:8080/login
  • 那此時就存在跨域操作了,因為我們是在 http://192.168.150.200/login.html 這個頁面上訪問了http://192.168.150.100:8080/login 接口
  • 此時如果服務器設置了一個Cookie,這個Cookie是不能使用的,因為Cookie無法跨域

區分跨域的維度:

  • 協議
  • IP/協議
  • 端口

只要上述的三個維度有任何一個維度不同,那就是跨域操作

舉例:

? http://192.168.150.200/login.html ----------> https://192.168.150.200/login [協議不同,跨域]

? http://192.168.150.200/login.html ----------> http://192.168.150.100/login [IP不同,跨域]

? http://192.168.150.200/login.html ----------> http://192.168.150.200:8080/login [端口不同,跨域]

? http://192.168.150.200/login.html ----------> http://192.168.150.200/login [不跨域]

2.2.2.2 方案二 - Session

參考視頻

其實Session和Cookie基本差不多,也是Cookie那個邏輯

前面介紹的時候,我們提到Session,它是服務器端會話跟蹤技術,所以它是存儲在服務器端的。而 Session 的底層其實就是基于我們剛才所介紹的 Cookie 來實現的。

  • 獲取Session

在這里插入圖片描述

如果我們現在要基于 Session 來進行會話跟蹤,瀏覽器在第一次請求服務器的時候,我們就可以直接在服務器當中來獲取到會話對象Session。如果是第一次請求Session ,會話對象是不存在的,這個時候服務器會自動的創建一個會話對象Session 。而每一個會話對象Session ,它都有一個ID(示意圖中Session后面括號中的1,就表示ID),我們稱之為 Session 的ID。

  • 響應Cookie (JSESSIONID)

    在這里插入圖片描述

    接下來,服務器端在給瀏覽器響應數據的時候,它會將 Session 的 ID 通過 Cookie 響應給瀏覽器。其實在響應頭當中增加了一個 Set-Cookie 響應頭。這個 Set-Cookie 響應頭對應的值是不是cookie? cookie 的名字是固定的 JSESSIONID 代表的服務器端會話對象 Session 的 ID。瀏覽器會自動識別這個響應頭,然后自動將Cookie存儲在瀏覽器本地。

  • 查找Session
    在這里插入圖片描述

    接下來,在后續的每一次請求當中,都會將 Cookie 的數據獲取出來,并且攜帶到服務端。接下來服務器拿到JSESSIONID這個 Cookie 的值,也就是 Session 的ID。拿到 ID 之后,就會從眾多的 Session 當中來找到當前請求對應的會話對象Session。

Session總結:

  • Session存儲在服務器,攜帶Session id的Cookie 存儲在客戶端

  • 步驟:一次會話中

    • step1: 客戶端第一次向發送用戶登錄請求,服務端生成一個會生成一個Session對象(可以往里面自定義封裝一些數據,例如用戶信息),注意每一個Session對象都有一個Session_id;服務器端會干兩件事情,一是將這個生成的Session對象存儲到服務器上,二是將這個Session對象的id封裝在cookie里面返回給前端,前端會自動在客戶端保存這個cookie
    • step2: 今后客戶端在這次會話中發送任何請求,就會自動將保存下來的這次會話的cookie封裝在HTTP 協議的請求頭中,服務端就可以通過這個解析拿到cookie里面的Session_id信息進行邏輯處理,通過Session_id找到服務器上保存的Session對象中的封裝的數據進行操作,例如身份驗證這些

    這樣我們是不是就可以通過 Session 會話對象在同一次會話的多次請求之間來共享數據了?好,這就是基于 Session 進行會話跟蹤的流程。

代碼測試

@Slf4j
@RestController
public class SessionController {@GetMapping("/s1")public AjaxResult session1(HttpSession session){log.info("HttpSession-s1: {}", session.hashCode());session.setAttribute("loginUser", "tom"); //往session中存儲數據return AjaxResult.success();}@GetMapping("/s2")public AjaxResult session2(HttpServletRequest request){HttpSession session = request.getSession();log.info("HttpSession-s2: {}", session.hashCode());Object loginUser = session.getAttribute("loginUser"); //從session中獲取數據log.info("loginUser: {}", loginUser);return AjaxResult.success(loginUser);}
}

A. 訪問 s1 接口,http://localhost:8080/s1
在這里插入圖片描述

請求完成之后,在響應頭中,就會看到有一個Set-Cookie的響應頭,里面響應回來了一個Cookie,就是JSESSIONID,這個就是服務端會話對象 Session 的ID。

B. 訪問 s2 接口,http://localhost:8080/s2
在這里插入圖片描述

接下來,在后續的每次請求時,都會將Cookie的值,攜帶到服務端,那服務端呢,接收到Cookie之后,會自動的根據JSESSIONID的值,找到對應的會話對象Session。

那經過這兩步測試,大家也會看到,在控制臺中輸出如下日志:
在這里插入圖片描述

兩次請求,獲取到的Session會話對象的hashcode是一樣的,就說明是同一個會話對象。而且,第一次請求時,往Session會話對象中存儲的值,第二次請求時,也獲取到了。 那這樣,我們就可以通過Session會話對象,在同一個會話的多次請求之間來進行數據共享了。

優缺點

  • 優點:Session是存儲在服務端的,安全
  • 缺點:
    • 服務器集群環境下無法直接使用Session
    • 移動端APP(Android、IOS)中無法使用Cookie
    • 用戶可以自己禁用Cookie
    • Cookie不能跨域

PS:Session 底層是基于Cookie實現的會話跟蹤,如果Cookie不可用,則該方案,也就失效了。

服務器集群環境為何無法使用Session?

? 在這里插入圖片描述

  • 首先第一點,我們現在所開發的項目,一般都不會只部署在一臺服務器上,因為一臺服務器會存在一個很大的問題,就是單點故障。所謂單點故障,指的就是一旦這臺服務器掛了,整個應用都沒法訪問了。

?
在這里插入圖片描述

  • 所以在現在的企業項目開發當中,最終部署的時候都是以集群的形式來進行部署,也就是同一個項目它會部署多份。比如這個項目我們現在就部署了 3 份。

  • 而用戶在訪問的時候,到底訪問這三臺其中的哪一臺?其實用戶在訪問的時候,他會訪問一臺前置的服務器,我們叫負載均衡服務器,我們在后面項目當中會詳細講解。目前大家先有一個印象負載均衡服務器,它的作用就是將前端發起的請求均勻的分發給后面的這三臺服務器。

    在這里插入圖片描述

  • 此時假如我們通過 session 來進行會話跟蹤,可能就會存在這樣一個問題。用戶打開瀏覽器要進行登錄操作,此時會發起登錄請求。登錄請求到達負載均衡服務器,將這個請求轉給了第一臺 Tomcat 服務器。

    Tomcat 服務器接收到請求之后,要獲取到會話對象session。獲取到會話對象 session 之后,要給瀏覽器響應數據,最終在給瀏覽器響應數據的時候,就會攜帶這么一個 cookie 的名字,就是 JSESSIONID ,下一次再請求的時候,是不是又會將 Cookie 攜帶到服務端?

    好。此時假如又執行了一次查詢操作,要查詢部門的數據。這次請求到達負載均衡服務器之后,負載均衡服務器將這次請求轉給了第二臺 Tomcat 服務器,此時他就要到第二臺 Tomcat 服務器當中。根據JSESSIONID 也就是對應的 session 的 ID 值,要找對應的 session 會話對象。

    我想請問在第二臺服務器當中有沒有這個ID的會話對象 Session, 是沒有的。此時是不是就出現問題了?我同一個瀏覽器發起了 2 次請求,結果獲取到的不是同一個會話對象,這就是Session這種會話跟蹤方案它的缺點,在服務器集群環境下無法直接使用Session。

大家會看到上面這兩種傳統的會話技術,在現在的企業開發當中是不是會存在很多的問題。 為了解決這些問題,在現在的企業開發當中,基本上都會采用第三種方案,通過令牌技術來進行會話跟蹤。接下來我們就來介紹一下令牌技術,來看一下令牌技術又是如何跟蹤會話的。

2.2.2.3 方案三 - 令牌技術

這里我們所提到的令牌,其實它就是一個用戶身份的標識,看似很高大上,很神秘,其實本質就是一個字符串。
在這里插入圖片描述

如果通過令牌技術來跟蹤會話,我們就可以在瀏覽器發起請求。在請求登錄接口的時候,如果登錄成功,我就可以生成一個令牌,令牌就是用戶的合法身份憑證。接下來我在響應數據的時候,我就可以直接將令牌響應給前端。

接下來我們在前端程序當中接收到令牌之后,就需要將這個令牌存儲起來。這個存儲可以存儲在 cookie 當中,也可以存儲在其他的存儲空間(比如:localStorage)當中

接下來,在后續的每一次請求當中,都需要將令牌攜帶到服務端。攜帶到服務端之后,接下來我們就需要來校驗令牌的有效性。如果令牌是有效的,就說明用戶已經執行了登錄操作,如果令牌是無效的,就說明用戶之前并未執行登錄操作。

此時,如果是在同一次會話的多次請求之間,我們想共享數據,我們就可以將共享的數據存儲在令牌當中就可以了。

優缺點

  • 優點:
    • 支持PC端、移動端
    • 解決集群環境下的認證問題
    • 減輕服務器的存儲壓力(無需在服務器端存儲)
  • 缺點:需要自己實現(包括令牌的生成、令牌的傳遞、令牌的校驗)

針對于這三種方案,現在企業開發當中使用的最多的就是第三種令牌技術進行會話跟蹤。而前面的這兩種傳統的方案,現在企業項目開發當中已經很少使用了。所以在我們的課程當中,我們也將會采用令牌技術來解決案例項目當中的會話跟蹤問題。

2.2.3 JWT令牌(Token)

前面我們介紹了基于令牌技術來實現會話追蹤。這里所提到的令牌就是用戶身份的標識,其本質就是一個字符串。令牌的形式有很多,我們使用的是功能強大的 JWT令牌。

2.2.3.1 介紹

參考視頻

JWT全稱:JSON Web Token (官網:https://jwt.io/)

  • 定義了一種簡潔的、自包含的格式,用于在通信雙方以json數據格式安全的傳輸信息。由于數字簽名的存在,這些信息是可靠的。

    簡潔:是指jwt就是一個簡單的字符串。可以在請求參數或者是請求頭當中直接傳遞。

    自包含:指的是jwt令牌,看似是一個隨機的字符串,但是我們是可以根據自身的需求在jwt令牌中存儲自定義的數據內容。如:可以直接在jwt令牌中存儲用戶的相關信息。

    簡單來講,jwt就是將原始的json數據格式進行了安全的封裝,這樣就可以直接基于jwt在通信雙方安全的進行信息傳輸了。

JWT的組成: (JWT令牌由三個部分組成,三個部分之間使用英文的點來分割)

  • 第一部分:Header(頭), 記錄令牌類型、簽名算法等。 例如:{“alg”:“HS256”,“type”:“JWT”}

  • 第二部分:Payload(有效載荷),攜帶一些自定義信息、默認信息等。 例如:{“id”:“1”,“username”:“Tom”}

  • 第三部分:Signature(簽名),防止Token被篡改、確保安全性。將header、payload,并加入指定秘鑰(secret),通過指定簽名算法計算而來。

    簽名的目的就是為了防jwt令牌被篡改,而正是因為jwt令牌最后一個部分數字簽名的存在,所以整個jwt 令牌是非常安全可靠的。一旦jwt令牌當中任何一個部分、任何一個字符被篡改了,整個令牌在校驗的時候都會失敗,所以它是非常安全可靠的。
    在這里插入圖片描述

JWT是如何將原始的JSON格式數據,轉變為字符串的呢?
其實在生成JWT令牌時,會對JSON格式的數據進行一次編碼:進行base64編碼

Base64:是一種基于64個可打印的字符來表示二進制數據的編碼方式。既然能編碼,那也就意味著也能解碼。所使用的64個字符分別是A到Z、a到z、 0- 9,一個加號,一個斜杠,加起來就是64個字符。任何數據經過base64編碼之后,最終就會通過這64個字符來表示。當然還有一個符號,那就是等號。等號它是一個補位的符號

需要注意的是Base64是編碼方式,而不是加密方式。

在這里插入圖片描述

JWT令牌最典型的應用場景就是登錄認證:

  1. 在瀏覽器發起請求來執行登錄操作,此時會訪問登錄的接口,如果登錄成功之后,我們需要生成一個jwt令牌,將生成的 jwt令牌返回給前端。
  2. 前端拿到jwt令牌之后,會將jwt令牌存儲起來。在后續的每一次請求中都會將jwt令牌攜帶到服務端。
  3. 服務端統一攔截請求之后,先來判斷一下這次請求有沒有把令牌帶過來,如果沒有帶過來,直接拒絕訪問,如果帶過來了,還要校驗一下令牌是否是有效。如果有效,就直接放行進行請求的處理。

在JWT登錄認證的場景中我們發現,整個流程當中涉及到兩步操作:

  1. 在登錄成功之后,要生成令牌。
  2. 每一次請求當中,要接收令牌并對令牌進行校驗。

稍后我們再來學習如何來生成jwt令牌,以及如何來校驗jwt令牌。

2.2.3.2 生成和校驗

參考視頻

簡單介紹了JWT令牌以及JWT令牌的組成之后,接下來我們就來學習基于Java代碼如何生成和校驗JWT令牌。

首先我們先來實現JWT令牌的生成。要想使用JWT令牌,需要先引入JWT的依賴:

<!-- JWT依賴-->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>

在引入完JWT來賴后,就可以調用工具包中提供的API來完成JWT令牌的生成和校驗

工具類:Jwts

生成JWT代碼實現:

@Test
public void genJwt(){Map<String,Object> claims = new HashMap<>();claims.put("id",1);claims.put("username","Tom");String jwt = Jwts.builder().setClaims(claims) //自定義內容(載荷),想共享的數據往這個里面放就可以了.signWith(SignatureAlgorithm.HS256, "itheima456352wfesfsF") //簽名算法,第二個參數就是我們指定的 秘鑰 secret,隨便寫(一般情況下這個秘鑰不泄露,jwt令牌就很安全).setExpiration(new Date(System.currentTimeMillis() + 3600*1000)) //設置該令牌有效期為   1小時.compact();System.out.println(jwt);
}

運行測試方法:

eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNzQwNTgwNDc1LCJ1c2VybmFtZSI6IlRvbSJ9._1fZ9OukvSs5YldnzIaMYkRBHeYKptcssxzjnePXblI

輸出的結果就是生成的JWT令牌,,通過英文的點分割對三個部分進行分割,我們可以將生成的令牌復制一下,然后打開JWT的官網,將生成的令牌直接放在Encoded位置,此時就會自動的將令牌解析出來。

在這里插入圖片描述

第一部分解析出來,看到JSON格式的原始數據,所使用的簽名算法為HS256。

第二個部分是我們自定義的數據,之前我們自定義的數據就是id,還有一個exp代表的是我們所設置的過期時間。

由于前兩個部分是base64編碼,所以是可以直接解碼出來。但最后一個部分并不是base64編碼,是經過簽名算法計算出來的,所以最后一個部分是不會解析的。

實現了JWT令牌的生成,下面我們接著使用Java代碼來校驗JWT令牌(解析生成的令牌):

@Test
public void parseJwt(){Claims claims = Jwts.parser().setSigningKey("itheima456352wfesfsF")//指定簽名密鑰(必須保證和生成令牌時使用相同的簽名密鑰)  .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNzQwNTgwNDc1LCJ1c2VybmFtZSI6IlRvbSJ9._1fZ9OukvSs5YldnzIaMYkRBHeYKptcssxzjnePXblI").getBody();System.out.println(claims);
}

運行測試方法:

{id=1, exp=1740580475, username=Tom}

令牌解析后,我們可以看到id和過期時間,如果在解析的過程當中沒有報錯,就說明解析成功了。

下面我們做一個測試:把令牌header中的數字9變為8,運行測試方法后發現報錯:

原header: eyJhbGciOiJIUzI1NiJ9

修改為: eyJhbGciOiJIUzI1NiJ8

在這里插入圖片描述

結論:篡改令牌中的任何一個字符,在對令牌進行解析時都會報錯,所以JWT令牌是非常安全可靠的。

我們繼續測試:修改生成令牌的時指定的過期時間,修改為1分鐘

@Test
public void genJwt(){Map<String,Object> claims = new HashMap<>();claims.put(“id”,1);claims

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/71069.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/71069.shtml
英文地址,請注明出處:http://en.pswp.cn/web/71069.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

mysql系列10—mysql鎖

背景 mysql中鎖機制核心是保證數據的一致性以及并發控制。鎖機制的實現與存儲引擎有關&#xff0c;本文介紹的是INNODB存儲引擎的鎖機制&#xff1b;其他存儲引擎如myISAM和memory等僅支持表鎖不支持行鎖&#xff0c;不是本文關注的重點。 本文介紹mysql數據庫提供的鎖機制&am…

Redis7——基礎篇(八)

前言&#xff1a;此篇文章系本人學習過程中記錄下來的筆記&#xff0c;里面難免會有不少欠缺的地方&#xff0c;誠心期待大家多多給予指教。 基礎篇&#xff1a; Redis&#xff08;一&#xff09;Redis&#xff08;二&#xff09;Redis&#xff08;三&#xff09;Redis&#x…

《國密算法開發實戰:從合規落地到性能優化》

前言 隨著信息技術的飛速發展,信息安全已成為全球關注的焦點。在數字化時代,數據的保密性、完整性和可用性直接關系到國家、企業和個人的利益。為了保障信息安全,密碼技術作為核心支撐,發揮著至關重要的作用。國密算法,即國家密碼算法,是我國自主設計和推廣的一系列密碼…

yolov12 部署瑞芯微 rk3588、RKNN 部署工程難度小、模型推理速度快

yolov12 部署又來了。 特別說明&#xff1a;如有侵權告知刪除&#xff0c;謝謝。 完整代碼&#xff1a;包括onnx轉rknn和測試代碼、rknn板端部署C代碼&#xff1a; 【onnx轉rknn和測試代碼】 【rknn板端部署C代碼】 1 模型訓練 yolov12訓練官方開源的已經非常詳細了&#…

windows本地化部署Dify+Deepseek

Windows本地化部署DifyDeepseek 一、下載Docker 前往 Docker 官網 下載 Docker Desktop&#xff0c;按序安裝。 1.1啟用WSL 打開本機的控制面板>程序>啟用或關閉 Windows 功能,勾選: Linux 的 Windows 子系統虛擬機平臺&#xff08;若無該選擇則勾選 Hyper-V &#…

使用Spring Boot與達夢數據庫(DM)進行多數據源配置及MyBatis Plus集成

使用Spring Boot與達夢數據庫(DM)進行多數據源配置及MyBatis Plus集成 在現代企業級應用開發中&#xff0c;處理多個數據源是一個常見的需求。本文將詳細介紹如何使用Spring Boot結合達夢數據庫&#xff08;DM&#xff09;&#xff0c;并通過MyBatis Plus來簡化數據庫操作&…

第二十四:5.2【搭建 pinia 環境】axios 異步調用數據

第一步安裝&#xff1a;npm install pinia 第二步&#xff1a;操作src/main.ts 改變里面的值的信息&#xff1a; <div class"count"><h2>當前求和為&#xff1a;{{ sum }}</h2><select v-model.number"n">  // .number 這里是…

使用 DeepSeek 生成流程圖、甘特圖與思維導圖:結合 Typora 和 XMind 的高效工作流

在現代工作與學習中&#xff0c;可視化工具如流程圖、甘特圖和思維導圖能夠極大地提升信息整理與表達的效率。本文將詳細介紹如何使用 DeepSeek 生成 Mermaid 文本&#xff0c;結合 Typora 快速生成流程圖和甘特圖&#xff0c;并通過 Markdown 格式生成思維導圖&#xff0c;最終…

DeepSeek 開源周:第五天 - Fire-Flyer 文件系統(3FS)

&#xff08;下面文字主要由 Grok 3 協助生成&#xff09; 概述 Deepseek 今天開源的 Fire-Flyer 文件系統&#xff08;3FS&#xff09;是一個高性能分布式文件系統&#xff0c;專門為 AI 訓練和推理設計。研究表明&#xff0c;它解決了 AI 工作負載中處理海量數據的高效存儲需…

【筆記】論文閱讀方法(AI大模型)

1 為什么讀論文 構建知識體系&#xff1a;通過Related Works快速了解該方向研究現狀&#xff0c;追蹤經典論文 緊跟前沿技術&#xff1a;了解領域內新技術及效果&#xff0c;快速借鑒到自身項目 培養科研邏輯&#xff1a;熟悉論文體系&#xff0c;了解如何創造新事物&#x…

【數據集】ACM數據集

ACM&#xff08;Association for Computing Machinery&#xff09;數據集是計算機科學領域常用于研究學術論文、作者關系、引文網絡、推薦系統、圖神經網絡&#xff08;GNN&#xff09;等任務的數據集之一。該數據集通常包含學術論文、作者、研究領域以及它們之間的關系&#x…

SQL server配置ODBC數據源(本地和服務器)

本地配置 1. 控制面板中找到系統ODBC數據源&#xff08;打開控制面板直接搜&#xff09; 2. 選擇“系統DSN”&#xff0c;點擊“添加” 3. 選擇“SQL server” 4. 名稱和描述自己填&#xff0c;服務器選擇本機設備名稱 5. 選擇ID和密碼驗證&#xff0c;并填寫本地SQL server登…

使用 Postman 訪問 Keycloak 端點

1. 引言 在本教程中&#xff0c;我們將首先快速回顧 OAuth 2.0、OpenID 和 Keycloak。然后&#xff0c;我們將了解 Keycloak REST API 以及如何在 Postman 中調用它們。 2. OAuth 2.0 OAuth 2.0 是一個授權框架&#xff0c;它允許經過身份驗證的用戶通過令牌向第三方授予訪問…

文生圖開源模型發展史(2014-2025年)

文生圖開源模型的發展歷程是一段充滿技術革新、社區生態繁榮與商業化競爭的多維度演進史。 一、技術萌芽期&#xff08;2014-2020年&#xff09; 核心突破 2014年&#xff1a;GAN&#xff08;生成對抗網絡&#xff09;誕生&#xff0c;首次實現數據驅動式圖像生成&#xff0…

微服務學習(2):實現SpringAMQP對RabbitMQ的消息收發

目錄 SpringAMQP是什么 為什么采用SpringAMQP SpringAMQP應用 準備springBoot工程 實現消息發送 SpringAMQP是什么 Spring AMQP是Spring框架下用于簡化AMQP&#xff08;高級消息隊列協議&#xff09;應用開發的一套工具集&#xff0c;主要針對RabbitMQ等消息中間件的集成…

AI人工智能機器學習之神經網絡

1、概要 本篇學習AI人工智能機器學習之神經網絡&#xff0c;以MLPClassifier和MLPRegressor為例&#xff0c;從代碼層面講述最常用的神經網絡模型MLP。 2、神經網絡 - 簡介 在 Scikit-learn 中&#xff0c;神經網絡是通過 sklearn.neural_network 模塊提供的。最常用的神經網…

WPF高級 | WPF 與數據庫交互:連接、查詢與數據更新

WPF高級 | WPF 與數據庫交互&#xff1a;連接、查詢與數據更新 前言一、數據庫交互基礎概念1.1 數據庫簡介1.2 數據訪問技術 二、WPF 與數據庫連接2.1 連接字符串2.2 建立連接 三、WPF 中的數據查詢3.1 使用ADO.NET進行數據查詢3.2 使用 Entity Framework 進行數據查詢3.3 使用…

【ESP32S3接入訊飛在線語音識別】

【ESP32S3接入訊飛在線語音識別】 1. 前言1.1 步驟概括1.2 硬件介紹1.3 接線2. 操作流程2.1 創建語音識別應用2.2 記錄API秘鑰3. JSON語音接入api3.1 JSON格式3.2 交互流程3.2 ESP32S3 Sense接入代碼1. 核心功能2. 主要模塊3. 工作流程4. 典型應用場景5. 關鍵技術點6. 待完善功…

學生管理前端

文章目錄 首頁student.html查詢功能 首頁 SpringBoot前端html頁面放在static文件夾下&#xff1a;/src/main/resources/static 默認首頁為index.html&#xff0c;我們可以用兩個超鏈接或者兩個button跳轉到對應的頁面。這里只是單純的跳轉頁面&#xff0c;不需要提交表單等其…

(動態規劃 最長遞增的子序列)leetcode 300

這道題我第一眼反應就是暴力&#xff0c;但是暴力的話就是n*n-1*n-2*...n-(n-1) 也就是O(n^n)dfs做絕對超時 貪心也不行&#xff0c;這里是子序列&#xff0c;要考慮在ni的范圍內考慮多種路線取最優&#xff0c;所以用動態規劃 如何用動態規劃呢&#xff1f; 答&#xff1a;…