SpringBoot-2-X-使用-JWT(JSON-Web-Token)

1、跨域認證遇到的問題

因爲多終端的出現,不少的站點經過 web api restful 的形式對外提供服務,採用了先後端分離模式進行開發,於是在身份驗證的方式上可能與傳統的基於 cookieSession Id 的作法有所不一樣,除了面臨跨域提交 cookie 的問題外,更重要的是,有些終端可能根本不支持 cookiehtml

JWT(JSON Web Token) 是一種身份驗證及受權方案,簡單的說就是調用端調用 api 時,附帶上一個由 api 端頒發的 token,以此來驗證調用者的受權信息。web

通常流程是下面這樣:算法

  1. 用戶向服務器發送用戶名和密碼。
  2. 服務器驗證經過後,在當前對話(session)裏面保存相關數據,好比用戶角色、登陸時間等等。
  3. 服務器向用戶返回一個 session_id,寫入用戶的 Cookie。
  4. 用戶隨後的每一次請求,都會經過 Cookie,將 session_id 傳回服務器。
  5. 服務器收到 session_id,找到前期保存的數據,由此得知用戶的身份。

這種模式的問題在於擴展性很差。單機沒有問題,若是是服務器集羣、跨域的服務導向架構或者用戶禁用了 cookie ,就不行了。json

2、解決方案後端

1. 單機和分佈式應用下登陸校驗,session 共享api

  • 單機和多節點 tomcat 應用登陸檢驗跨域

    ①、單機 tomcat 應用登陸, sesssion 保存在瀏覽器和應用服務器會話之間,用戶登陸成功後,服務端會保證一個 session,也會給客戶端一個 sessionId,客戶端會把 sessionId 保存在 cookie 中,用戶每次請求都會攜帶這個 sessionId

    ②、多節點 tomcat 應用登陸,開啓 session 數據共享後,每臺服務器都可以讀取 session。缺點是每一個 session 都是佔用內存和資源的,每一個服務器節點都須要同步用戶的數據,即一個數據須要存儲多份到每一個服務器,當用戶量到達百萬、千萬級別的時,佔用資源就嚴重,用戶體驗特別很差!!瀏覽器

  • 分佈式應用中 session 共享tomcat

    ①、真實的應用不可能單節點部署,因此就有個多節點登陸 session 共享的問題須要解決。 tomcat 支持 session 共享,可是有廣播風暴;用戶量大的時候,佔用資源就嚴重,不推薦

    ②、Reids 集羣,存儲登錄的 token,向外提供服務接口,Redis 可設置過時時間(服務端使用 UUID生成隨機 64 位或者 128token ,放入 Redis 中,而後返回給客戶端並存儲)。服務器

    ③、用戶第一次登陸成功時,須要先自行生成 token,而後將 token 返回到瀏覽器並存儲在 cookie 中, 並在 Redis 服務器上以 tokenkey,用戶信息做爲 value 保存。後續用戶再操做,能夠經過 HttpServletRequest 對象直接讀取 cookie 中的 token,並在 Redis 中取得相對應的用戶數據進行比較(用戶每次訪問都攜帶此 token,服務端去 Redis 中校驗是否有此用戶便可)。

    ④、 缺點:必須部署 Redis,每次必須訪問 RedisIO 開銷特別大。

2. 最終解決方案:使用 JWT 實現 Token 認證

  • JWT 的原理

    服務器認證之後,生成一個 JSON 對象發回給用戶,之後用戶與服務端通訊的時候,都要發回這個 JSON 對象。服務器徹底只靠這個對象認定用戶身份。爲了防止用戶篡改數據,服務器在生成這個對象的時候,會加上簽名。也就是說服務器就不保存任何 session 數據了,即服務器變成無狀態了,從而比較容易實現擴展。

    簡單來講,就是經過必定規範來生成 token,而後能夠經過解密算法逆向解密 token,這樣就能夠獲取用戶信息</pre>

  • 優勢和缺點

    優勢:生產的 token 能夠包含基本信息,好比 id、用戶暱稱、頭像等信息,避免再次查庫;存儲在客戶端,不佔用服務端的內存資源

    缺點:token 是通過 base64 編碼,因此能夠解碼,所以 token 加密前的對象不該該包含敏感信息(如用戶權限,密碼等)

  • JWT 格式組成:頭部+負載+簽名 ( header + payload + signature )

    頭部:主要是描述簽名算法。

    負載:主要描述是加密對象的信息,如用戶的 id 等,也能夠加些規範裏面的東西,如 iss 簽發者,exp 過時時間,sub 面向的用戶。

    簽名:主要是把前面兩部分進行加密,防止別人拿到 token 進行base 解密後篡改 token。

3. 案例圖設計

2.png

3、代碼演示案例

  • pom.xml 文件引入依賴和實體類
<!-- 依賴能夠減小實體類 getter/setter等方法書寫 -->
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <optional>true</optional>
</dependency>
<!-- JWT相關 -->
<dependency>
   <groupId>io.jsonwebtoken</groupId>
   <artifactId>jjwt</artifactId>
   <version>0.7.0</version>
</dependency>

=========================================================

@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

    private Integer id;
    private String openid;
    private String name;
    private String headImg;
    private String phone;
    private String sign;
    private Integer sex;
    private String city;
    private Date createTime;

}
  • 生成 JWT 工具類
public class JwtUtil {

    // 主題
    public static final String SUBJECT = "RookieLi";

    // 祕鑰
    public static final String SECRETKEY = "Rookie666";

    // 過時時間
    public static final long EXPIRE = 1000 * 60 * 60 * 24 * 7;  //過時時間,毫秒,一週

    // 生成 JWT
    public static String geneJsonWebToken(User user) {

        if (user == null ||
                user.getId() == null ||
                user.getName() == null ||
                user.getHeadImg() == null) {

            return null;
        }
        String token = Jwts.builder()
                .setSubject(SUBJECT)
                .claim("id", user.getId())
                .claim("name", user.getName())
                .claim("img", user.getHeadImg())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .signWith(SignatureAlgorithm.HS256, SECRETKEY).compact();

        return token;
    }


    // 校驗 JWT
    public static Claims checkJWT(String token) {

        try {
            final Claims claims = Jwts.parser().setSigningKey(SECRETKEY).
                    parseClaimsJws(token).getBody();
            return claims;

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
  • 測試 JWT 工具類
public class JwtUtilTest {

    @Test
    public void testGeneJwt(){
    
        User user = new User();
        user.setId(999);
        user.setHeadImg("I'm busy");
        user.setName("Rookie");
        String token = JwtUtil.geneJsonWebToken(user);
        System.out.println(token);

    }


    @Test
    public void testCheck(){

        // 下面此 token 字符串是上面的結果生成的,每次不同,不是寫死的
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJSb29raWVMaSIsImlkIjo5OTksIm5hbWUiOiJSb29raWUiLCJpbWciOiJJJ20gYnVzeSIsImlhdCI6MTU2NzMxNjk4NywiZXhwIjoxNTY3OTIxNzg3fQ.FJh41VwVh2gh5-_cOG0SOgoO3dR_ZcK9VWNNskWqKl0";
        Claims claims = JwtUtil.checkJWT(token);
        if(claims != null){
            String name = (String)claims.get("name");
            String img = (String)claims.get("img");
            int id =(Integer) claims.get("id");
            System.out.println(name);
            System.out.println(img);
            System.out.println(id);
        }else{
            System.out.println("非法token");
        }
    }
}
參考博客:
http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
https://www.cnblogs.com/jpfss/p/10929458.html
相關文章
相關標籤/搜索