SpringBoot實現JWT保護先後端分離RESTful API

原文連接:https://www.jianshu.com/p/e62af4a217ebcss

一般狀況下, 將api直接暴露出來是很是危險的. 每個api呼叫, 用戶都應該附上額外的信息, 以供咱們認證和受權. 而JWT是一種既能知足這樣需求, 而又簡單安全便捷的方法. 前端login獲取JWT以後, 只需在每一次HTTP呼叫的時候添加上JWT做爲HTTP Header便可.

本文將用不到100行Java代碼, 教你如何在Spring Boot裏面用JWT保護RESTful api.前端

源代碼在 https://github.com/ZhongjunTian/spring-boot-jwt-demo/basic
打開在線demo網站jontian.com:8080 或者代碼運行以後打開localhost:8080,
未登陸以前點擊 Call Example Service 返回 401 Unaothorized 錯誤.
java

 
登陸前

登陸以後便可獲得正確結果
 
登錄後

 


1. 什麼是JWT

瞭解JWT的同窗能夠跳過這一部分git

廢話少說, 咱們先看看什麼是JWT. JSON Web Token其實就是一個包含認證數據的JSON, 大概長這樣子
分三個部分,
第一部分{"alg":"HS512"}是簽名算法
第二部分 {"exp":1495176357,"username":"admin"}是一些數據(你想放什麼均可以), 這裏有過時日期和用戶名
第三部分')4'7�6-DM�(�H6fJ::$c���a4�~tI2%Xd-�$nL(l很是重要,是簽名Signiture, 服務器會驗證這個以防僞造. 由於JWT實際上是明文傳送, 任何人都能篡改裏面的內容. 服務端經過驗證簽名, 從而肯定這個JWT是本身生成的.github

原理也不是很複雜, 我用一行代碼就能表示出來
首先咱們將JWT第一第二部分的內容, 加上你的祕鑰(key或者叫secret), 而後用某個算法(好比hash算法)求一下, 求得的內容就是你的簽名. 驗證的時候只須要驗證你用JWT算出來的值是否等於JWT裏面的簽名.
由於別人沒有你的key, 因此也就無法僞造簽名.算法

簡單粗暴一行代碼解釋什麼是簽名:
int signiture = ("{alg:HS512}{exp:1495176357,username:admin}" + key).hashCode(); 
最後附上簽名,獲得完整的JWT:
{"alg":"HS512"}{"exp":1495176357,"username":"admin"} ')4'7�6-DM�(�H6fJ::$c���a4�~tI2%Xd-�$nL(l 

爲了方便複製和使用, 一般咱們都是把JWT用base64編碼以後放在http的header裏面, 而且每一次呼叫api都附上這個JWT, 而且服務器每次也驗證JWT是否過時spring

一般咱們用到的JWT:
Base64編碼後: eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE0OTUxNzYzNTcsInVzZXJuYW1lIjoiYWRtaW4ifQ.mQtCfLKfI0J7c3HTYt7kRN4AcoixiUSDaZv2ZKOjq2JMZjBhf1DmE0Fn6PdEkyJZhYZJTMLaIPwyR-uu6BMKGw 

2. 三個class實現JWT

整個demo一共有三個class
Application.java JwtAuthenticationFilter.java 和 JwtUtil.javaapi

2.1首先咱們看一看Application.java

第一步建立一個hello world api安全

 @GetMapping("/protected")
    public @ResponseBody Object hellWorld() {
        return "Hello World! This is a protected api";
    }

 

第二步建立一個 login的api, 咱們會驗證用戶的密碼, 若是正確, 那麼咱們會返回生成jwt. 這時前端拿到的這個jwt就相似於拿到了一個臨時的密碼, 以後全部的HTTP RESTful api請求都附上這個"臨時密碼"便可.(專業術語叫令牌/token)ruby

@PostMapping("/login")
    public Object login(HttpServletResponse response, @RequestBody final Account account) throws IOException {
        if(isValidPassword(account)) {
            String jwt = JwtUtil.generateToken(account.username);
            return new HashMap<String,String>(){{
                put("token", jwt);
            }};
        }else {
            return new ResponseEntity(HttpStatus.UNAUTHORIZED);
        }
    }

 

登陸效果以下圖

 
 

最後咱們再註冊一個檢驗jwt的過濾器Filter, 經過這個過濾器Filter實現對每一個Rest api請求都驗證jwt的功能. 這個JwtAuthenticationFilter繼承了OncePerRequestFilter, 任何請求都會先通過咱們的filter, 而後咱們會選擇讓那些有合法jwt的請求經過咱們的filter.

 @Bean
    public FilterRegistrationBean jwtFilter() {
        final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        JwtAuthenticationFilter filter = new JwtAuthenticationFilter();
        registrationBean.setFilter(filter);
        return registrationBean;
    }

 

2.2而後咱們看一下JwtAuthenticationFilter.java

這裏咱們繼承了OncePerRequestFilter, 保證了用戶請求任何資源都會運行這個doFilterInternal. 這裏咱們會從HTTP Header裏面截取JWT, 而且驗證JWT的簽名和過時時間, 若是有問題, 咱們會返回HTTP 401錯誤.
PS: 這裏有個狀況就是用戶登陸/login前是沒有jwt的, 因此咱們要讓登陸的請求

public class JwtAuthenticationFilter extends OncePerRequestFilter {
    //......一些不重要的代碼......
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            if(isProtectedUrl(request)) {
                String token = request.getHeader("Authorization");
                //檢查jwt令牌, 若是令牌不合法或者過時, 裏面會直接拋出異常, 下面的catch部分會直接返回
                JwtUtil.validateToken(token);
            }
        } catch (Exception e) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
            return;
        }
        //若是jwt令牌經過了檢測, 那麼就把request傳遞給後面的RESTful api
        filterChain.doFilter(request, response);
    }
//咱們只對地址 /api 開頭的api檢查jwt. 否則的話登陸/login也須要jwt
private boolean isProtectedUrl(HttpServletRequest request) {
return pathMatcher.match("/**", request.getServletPath());
}

private boolean isExceededUrl(HttpServletRequest request) {
return pathMatcher.match("/login", request.getServletPath()) || pathMatcher.match("register", request.getServletPath());
}


}

 

2.3最後咱們看一下JwtUtil.java

這裏就兩個函數, 第一個函數生成一個有效期1000小時的jwt
public static String generateToken(String username)
第二個函數是驗證JWT是否有效, 若是JWT有效則返回用戶名, 不然拋出Exception
public static void validateToken(String token)
這裏的代碼都很是簡潔就十幾行, 使用的都是現成的包, 建議直接看源代碼.

3.測試

這就是呼叫api的效果

 
正確jwt

 
相關文章
相關標籤/搜索