爲合作公司提供接口的權限驗證問題 攔截器 cookie

巨大的建築,總是由一木一石疊起來的,我們何妨做做這一木一石呢?我時常做些零碎事,就是爲此。
這是對的,但是我沒有說過這句話! —— 魯迅

問題描述

通過Api爲第三方公司提供接口服務是一項很common的需求,其中一種實現方式就是利用restful的形式,提供http接口服務.
提供出接口之後,誰都可以來訪問,對系統本身是有一定危險性的,所以我們要做一下驗證信息,來確認對方的身份.

對於自己內部的接口,比如用於提供給mask(前臺頁面)訪問的api,可以做登錄驗證,只有登錄了,纔可以訪問。
但是對於第三方公司而言,不是我們的用戶,屬於合作伙伴,性質不一樣.
我們需要給他們提供10個接口,這10個接口是需要放開權限的,不然直接訪問的話,會直接提示:*未登錄* 的錯誤。
權限放開了,那麼權限如何驗證呢.

權限驗證

不增加任何驗證,直接可以訪問

安全性差,比如推送數據的接口(一般數據是有格式的,他要是自己猜確實不好猜到),假如第三方公司在正常使用的時候,請求數據推送,
被壞人截取到了相應的信息。通過分析,他就可以一直推送,知道把你的數據庫填滿.

接口的驗證

  1. 客戶端訪問接口,需要佩戴token,假如這個token正好是我們產生的,則驗證通過,token有時效限制。
  2. 這樣一來,需要提供一個接口,便是產生token的接口,而這個接口是開放的,誰都可以訪問。
  3. 那麼這時候,假如一個壞人想要惡意訪問的話,他首先需要知道這個產生token的接口是什麼,其次需要知道自己要訪問的接口是什麼,然後再訪問

安全性比開始的已經大一些了。此時假如他截取到了某次請求的數據,再請求的話,就只能在實效內管用了.

對token產生的接口進行驗證處理

上面是對token產生的接口可以隨意訪問,一旦被人知道了這個接口,則可以隨意獲取token值了,加一個判斷的話,比如需要用戶名,和密碼,則可以進行一種驗證。
簡單的寫法,就是對不同的第三方協議公司定一個用戶名,密碼,直接傳過來.

除了驗證身份之外,加上授權處理.

這種情況就跟咱們的登錄差不多了,但是身份不一樣。
一般登錄的設計裏面,都會有角色的設計,身份不一致的話,可以新增一種角色,這種角色就給服務公司提供一種控制,這樣就類似於登錄了.
但是一般對接公司需要的接口都是固定的,假如需要根據功能收費的話,授權是需要考慮的.

以下主要說一下接口的簡單驗證.

接口的驗證

提供產生token接口

比如直接返回UUID

@RequestMapping(value = "/", method = RequestMethod.POST)
@ResponseBody
public Result<String, State> getToken(HttpServletRequest request, HttpServletResponse response) {
  Result<String, State> result = new Result<>();
  UUID uuid = UUID.randomUUID();
  String token = uuid.toString();
  //放到redis裏面,實效爲30分鐘.
  RedisCacheUtil.setex(BUSSINESSKEY, token, token, 30 * 60);
  result.setCode(State.SUCCESS);
  result.setData(token);
  return result;
}

這樣,就把生成的token返回了,那麼接口的寫法如下:

@RequestMapping(value = "/", method = RequestMethod.GET)
@ResponseBody
public Result<String, State> getSomeThing(@RequestParam String token,
                                          String param) {
  Result<String, State> result = new Result<>();
  String tokenRedis = RedisCacheUtil.get(BUSSINESSKEY, token, String.class);
  if (token.equals(tokenRedis) {
      result.setCode(State.SUCCESS);
  } else {
      result.setCode(State.FAILED);
  }
  return result;
}

關於redis裏key值的討論

開始我打算用請求方的ip,假如是ip的話,那麼同一個ip則會用同一個token,不同的ip用不同的token,假如你
知道了別人的token,不是同一個ip,也照樣不能訪問。
現實發現問題,是因爲我們的接口是寫在api服務裏面,而api服務是一個內網服務,第三方(App)是通過請求我們的nginx服務轉發到api裏面。
這樣的話,每次我獲取到的ip,都是我們自己服務器的內網地址,並不能用.

後來發現用token作爲key值也可以,這樣每個用戶,就擁有自己的token了,但是問題是隨便一個人一旦獲取到了別人的token,在token相應未失效的時候,也是可以用的.

用攔截器來處理token的驗證。

按照上面的方式寫接口,假如提供10個接口的話,則在每個接口中都需要接收token參數.不好維護。

配置攔截器

<mvc:interceptors>
    <!--跨域過濾器-->
    <bean class="com.enn.zhwl.interceptor.CrossInterceptor"/>

    <mvc:interceptor>
        <mvc:mapping path="/hdd/out/**" />
        <mvc:exclude-mapping path="/hdd/out/token/**" />
        <bean class="com.enn.zhwl.interceptor.HddInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>
  • springMVC框架中,在servlet.xml中加入攔截器的配置
  • mvc:mapping 是改攔截器對哪些類生效
  • mvc:exclude-mapping 是將上面生效的文件中剔除一些類
  • bean 則是攔截器的實現類.

攔截器實現

public class HddInterceptor implements HandlerInterceptor {
  private static final String BUSSINESSKEY = "HDDTOKEN";

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    String token = request.getParameter("token");
    String tokenRedis = RedisCacheUtil.get(BUSSINESSKEY, token, String.class);

    if (token.equals(tokenRedis) {
       return true;
    } else {
      PrintWriter writer = response.getWriter();
      writer.write("token error");
      writer.close();
       return false;
    }

  }

  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

  }

  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

  }

}

token作爲參數傳遞的問題

這樣是讓客戶端將token值放到請求的參數中,來進行接收。
現在有一種情況,就是一般接口提供有兩種方式。

  • 接收普通變量,比如String 等類型的值
  • 接收對象類型(@RequestBody)。

接收普通變量和對象類型時,發送的ajax請求的 contentType 是不一樣的。
發送對象數據的時候,contentType='application/json',對於這種情況,直接用 request.getParameter 就獲取不到了.

token放到cookie中進行傳遞

一般登錄驗證的時候,token就是放到cookie中的.放到cookie中可以解決上面的問題。
而且還方便,客戶端完全感知不到token的存在,也不用在調用方法的時候,顯示傳遞.

修改設置token的方法

將token放到cookie中而不是放到data中.

public Result<String, State> getToken(HttpServletRequest request, HttpServletResponse response) {
  Result<String, State> result = new Result<>();
  UUID uuid = UUID.randomUUID();
  String token = uuid.toString();
  //放到redis裏面,實效爲30分鐘.
  RedisCacheUtil.setex(BUSSINESSKEY, token, token, 30 * 60);
  //放到cookie中而不是放到data中.
  Cookie cookie = new Cookie("hddtoken", token);
  cookie.setPath("/");
  response.addCookie(cookie);
  result.setCode(State.SUCCESS);
  return result;
}

修改攔截器的實現

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  //String token = request.getParameter("token");
  Cookie[] cookies = request.getCookies();
  Optional<Cookie> cookieToken = Arrays.stream(cookies).filter(cookie -> cookie.getName().equals("hddtoken")).findFirst();
  //這裏需要判斷token是否存在,就不寫了
  String token = cookieToken.get().getValue();
  String tokenRedis = RedisCacheUtil.get(BUSSINESSKEY, token, String.class);

  if (token.equals(tokenRedis) {
      return true;
  } else {
    PrintWriter writer = response.getWriter();
    writer.write("token error");
    writer.close();
    return false;
  }
}

這樣客戶端也不用傳了,接收端也不用接收了,一切都默默的執行,只需要在請求之前,先調用一下獲取token的接口就行了.

這裏是寫了一些簡單驗證是思路與遇到的問題,至於加密啊,密碼啊,授權啊等便是另外的事了.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章