未經安全保護的API很是的危險,其和裸奔無異。即便API文檔沒有被人爲泄露,經過簡單的抓包也能夠很是容易的獲取到API的URL以及對應的請求參數。下面舉幾個未經保護的API可能將會形成的安全事故:java
經過抓包,找到發送短信驗證碼的API。而後利用該API惡意的發送短信驗證碼。而發送短信驗證碼是須要收費的,這樣的惡意攻擊,將會致使無端的損失發送短信的費用。還會讓不少不明真相的吃瓜羣衆收到奇怪的短信驗證碼,進而對產品產生很差的印象。git
經過抓包,找到獲取用戶信息的API。該API的暴露,將會讓該平臺上全部用戶的信息被毫無保留的暴露在互聯網上。若是用戶信息中還涉及到一些重要的我的信息。好比身份證號,手機號等。將會讓用戶受到人生和財產的損失。github
經過抓包,找到和財產相關的API。好比說修改訂單狀態的API。一旦該API暴露,黑客能夠惡意的修改用戶的訂單狀態。好比說將訂單的狀態修改成取消。這可能將會直接對用戶形成財產損失。web
上面這些例子僅僅只是筆者臨時想到的一些利用裸露API進行惡意攻擊的方式,實際中還存在着更多因爲未對API進行加密,而形成損失的狀況。所以,未經安全保護的API很是的危險,對API進行安全保護異常重要。算法
JWT是json web token的縮寫。關於其如何確保數據傳輸的安全性的文章你能夠在搜索引擎上找到不少,在這裏我將僅僅簡單介紹我對此的理解。spring
JWT能夠理解爲一串經過特定算法生成的字符串,在API的請求中,將這段字符串放入請求參數中。API Server經過判斷這段字符串是合法的仍是僞造的,來肯定此次API請求是否有效。經過該安全措施,將確保即便API被暴露,沒有生成JWT字符串的算法,也沒有辦法成功調用API。json
JWT字符串分爲兩個部分(官方的說法是分爲3個部分),分別是‘未加密部分’和‘加密部分’。而‘加密部分’的內容其實是‘未加密部分’加密獲得的。API Server檢查JWT字符串是否有效的第一步是將‘加密部分’解密而後與‘未加密部分’進行比較,查看是否內容一致。若是內容不一致,則說明該JWT字符串是僞造的。安全
JWT字符串中包括一個‘過時時間’的字段,當API Server獲取到JWT字符串後,能夠經過檢查該字段與當前時間相比,是否已經處於過時的狀態。若是‘過時時間’字段早於當前時間,則說明此次API請求是無效的。gradle
你也能夠在JWT字段種加入自定義的字段。而後在API Server獲取到JWT字段後,經過這些自定義的字段判斷是否是符合具體的業務邏輯,進而判斷此次請求是否是有效。ui
jjwt
是java
對JWT的封裝,下面的方法將會演示。在java中如何利用jjwt
實現API的保護
compile 'io.jsonwebtoken:jjwt:0.7.0'
public String buildJwt(Date exp) { String jwt = Jwts.builder() .signWith(SignatureAlgorithm.HS256,SECRET_KEY)//SECRET_KEY是加密算法對應的密鑰,這裏使用額是HS256加密算法 .setExpiration(exp)//expTime是過時時間 .claim("key","vaule")//該方法是在JWT中加入值爲vaule的key字段 .compact(); return jwt; }
public boolean isJwtValid(String jwt) { try { //解析JWT字符串中的數據,並進行最基礎的驗證 Claims claims = Jwts.parser() .setSigningKey(SECRET_KEY)//SECRET_KEY是加密算法對應的密鑰,jjwt能夠自動判斷機密算法 .parseClaimsJws(jwt)//jwt是JWT字符串 .getBody(); String vaule = claims.get("key", String.class);//獲取自定義字段key //判斷自定義字段是否正確 if ("vaule".equals(vaule)) { return true; } else { return false; } } //在解析JWT字符串時,若是密鑰不正確,將會解析失敗,拋出SignatureException異常,說明該JWT字符串是僞造的 //在解析JWT字符串時,若是‘過時時間字段’已經早於當前時間,將會拋出ExpiredJwtException異常,說明本次請求已經失效 catch (SignatureException|ExpiredJwtException e) { return false; } }
Client端須要作的就是,根據API的需求將JWT字符串放入http請求中。個人作法是對於全部的API,在Client端生成JWT字段,而後將其添加到http請求的header中,確保全部的API都得到保護。對於一些比較敏感的信息,再用加一層JWT驗證。好比說用戶信息,在調用登陸API後,API Server將會返回一個特定的JWT字符串,該JWT字段總將會包含該用戶的userId。若是要獲取用戶信息,除了要將Client端生成的JWT字段放入請求,還須要將該JWT字符串放入請求。接下來展現一下利用OKHttp在http請求的header中加入JWT字段的代碼:
//該方法將會在全部請求的header中加入jwt public Response call(Request request) throws IOException { OkHttpClient client = new OkHttpClient(); Request.Builder requestBuilder = request.newBuilder() .addHeader("commonJwt", jwtService.makeJwt());//加入Client本地生成的JWT字符串 //加入登陸成功後獲取到的JWT字符串 String userJwt = jwtService.getUserJwt(); if (!StringUtils.isSpace(userJwt)) requestBuilder.addHeader("userJwt", userJwt); request = requestBuilder.build(); return client.newCall(request).execute(); }
API Server端須要作的就是,在收到API請求時,首先檢查client端生成的JWT字段是否有效,而後若是該API涉及敏感信息,則檢查檢測特定的JWT字段是否有效。接下來展現一下在spring
中利用aop
進行JWT字段的驗證:
@Pointcut("@annotation(org.springframework.web.bind.annotation.ResponseBody)") public void onCommonAuth(){} //全部的API都須要驗證client生成的JWT字段是否有效 @Order(1) @Around("onCommonAuth()") public Object onCommonAuth(ProceedingJoinPoint joinPoint) throws Throwable { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String commonJwt = request.getHeader("commonJwt"); if (jwtService.isCommonJwtValid(commonJwt)) { return joinPoint.proceed(); } else { return "沒有訪問該API的權限"; } } @Pointcut("execution(* com.demo.controller.UserController.getUserInfo(..))") public void onGetByUserInfo() {} //對獲取用戶信息API,堅持userJwt是否有效 @Order(2) @Around("onGetByUserInfo()&&args(userId,..)") public Object onGetByUserInfo(ProceedingJoinPoint joinPoint, Long userId) throws Throwable { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String userJwt = request.getHeader("userJwt"); if (jwtService.isUserJwtValid(userJwt, userId)) { return joinPoint.proceed(); } else { return "沒有訪問該API的權限"; } }
JWT在必定程度上,保護了API的安全。可是其自己仍是存在必定的缺陷的。好比說,必定JWT的加密密鑰一旦被泄露,那麼黑客就能夠生成JWT字符串了,所以保護好JWT加密密鑰很是重要。
在上面的例子當中,介紹了獲取用戶信息API須要加入userJwt的例子。userJwt其實就是在JWT字符串中加入了userId字段,繼而保證一個userJwt只能訪問一個用戶的信息。對於其餘的API,好比說PUT和POST操做,須要新增和修改數據的API。能夠將請求參數一併放入jwt中,以此來確保數據的安全性。不然黑客還能夠在JWT字符串尚未過時的時間段內,修改請求中的參數,達到攻擊的目的。
另外還要防止重複式攻擊,黑客還能夠在JWT字符串尚未過時的時間段內,重複提交請求,達到攻擊的目的。好比說新增訂單的API,若是被黑客採用重複式攻擊的方式,就會生成多個訂單。
RESTful Api 身份認證中的安全性設計探討
JSON Web Token - 在Web應用間安全地傳遞信息
八幅漫畫理解使用JSON Web Token設計單點登陸系統
How to Create and verify JWTs in Java
jwt官方介紹
jwt官網
jjwt在github上的地址