JWT 基本使用


JWT 基本使用

在上一節中 session 共享功能使用 redis 進行存儲,用戶量激增時會導致 redis 崩潰,而 JWT 不依賴服務器,能夠避免這個問題。

1、傳統 session

1.1.認證方式

http協議本身是一種無狀態的協議,如果用戶向服務器提供了用戶名和密碼來進行用戶認證,下次請求時,用戶還要再一次進行用戶認證才行。因為根據http協議,服務器並不能知道是哪個用戶發出的請求,所以為了讓我們的應用能識別是哪個用戶發出的請求,我們只能在服務器存儲─份用戶登錄的信息,這份登錄信息會在響應時傳遞給瀏覽器,告訴其保存為cookie,以便下次請求時發送給我們的應用,這樣應用就能識別請求來自哪個用戶。

session

1.2.暴露的問題

  • 用戶經過應用認證后,應用都要在服務端做一次記錄,以方便用戶下次請求的鑒別,通常而言session都是保存在內存中,而隨着認證用戶的增多,服務端的開銷會明顯增大;
  • 用戶認證后,服務端做認證記錄,如果認證的記錄被保存在內存中的話,用戶下次請求還必須要請求在這台服務器上,這樣才能拿到授權的資源。在分布式的應用上,限制了負載均衡器的能力。以此限制了應用的擴展能力;
  • session是基於cookie來進行用戶識別,cookie如果被截獲,用戶很容易受到CSRF(跨站偽造請求攻擊)攻擊;
  • 在前后端分離系統中應用解耦后增加了部署的復雜性。通常用戶一次請求就要轉發多次。如果用session每次攜帶sessionid到服務
    器,服務器還要查詢用戶信息。同時如果用戶很多。這些信息存儲在服務器內存中,給服務器增加負擔。還有就是sessionid就是一個特征值,表達的信息不夠豐富。不容易擴展。而且如果你后端應用是多節點部署。那么就需要實現session共享機制。不方便集群應用。

2、概念

Json web token (JWT) ,是一種基於 JSON的開放標准(RFC 7519),定義了一種簡潔的、自包含的方法用於通信雙方之間以 JSON 對象的形式安全的傳遞信息。 因為數字簽名的存在,這些信息是可信的,JWT 可以使用 HMAC 算法或者 RSA 的公鑰私鑰進行簽名。

3、作用

  1. 授權:一旦用戶登錄,每個后續請求將包括JWT,從而允許用戶訪問該令牌允許的路由,服務和資源。它的開銷很小並且可以在不同的域中使用。如:單點登錄。
  2. 信息交換:在各方之間安全地傳輸信息。JWT可進行簽名(如使用公鑰/私鑰對),因此可確保發件人。由於簽名是使用標頭和有效負載計算的,因此還可驗證內容是否被篡改。

4、認證流程

  1. 前端通過Web表單將自己的用戶名和密碼發送到后端的接口。該過程一般是HTTP的POST請求。建議的方式是通過SSL加密的傳輸(https協議),從而避免敏感信息被嗅探。
  2. 后端核對用戶名和密碼成功后,將用戶的id等其他信息作為JWT Payload(負載),將其與頭部分別進行Base64編碼拼接后簽名,形成一個JWT(Token)。
  3. 后端將JWT字符串作為登錄成功的返回結果返回給前端。前端可以將返回的結果保存在localStorage(瀏覽器本地緩存)或sessionStorage(session緩存)上,退出登錄時前端刪除保存的JWT即可。
  4. 前端在每次請求時將JWT放入HTTP的Header中的Authorization位。(解決XSS和XSRF問題)HEADER
    后端檢查是否存在,如存在驗證JWT的有效性。例如,檢查簽名是否正確﹔檢查Token是否過期;檢查Token的接收方是否是自己(可選)
  5. 驗證通過后后端使用JWT中包含的用戶信息進行其他邏輯操作,返回相應結果。

20201029154848467

5、優點及結構

5.1、優點

  • 簡潔(Compact):可以通過URL,POST參數或者在HTTP header發送,數據量小,傳輸速度也很快;
  • 自包含(Self-contained):負載中包含了所有用戶所需要的信息,避免了多次查詢數據庫;
  • Token是以JSON加密的形式保存在客戶端,所以JWT是跨語言的,原則上任何web形式都支持。
  • 不需要在服務端保存會話信息,特別適用於分布式微服務。

