技术概述
- 为防止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上查找相关资料慢慢将这个功能完成了。
总结
我认为一个软件其安全验证是一个非常重要的部分,通过这次的实践,我将之前学到的理论变成了现实,是自身的一种进步。