雙方之間傳遞安全信息的簡潔的、URL安全的表述性聲明規範。JWT做爲一個開放的標準(RFC 7519),定義了一種簡潔的,自包含的方法用於通訊雙方之間以Json對象的形式安全的傳遞信息。簡潔(Compact): 能夠經過URL,POST參數或者在HTTP header發送,由於數據量小,傳輸速度也很快 自包含(Self-contained):負載中包含了全部用戶所須要的信息,避免了屢次查詢數據庫。html
第一步:引入maven依賴java
<!--引入JWT依賴,因爲是基於Java,因此須要的是java-jwt--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency>
第二步:建立兩個註解,攔截器經過註釋區分是否進行權限攔截web
package com.pjb.springbootjjwt.jimisun; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface LoginToken { boolean required() default true; }
package com.pjb.springbootjjwt.jimisun; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface CheckToken { boolean required() default true; }
第三步:編寫JwtUtil工具類(生成token,解析token,校驗token)算法
package com.pjb.springbootjjwt.jimisun; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; /** * @Author:jimisun * @Description: * @Date:Created in 14:08 2018/8/15 * @Modified By: */ public class JwtUtil { /** * 用戶登陸成功後生成Jwt * 使用Hs256算法 私匙使用用戶密碼 * * @param ttlMillis jwt過時時間 * @param user 登陸成功的user對象 * @return */ public static String createJWT(long ttlMillis, User user) { //指定簽名的時候使用的簽名算法,也就是header那部分,jjwt已經將這部份內容封裝好了。 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; //生成JWT的時間 long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); //建立payload的私有聲明(根據特定的業務須要添加,若是要拿這個作驗證,通常是須要和jwt的接收方提早溝通好驗證方式的) Map<String, Object> claims = new HashMap<String, Object>(); claims.put("id", user.getId()); claims.put("username", user.getUsername()); claims.put("password", user.getPassword()); //生成簽名的時候使用的祕鑰secret,這個方法本地封裝了的,通常能夠從本地配置文件中讀取,切記這個祕鑰不能外露哦。它就是你服務端的私鑰,在任何場景都不該該流露出去。一旦客戶端得知這個secret, 那就意味着客戶端是能夠自我簽發jwt了。 String key = user.getPassword(); //生成簽發人 String subject = user.getUsername(); //下面就是在爲payload添加各類標準聲明和私有聲明瞭 //這裏其實就是new一個JwtBuilder,設置jwt的body JwtBuilder builder = Jwts.builder() //若是有私有聲明,必定要先設置這個本身建立的私有的聲明,這個是給builder的claim賦值,一旦寫在標準的聲明賦值以後,就是覆蓋了那些標準的聲明的 .setClaims(claims) //設置jti(JWT ID):是JWT的惟一標識,根據業務須要,這個能夠設置爲一個不重複的值,主要用來做爲一次性token,從而回避重放***。 .setId(UUID.randomUUID().toString()) //iat: jwt的簽發時間 .setIssuedAt(now) //表明這個JWT的主體,即它的全部人,這個是一個json格式的字符串,能夠存放什麼userid,roldid之類的,做爲何用戶的惟一標誌。 .setSubject(subject) //設置簽名使用的簽名算法和簽名使用的祕鑰 .signWith(signatureAlgorithm, key); if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); //設置過時時間 builder.setExpiration(exp); } return builder.compact(); } /** * Token的解密 * @param token 加密後的token * @param user 用戶的對象 * @return */ public static Claims parseJWT(String token, User user) { //簽名祕鑰,和生成的簽名的祕鑰如出一轍 String key = user.getPassword(); //獲得DefaultJwtParser Claims claims = Jwts.parser() //設置簽名的祕鑰 .setSigningKey(key) //設置須要解析的jwt .parseClaimsJws(token).getBody(); return claims; } /** * 校驗token * 在這裏可使用官方的校驗,我這裏校驗的是token中攜帶的密碼於數據庫一致的話就校驗經過 * @param token * @param user * @return */ public static Boolean isVerify(String token, User user) { //簽名祕鑰,和生成的簽名的祕鑰如出一轍 String key = user.getPassword(); //獲得DefaultJwtParser Claims claims = Jwts.parser() //設置簽名的祕鑰 .setSigningKey(key) //設置須要解析的jwt .parseClaimsJws(token).getBody(); if (claims.get("password").equals(user.getPassword())) { return true; } return false; } }
第四步:編寫攔截器攔截請求進行權限驗證spring
package com.pjb.springbootjjwt.interceptor; import com.auth0.jwt.JWT; import com.auth0.jwt.exceptions.JWTDecodeException; import com.pjb.springbootjjwt.jimisun.CheckToken; import com.pjb.springbootjjwt.jimisun.JwtUtil; import com.pjb.springbootjjwt.jimisun.LoginToken; import com.pjb.springbootjjwt.jimisun.User; import com.pjb.springbootjjwt.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; /** * jimisun */ public class AuthenticationInterceptor implements HandlerInterceptor { @Autowired UserService userService; @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception { // 從 http 請求頭中取出 token String token = httpServletRequest.getHeader("token"); // 若是不是映射到方法直接經過 if (!(object instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod = (HandlerMethod) object; Method method = handlerMethod.getMethod(); //檢查是否有LoginToken註釋,有則跳過認證 if (method.isAnnotationPresent(LoginToken.class)) { LoginToken loginToken = method.getAnnotation(LoginToken.class); if (loginToken.required()) { return true; } } //檢查有沒有須要用戶權限的註解 if (method.isAnnotationPresent(CheckToken.class)) { CheckToken checkToken = method.getAnnotation(CheckToken.class); if (checkToken.required()) { // 執行認證 if (token == null) { throw new RuntimeException("無token,請從新登陸"); } // 獲取 token 中的 user id String userId; try { userId = JWT.decode(token).getClaim("id").asString(); } catch (JWTDecodeException j) { throw new RuntimeException("訪問異常!"); } User user = userService.findUserById(userId); if (user == null) { throw new RuntimeException("用戶不存在,請從新登陸"); } Boolean verify = JwtUtil.isVerify(token, user); if (!verify) { throw new RuntimeException("非法訪問!"); } 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 { } }
配置攔截器(ps:我使用的是springboot,你們使用ssm配置攔截器的方式不同)數據庫
package com.pjb.springbootjjwt.interceptorconfig; import com.pjb.springbootjjwt.interceptor.AuthenticationInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authenticationInterceptor()) .addPathPatterns("/**"); // 攔截全部請求,經過判斷是否有 @LoginRequired 註解 決定是否須要登陸 } @Bean public AuthenticationInterceptor authenticationInterceptor() { return new AuthenticationInterceptor(); } }
第五步:在示例Controller中的實際應用json
package com.pjb.springbootjjwt.jimisun; import com.alibaba.fastjson.JSONObject; import com.pjb.springbootjjwt.annotation.PassToken; import com.pjb.springbootjjwt.annotation.UserLoginToken; import com.pjb.springbootjjwt.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import java.util.UUID; /** * @Author:jimisun * @Description: * @Date:Created in 15:04 2018/8/15 * @Modified By: */ @RestController @RequestMapping("/api") public class UserController { @Autowired private UserService userService; //登陸 @PostMapping("/login") @LoginToken public Object login(@RequestBody @Valid com.pjb.springbootjjwt.jimisun.User user) { JSONObject jsonObject = new JSONObject(); com.pjb.springbootjjwt.jimisun.User userForBase = userService.findByUsername(user); if (userForBase == null) { jsonObject.put("message", "登陸失敗,用戶不存在"); return jsonObject; } else { if (!userForBase.getPassword().equals(user.getPassword())) { jsonObject.put("message", "登陸失敗,密碼錯誤"); return jsonObject; } else { String token = JwtUtil.createJWT(6000000, userForBase); jsonObject.put("token", token); jsonObject.put("user", userForBase); return jsonObject; } } } //查看我的信息 @CheckToken @GetMapping("/getMessage") public String getMessage() { return "你已經過驗證"; } }
最後一步,咱們如今來訪問一下啊api
先進行登陸安全
登陸後攜帶token進行業務操做springboot
簡單的Demo程序就到這裏了,固然還有不少東西沒有考慮到,好比jwt在集羣環境下的應用等一些問題留到下回探討啦~
雜家不如專家,精益求精