5.2、結構

就是令牌token,是一個String字符串,由3部分組成,中間用點隔開,令牌組成:

  1. 標頭(Header)
  2. 有效載荷(Payload)
  3. 簽名(Signature)

token格式:head.payload.singurater 如:xxxxx.yyyy.zzzz

  • Header:有令牌的類型和所使用的簽名算法,如HMAC、SHA256、RSA;使用Base64編碼組成;(Base64是一種編碼,不是一種加密過程,可以被翻譯成原來的樣子)
{
	"alg" : "HS256",
	"type" : "JWT"
}
  • Payload :有效負載,包含聲明;聲明是有關實體(通常是用戶)和其他數據的聲明,不放用戶敏感的信息,如密碼。同樣使用Base64編碼
{
	"sub" : "123",
	"name" : "John Do",
	"admin" : true
}
  • Signature :前面兩部分都使用Base64進行編碼,前端可以解開知道里面的信息。Signature需要使用編碼后的header和payload
    加上我們提供的一個密鑰,使用header中指定的簽名算法(HS256)進行簽名。簽名的作用是保證JWT沒有被篡改過
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret);

20201029154920267

6、使用

6.1、引入依賴

<!--引入JWT-->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.10.0</version>
</dependency>

6.2、創建配置類

public class JWTUtils {
    //自定義密鑰
    private static final String SECRET_KEY = "@Tes*RI#s&";

    /**
     * 生成token
     *
     * @param map
     * @return token
     */
    public static String createToken(Map<String, String> map) {
        JWTCreator.Builder builder = JWT.create();
        //保存用戶信息
        map.forEach((k,v)-> {
            builder.withClaim(k,v);
        });
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.MINUTE,200);
        //設置過期時間
        builder.withExpiresAt(instance.getTime());
        //設置簽名
        return builder.sign(Algorithm.HMAC256(SECRET_KEY));
    }

    /**
     * 驗證token
     * @param token
     * @return 驗證信息
     */
    public static Map verify(String token){
        Map<String, Object> map = new HashMap<>();
        try {
            JWT.require(Algorithm.HMAC256(SECRET_KEY)).build().verify(token);
            map.put("status", true);
            return map;
        }catch (TokenExpiredException e){
            e.printStackTrace();
            map.put("msg","token過期");
        }catch (SignatureVerificationException e) {
            e.printStackTrace();
            map.put("msg", "無效簽名");
        }catch (AlgorithmMismatchException e) {
            e.printStackTrace();
            map.put("msg","算法不一致");
        }catch (Exception e) {
            e.printStackTrace();
            map.put("msg","token無效");
        }
        map.put("status", false);
        return map;
    }
}

常見異常信息如下:

SignatureVerificationException //簽名不一致異常
TokenExpiredException //令牌過期異常
AlgorithmMismatchException //算法不匹配異常
InvalidClaimException //失效的payload異常(傳給客戶端后,token被改動,驗證不一致)

6.3、過濾器獲取token

public class AccessFilter implements Filter {
    Logger logger = LoggerFactory.getLogger(AccessFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        //登錄接口不用驗證token
        if (request.getRequestURI().contains("/login")){
            filterChain.doFilter(request, servletResponse);
            return;
        }
        //驗證token
        String token = request.getHeader("token");
        Map<String, Object> map = JWTUtils.verify(token);
        //驗證成功
        if ((Boolean)map.get("status")) {
            DecodedJWT decodedJWT = JWTUtils.getToken(token);
            logger.info("=========" + decodedJWT.getClaim("userName").asString());
        //驗證失敗    
        } else {
            String json = new ObjectMapper().writeValueAsString(map);
            response.setContentType("application/json; charset=UTF-8");
            response.getWriter().println(json);
            return;
        }
        filterChain.doFilter(request, servletResponse);
    }

    @Override
    public void destroy() {
    }
}

6.4、模擬登錄接口

@GetMapping(value = "/login")
@ResponseBody
public Result login(HttpSession session) {
    Map<String, String> map = new HashMap<>();
    map.put("userId", "1");
    map.put("userName", "123456");
    //生成token
    String token = JWTUtils.createToken(map);
    return ResultUtil.success(token);
}

返回 token 給客戶端,客戶端每次請求攜帶token,過濾器對 token 有效性進行校驗,驗證成功返回數據,驗證失敗返回登錄頁面。

參考資料:JWT詳細教程及使用


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM