1. 程式人生 > 其它 >token防禦CSRF攻擊

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)));

流程圖

遇到的問題和解決過程

再此之前直接觸過CSRF和token驗證的理論,所以主要是對此應用不太熟練,後面也問了團隊的小夥伴還有在CSDN上查詢相關資料慢慢將這個功能完成了。

總結

我認為一個軟體其安全驗證是一個非常重要的部分,通過這次的實踐,我將之前學到的理論變成了現實,是自身的一種進步。

參考文獻

SpringBoot整合JWT實現token驗證(自定義註解)
CSRF攻擊與防禦