token防御CSRF攻击

技术概述

  • 为防止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)));

流程图

image

遇到的问题和解决过程

再此之前直接触过CSRF和token验证的理论,所以主要是对此应用不太熟练,后面也问了团队的小伙伴还有在CSDN上查找相关资料慢慢将这个功能完成了。

总结

我认为一个软件其安全验证是一个非常重要的部分,通过这次的实践,我将之前学到的理论变成了现实,是自身的一种进步。

参考文献

SpringBoot集成JWT实现token验证(自定义注解)
CSRF攻击与防御

posted @ 2021-06-26 18:40  221801124张思萍  阅读(783)  评论(0编辑  收藏  举报
/*目录*/