JSON Web Token(JWT)是一個開放標準(RFC 7519),它定義了一種緊湊且獨立的方式,用於在各方之間做爲JSON對象安全地傳輸信息。此信息能夠經過數字簽名進行驗證和信任。JWT可使用祕密(使用HMAC算法)或使用RSA或ECDSA的公鑰/私鑰對進行簽名。html
雖然JWT能夠加密以在各方之間提供保密,但咱們將專一於簽名令牌。簽名令牌能夠驗證其中包含的聲明的完整性,而加密令牌則隱藏其餘方的聲明。當使用公鑰/私鑰對簽名令牌時,簽名還證實只有持有私鑰的一方是簽署它的一方。java
如下是JSON Web令牌有用的一些場景:算法
在緊湊的形式中,JSON Web Tokens由dot(.
)分隔的三個部分組成,它們是:json
所以,JWT一般以下所示。api
xxxxx.yyyyy.zzzzz
複製代碼
讓咱們分解不一樣的部分。瀏覽器
標頭一般由兩部分組成:令牌的類型,即JWT,以及正在使用的簽名算法,例如HMAC SHA256或RSA。安全
例如:bash
{
"alg": "HS256",
"typ": "JWT"
}
複製代碼
而後,這個JSON被編碼爲Base64Url,造成JWT的第一部分。服務器
令牌的第二部分是payload,其中包含聲明。聲明是關於實體(一般是用戶)和其餘數據的聲明。聲明有三種類型:註冊,公開和私有聲明。app
已註冊的聲明:這些是一組預約義聲明,不是強制性的,但建議使用,以提供一組有用的,可互操做的聲明。其中一些是:iss (issuer), exp (expiration time), sub (subject), aud(audience)等。
請注意,聲明名稱只有三個字符,由於JWT意味着緊湊。
公開聲明:這些能夠由使用JWT的人隨意定義。但爲避免衝突,應在 IANA JSON Web令牌註冊表中定義它們,或者將其定義爲包含防衝突命名空間的URI。
私有聲明:私有聲明是提供者和消費者所共同定義的聲明,通常不建議存放敏感信息,由於base64是對稱解密的,意味着該部分信息能夠歸類爲明文信息。
示例payload能夠是:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
複製代碼
而後,payload通過Base64Url編碼,造成JSON Web令牌的第二部分。
請注意,對於簽名令牌,此信息雖然能夠防止被篡改,但任何人均可以讀取。除非加密,不然不要將祕密信息放在JWT的payload或header中。
要建立簽名部分,必須採用 header, payload, secret,標頭中指定的算法,並對其進行簽名。
例如,若是要使用HMAC SHA256算法,將按如下方式建立簽名:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
複製代碼
簽名用於驗證消息在此過程當中未被更改,而且,在使用私鑰簽名的令牌的狀況下,它還能夠驗證JWT的發件人是不是它所聲稱的人。
輸出是三個由點分隔的Base64-URL字符串,能夠在HTML和HTTP環境中輕鬆傳遞,而與基於XML的標準(如SAML)相比更加緊湊。
下面顯示了一個JWT,它具備先前的頭和有效負載編碼,並使用機密簽名。
1)用戶登錄的時候使用用戶名和密碼發送POST請求。
2)服務器使用私鑰建立一個jwt。
3)服務器返回這個jwt到瀏覽器。
4)瀏覽器將該jwt串加入請求頭中向服務器發送請求。
5)服務器驗證jwt。
6)返回相應的資源給客戶端。
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
複製代碼
PassToken:跳過驗證。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
複製代碼
UserLoginToken:須要驗證。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
boolean required() default true;
}
複製代碼
public class UserDo {
private String id;
private String userName;
private String userPasswd;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id == null ? null : id.trim();
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName == null ? null : userName.trim();
}
public String getUserPasswd() {
return userPasswd;
}
public void setUserPasswd(String userPasswd) {
this.userPasswd = userPasswd == null ? null : userPasswd.trim();
}
}
複製代碼
@Service
public class TokenServiceImpl implements TokenService {
@Override
public String getToken(UserDo userDo) {
String token="";
token= JWT.create().withAudience(userDo.getId())
.sign(Algorithm.HMAC256(userDo.getUserPasswd()));
return token;
}
}
複製代碼
public class AuthenticationInterceptor implements HandlerInterceptor {
@Reference
UserService userService;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
String token = httpServletRequest.getHeader("token");// 從 http 請求頭中取出 token
// 若是不是映射到方法直接經過
if (!(object instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
//檢查是否有passtoken註釋,有則跳過認證
if (method.isAnnotationPresent(PassToken.class)) {
PassToken passToken = method.getAnnotation(PassToken.class);
if (passToken.required()) {
return true;
}
}
//檢查有沒有須要用戶權限的註解
if (method.isAnnotationPresent(UserLoginToken.class)) {
UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
if (userLoginToken.required()) {
// 執行認證
if (token == null) {
throw new RuntimeException("無token,請從新登陸");
}
// 獲取 token 中的 user id
String userId;
try {
userId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
throw new RuntimeException("401");
}
UserDo user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException("用戶不存在,請從新登陸");
}
// 驗證 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getUserPasswd())).build();
try {
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
throw new RuntimeException("401");
}
return true;
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, Exception e) throws Exception {
}
}
複製代碼
流程圖以下:
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**");
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
}
複製代碼
@RestController
@CrossOrigin
@RequestMapping("/user")
@Api(tags = "用戶登陸")
public class LoginController extends BaseController {
@Reference
UserService userService;
@Reference
TokenService tokenService;
//登陸
@PostMapping("/login")
@ResponseBody
public Object login(UserDo user) {
JSONObject jsonObject = new JSONObject();
UserDo userForBase = userService.findUserByUsername(user.getUserName());
if (userForBase == null) {
jsonObject.put("message", "登陸失敗,用戶不存在");
return jsonObject;
} else {
if (!userForBase.getUserPasswd().equals(user.getUserPasswd())) {
jsonObject.put("message", "登陸失敗,密碼錯誤");
return jsonObject;
} else {
String token = tokenService.getToken(userForBase);
jsonObject.put("token", token);
jsonObject.put("user", userForBase);
return jsonObject;
}
}
}
@UserLoginToken
@GetMapping("/getMessage")
public String getMessage() {
return "你已經過驗證";
}
}
複製代碼
即傳入token才能夠調用getMessage()方法。
使用postmen調用接口:http://localhost:8094/user/getMessage
輸出錯誤信息以下:
使用postmen調用接口:http://localhost:8094/user/login
傳入id,用戶名和密碼三個參數,輸出結果以下:
在Headers中加入token參數,輸出結果以下: