前言
放假之前做了幾個小專案+課設,都用到了token實現登入驗證和許可權判斷,然鵝當時和同組的小夥伴也都是第一次接觸到了token,於是乎都是一臉懵逼(xjbx)的寫完了登入驗證的前後端邏輯(我寫前端,同組的小夥伴寫後端)。今天有空仔細學習了一下SpringBoot實現token認證以及和前端的互動,踩了不少坑,在這裡記錄一下
後端實現
- 首先需要匯入jwt的包,相關的pom.xml檔案如下:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.5.0</version>
</dependency>
複製程式碼
- 然後開始編寫TokenUtil類,首先定義token的過期時間和私鑰
private static final long EXPIRE_TIME = 15 * 60 * 1000;
private static final String TOKEN_SECRET = "thefirsttoken123";
複製程式碼
- 實現簽名方法: 這裡不應該使用密碼進行加密,不安全,但是是自己的小demo就這樣寫了。
/**
* 生成簽名,15分鐘過期
* @param **username**
* @param **password**
* @return
*/
public static String sign(String username, String password) {
try {
// 設定過期時間
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
// 私鑰和加密演算法
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
// 設定頭部資訊
Map<String, Object> header = new HashMap<>(2);
header.put("Type", "Jwt");
header.put("alg", "HS256");
// 返回token字串
return JWT.create()
.withHeader(header)
.withClaim("loginName", username)
.withClaim("pwd", password)
.withExpiresAt(date)
.sign(algorithm);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
複製程式碼
- 實現token的檢驗方法:
/**
* 檢驗token是否正確
* @param **token**
* @return
*/
public static boolean verify(String token){
try {
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception e){
return false;
}
}
複製程式碼
至此,工具類就編寫完成啦!
- 登入的controller層方法 這裡獲取到前端傳送過來的請求體,取出其中的使用者名稱和密碼,和資料庫比對如果無誤的話,簽發token,並返回給前端。 (API響應結果還沒有封裝,看著有點亂,嘿嘿)
@PostMapping(value = "/login")
public Map<String, Object> login(@RequestBody SysUser sysUser){
Map<String, Object> map = new HashMap<>();
String username = sysUser.getUsername();
String password = sysUser.getPassword();
if (sysUserService.login(username, password)){
String token = TokenUtil.sign(username,password);
if (token != null){
map.put("code", "10000");
map.put("message","認證成功");
map.put("token", token);
return map;
}
}
map.put("code", "00000");
map.put("message","認證失敗");
return map;
}
複製程式碼
現在服務端給客戶端簽發token的功能已經差不多實現了。 那麼客戶端如何將token應用到以後的請求中,服務端又如何識別token呢?
- 實現服務端自定義攔截器
/**
* 自定義token攔截器
*/
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (request.getMethod().equals("OPTIONS")){
response.setStatus(HttpServletResponse.SC_OK);
return true;
}
response.setCharacterEncoding("utf-8");
String token = request.getHeader("admin-token");
if (token != null){
boolean result = TokenUtil.verify(token);
if(result){
System.out.println("通過攔截器");
return true;
}
}
System.out.println("認證失敗");
response.getWriter().write("50000");
return false;
}
}
複製程式碼
TokenInterceptor實現了HandlerInterceptor介面,重寫了preHandle方法,該方法是在每個請求之前觸發執行,從request的頭裡面取出token,這裡我們統一了存放token的鍵為admin-token,驗證通過,放行,驗證不通過,返回認證失敗資訊。 這裡有一個坑,由於使用axios,每次前端傳送請求,都會先發一次預請求,也就是RequestMethod為OPTIONS不是我們常見的get、post等(關於OPTIONS的解釋,可以谷歌一下)。所有在這裡我們需要做一次判斷,如果請求方法為OPTIONS,就直接return通過。
- 配置攔截器 對登入介面的請求不攔截
@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {
private TokenInterceptor tokenInterceptor;
public InterceptorConfig(TokenInterceptor tokenInterceptor) {
this.tokenInterceptor = tokenInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
List<String> excludePath = new ArrayList<>();
String sysUserLogin = "/api/sysUser/login";
excludePath.add(sysUserLogin);
registry.addInterceptor(tokenInterceptor).excludePathPatterns(excludePath);
}
}
複製程式碼
- 服務端解析token 現在為了之後根據token去做相關的查詢,我們需要對token進行解密,取出之前加密的loginName。然後就可以愉快的增刪查改啦~
/**
* 從token中獲取username資訊
* @param **token**
* @return
*/
public static String getUserName(String token){
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("loginName").asString();
} catch (JWTDecodeException e){
e.printStackTrace();
return null;
}
}
複製程式碼
前端實現
前端使用vue+axios,主要是實現對axios的再封裝。 相關程式碼如下 大致邏輯就是,如果vuex中已經存在了token,那麼就把它放到請求頭中發往服務端。
// 建立axios例項
const service = axios.create({
baseURL: process.env.BASE_API // api 的 base_url
})
// request攔截器
service.interceptors.request.use(
config => {
if (store.getters.token) {
config.headers['admin-token'] = getToken() // 讓每個請求攜帶自定義token
}
return config
},
error => {
// 出錯
console.log(error)
Promise.reject(error)
}
)
複製程式碼
結語
哇在掘金的第一篇文章寫完了,嘻嘻,繼續努力!!!。
昨日今日明日日日碼不停蹄,
去年今年明年年年都是單身。
橫批:這就是命