從零開始的SpringBoot專案 ( 七 ) 實現基於Token的使用者身份驗證
阿新 • • 發佈:2020-08-22
1.首先了解一下Token
- uid: 使用者唯一身份標識
- time: 當前時間的時間戳
- sign: 簽名, 使用 hash/encrypt 壓縮成定長的十六進位制字串,以防止第三方惡意拼接
- 固定引數(可選): 將一些常用的固定引數加入到 token 中是為了避免重複查資料庫
2.token 驗證的機制(流程)
- 使用者登入校驗,校驗成功後就返回Token給客戶端。
- 客戶端收到資料後儲存在客戶端
- 客戶端每次訪問API是攜帶Token到伺服器端。
- 伺服器端採用filter過濾器校驗。校驗成功則返回請求資料,校驗失敗則返回錯誤碼
3.使用SpringBoot搭建基於token驗證
3.1 引入 POM 依賴
<!--Json web token--> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency>
3.2 新建一個攔截器配置 用於攔截前端請求 實現 WebMvcConfigurer
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; @Configurationpublic class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authenticationInterceptor()) .addPathPatterns("/**");//攔截所有請求,通過判斷是否有 @LoginRequired 註解 決定是否需要登入 } @Bean public AuthenticationInterceptor authenticationInterceptor() {return new AuthenticationInterceptor(); } }
3.3 新建一個AuthenticationInterceptor實現HandlerInterceptor介面 實現攔截還是放通的邏輯
import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.exceptions.JWTVerificationException; import com.my_springboot.rbac.pojo.Admin; import com.my_springboot.rbac.service.IAdminService; 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; /** * 攔截器去獲取token並驗證token*/ public class AuthenticationInterceptor implements HandlerInterceptor { @Autowired private IAdminService adminService; @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) { 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 adminId; try { adminId = JWT.decode (token).getAudience ().get (0); } catch (JWTDecodeException j) { throw new RuntimeException ("401"); } Admin admin = adminService.getById (adminId); if (admin == null) { throw new RuntimeException ("使用者不存在"); } // 驗證 token JWTVerifier jwtVerifier = JWT.require (Algorithm.HMAC256 (admin.getPassword ())).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 { } }
3.4 新建兩個註解 用於標識請求是否需要進行Token 驗證
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 需要登入才能進行操作的註解UserLoginToken*/ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface UserLoginToken { boolean required() default true; }
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 用來跳過驗證的PassToken*/ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface PassToken { boolean required() default true; }
3.5 新建一個Service用於下發Token
import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.my_springboot.rbac.pojo.Admin; import org.springframework.stereotype.Service; import java.util.Date; /** * 下發token*/ @Service public class TokenService { public String getToken(Admin admin) { Date start = new Date (); long currentTime = System.currentTimeMillis () + 60 * 60 * 1000;//一小時有效時間 Date end = new Date (currentTime); return JWT.create ().withAudience (admin.getId ()).withIssuedAt (start) .withExpiresAt (end) .sign (Algorithm.HMAC256 (admin.getPassword ())); } }
3.6 新建一個工具類 使用者從token中取出使用者Id
import com.auth0.jwt.JWT; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; /** * token工具類*/ public class TokenUtil { public static String getTokenUserId() { String token = getRequest().getHeader("token");// 從 http 請求頭中取出 token String userId = JWT.decode(token).getAudience().get(0); return userId; } /** * 獲取request * * @return */ public static HttpServletRequest getRequest() { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder .getRequestAttributes(); return requestAttributes == null ? null : requestAttributes.getRequest(); } }