技術概述
- 為防止CSRF跨站點請求偽造,在請求地址中添加 token 並驗證。
技術詳述:
在pom.xml中添加依賴
<!--jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
編寫工具類,利用JWT生成token
public class TokenUtil
{
public static String getToken(UserBO user)
{
return JWT.create().withAudience(String.valueOf(user.getId()))
.sign(Algorithm.HMAC256(user.getOpenId()));
}
public static String getToken(AdminBO admin)
{
return JWT.create().withAudience(String.valueOf(admin.getId()+10000))
.sign(Algorithm.HMAC256(admin.getAccount()));
}
}
編寫兩個自定義注解
用來跳過驗證的PassToken
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken
{
boolean required() default true;
}
需要通過token認證
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken
{
boolean required() default true;
}
使用攔截器獲取token並進行驗證
@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();
//檢查是否有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
long id;
UserBO user=null;
AdminBO admin=null;
try
{
id = Long.parseLong(JWT.decode(token).getAudience().get(0));
user = userDAO.getUserById(id);
// 驗證 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getOpenId())).build();
try
{
jwtVerifier.verify(token);
}
catch (JWTVerificationException e)
{
throw new RuntimeException("401");
}
}
catch (JWTDecodeException j)
{
throw new RuntimeException("401");
}
if (user == null)
{
throw new RuntimeException("用戶不存在,請重新登錄");
}
return true;
}
}
return true;
}
配置攔截器
package com.example.fidledemo.config;
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("/**");
}
@Bean
public AuthenticationInterceptor authenticationInterceptor()
{
return new AuthenticationInterceptor();
}
}
在控制器中處理用戶的登錄請求時,生成token並返回。
String token=TokenUtil.getToken(userBO);
return JSON.toJSONString(Result.successResult(new LoginVO(personVO,token)));
流程圖

遇到的問題和解決過程
再此之前直接觸過CSRF和token驗證的理論,所以主要是對此應用不太熟練,后面也問了團隊的小伙伴還有在CSDN上查找相關資料慢慢將這個功能完成了。
總結
我認為一個軟件其安全驗證是一個非常重要的部分,通過這次的實踐,我將之前學到的理論變成了現實,是自身的一種進步。
