RESTful API認證方式javascript
通常來說,對於RESTful API都會有認證(Authentication)和受權(Authorization)過程,保證API的安全性。html
Authentication vs. Authorizationjava
Authentication指的是肯定這個用戶的身份,Authorization是肯定該用戶擁有什麼操做權限。git
認證方式通常有三種github
Basic Authenticationweb
這種方式是直接將用戶名和密碼放到Header中,使用 Authorization: Basic Zm9vOmJhcg==
,使用最簡單可是最不安全。算法
TOKEN認證spring
這種方式也是再HTTP頭中,使用 Authorization: Bearer <token>
,使用最普遍的TOKEN是JWT,經過簽名過的TOKEN。數據庫
OAuth2.0apache
這種方式安全等級最高,可是也是最複雜的。若是不是大型API平臺或者須要給第三方APP使用的,不必整這麼複雜。
通常項目中的RESTful API使用JWT來作認證就足夠了。
Json web token (JWT), 是爲了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519).該token被設計爲緊湊且安全的,特別適用於分佈式站點的單點登陸(SSO)場景。JWT的聲明通常被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也能夠增長一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。
JWT官網: https://jwt.io/
JWT是由三段信息構成的,將這三段信息文本用.連接一塊兒就構成了Jwt字符串。就像這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
第一部分咱們稱它爲頭部(header),第二部分咱們稱其爲載荷(payload, 相似於飛機上承載的物品),第三部分是簽證(signature)。
header
jwt的頭部承載兩部分信息:
這裏的加密算法是單向函數散列算法,常見的有MD五、SHA、HAMC。這裏使用基於密鑰的Hash算法HMAC生成散列值。
完整的頭部就像下面這樣的JSON:
{
'typ': 'JWT',
'alg': 'HS256'
}
而後將頭部進行base64加密(該加密是能夠對稱解密的),構成了第一部分
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
playload
載荷就是存放有效信息的地方,這些有效信息包含三個部分:
標準中註冊的聲明 (建議但不強制使用) :
公共的聲明:
公共的聲明能夠添加任何的信息,通常添加用戶的相關信息或其餘業務須要的必要信息.但不建議添加敏感信息,由於該部分在客戶端可解密
私有的聲明:
私有聲明是提供者和消費者所共同定義的聲明,通常不建議存放敏感信息,由於base64是對稱解密的,意味着該部分信息能夠歸類爲明文信息。
定義一個payload:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
而後將其進行base64加密,獲得Jwt的第二部分:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
signature
jwt的第三部分是一個簽證信息,這個簽證信息由三部分組成:
header (base64後的)
payload (base64後的)
secret
這個部分須要base64加密後的header和base64加密後的payload使用.鏈接組成的字符串,而後經過header中聲明的加密方式進行加鹽secret組合加密,而後就構成了jwt的第三部分。
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
將這三部分用.鏈接成一個完整的字符串,構成了最終的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意:secret是保存在服務器端的,jwt的簽發生成也是在服務器端的,secret就是用來進行jwt的簽發和jwt的驗證,因此,它就是你服務端的私鑰,在任何場景都不該該流露出去。一旦客戶端得知這個secret, 那就意味着客戶端是能夠自我簽發jwt了。
通常是在請求頭裏加入Authorization,並加上Bearer標註:
fetch('api/user/1', {
headers: {
'Authorization': 'Bearer ' + token
}
})
服務器負責解析這個HTTP頭來作用戶認證和受權處理。大體流程以下:
JWT協議自己不具有安全傳輸功能,因此必須藉助於SSL/TLS的安全通道,因此建議以下:
簡要的說明下咱們爲何要用JWT,由於咱們要實現徹底的先後端分離,因此不可能使用session,cookie的方式進行鑑權,因此JWT就被派上了用場,能夠經過一個加密密鑰來進行先後端的鑑權。
程序邏輯:
這裏我講一下如何在SpringBoot中使用JWT來作接口權限認證,安全框架依舊使用Shiro,JWT的實現使用 jjwt
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.3.0</version>
</dependency>
<!-- shiro 權限控制 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- shiro ehcache (shiro緩存)-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
這個在shiro一節講過若是建立角色權限表,添加用戶Service來執行查找用戶操做,這裏就很少講具體實現了,只列出關鍵代碼:
/**
* 經過名稱查找用戶
*
* @param username
* @return
*/
public ManagerInfo findByUsername(String username) {
ManagerInfo managerInfo = managerInfoDao.findByUsername(username);
if (managerInfo == null) {
throw new UnknownAccountException();
}
return managerInfo;
}
用戶信息類:
public class ManagerInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主鍵ID
*/
private Integer id;
/**
* 帳號
*/
private String username;
/**
* 密碼
*/
private String password;
/**
* md5密碼鹽
*/
private String salt;
/**
* 一個管理員具備多個角色
*/
private List<SysRole> roles;
咱們寫一個簡單的JWT加密,校驗工具,而且使用用戶本身的密碼充當加密密鑰,這樣保證了token 即便被他人截獲也沒法破解。而且咱們在token中附帶了username信息,而且設置密鑰5分鐘就會過時。
public class JWTUtil {
// 過時時間5分鐘
private static final long EXPIRE_TIME = 5 * 60 * 1000;
/**
* 校驗token是否正確
*
* @param token 密鑰
* @param secret 用戶的密碼
* @return 是否正確
*/
public static boolean verify(String token, String username, String secret) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("username", username)
.build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception exception) {
return false;
}
}
/**
* 得到token中的信息無需secret解密也能得到
*
* @return token中包含的用戶名
*/
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 生成簽名,5min後過時
*
* @param username 用戶名
* @param secret 用戶的密碼
* @return 加密的token
*/
public static String sign(String username, String secret) {
try {
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(