App接口之Token令牌實(shí)現(xiàn)

《App接口之Token令牌實(shí)現(xiàn)》
轉(zhuǎn)載請注明來自 傻小孩b_移動開發(fā)http://www.itdecent.cn/users/d388bcf9c4d3)喜歡的可以關(guān)注我,不定期總結(jié)文章!您的支持是我的動力哈!

1、目的

眾所周知,在web端中,Token(令牌)只是作為防止用戶重復(fù)提交表單的作用而存在。但是對于App客戶端而言,Token卻充當(dāng)著另一種角色,類似現(xiàn)實(shí)生活中代表每個人的角色認(rèn)證、或者類似瀏覽器cookie代表你訪問網(wǎng)站的角色認(rèn)證。前提,在有用戶系統(tǒng)的應(yīng)用中,在每次訪問接口的時候,為了避免接口裸露被被無止境的請求攻擊,往往我們會利用一種機(jī)制,過濾一切非應(yīng)用用戶端的非合法請求。首先我們不可能每次利用賬號密碼作為我們的過濾標(biāo)準(zhǔn)(會存在被抓包密碼泄露風(fēng)險(xiǎn)),因此便有
Token(令牌)的存在。即在存在這里的Token是指在指定有效時間內(nèi)可以代表用戶角色,具有請求接口的權(quán)限。

當(dāng)然,這里有開發(fā)者會提問,為什么不適用session。理論上是可以的,只是如果是有接觸過這部分的移動開發(fā)者,session本地是不好處理的,并且完全依賴
session,會被黑客截取后模擬請求,依然會存在被攻擊的風(fēng)險(xiǎn)。

2、實(shí)現(xiàn)思路

這里我不做加密方式選擇的舉例,這里只是做了簡單的做了不可逆的MD5加密方式(賬號+時間戳)。首先web框架是利用(spring + struct2 + mybatis),簡單說明下實(shí)現(xiàn)思路:

1、用戶登錄。請求登錄接口,如果賬號密碼核對正確,會根據(jù)賬號和時間戳進(jìn)行Md5加密生成Token

2、服務(wù)端雙向保存token。服務(wù)端根據(jù)有效時間內(nèi)生成對應(yīng)的token之后,服務(wù)端雙向保存了Memcache中(
Memcache 是一種分布式緩存存儲機(jī)制,這里我就不詳細(xì)說明了),最后通過登錄接口返回Token信息至客戶端中
3、客戶端保存返回Token。
客戶端通過登錄接口成功返回的token,保存的內(nèi)存中,在每次請求接口都要攜帶這個token,進(jìn)行接口請求。

具體思路圖如下所示:

token_1.png

token_2.png

3、案例

(1)登錄接口實(shí)現(xiàn)

    /**
     * 用戶登錄
     * @throws IOException 
     */
    public void login() throws IOException{
        
        System.out.println("- AppUserManagerAction -" + "login");
        
        String token = TokenUtils.setToken(testAccount);
        System.out.println("賬號 " + testAccount + "生成的token:"+token);
        
        JSONObject json = new JSONObject();
        json.put("result", "0");
        json.put("reason", "用戶");
        json.put("token", token);
        
        this.getResponse().setContentType("text/plain");
        this.getResponse().setCharacterEncoding("UTF-8");
        this.getResponse().getWriter().write(json.toString());
        this.getResponse().getWriter().flush();
    }

(2)token生成

    /**
     * 為了登陸 或 刷新 Token 生成對應(yīng)token
     */
    public static String setToken(String account){
        
        String token = generateToken(account);
        
        // 存儲正反向
        MemcacheManager.set(account + TOKEN_MARKER, token,TOKEN_VAILD_TIME);
        MemcacheManager.set(token,account + TOKEN_MARKER,TOKEN_VAILD_TIME);

        return token;
    }

(3)struct2 請求攔截

首先在接口請求中,為了對token進(jìn)行驗(yàn)證,這里是直接在
struct2中寫了一個攔截器對請求接口用戶Token的驗(yàn)證,有疑問的可以自己谷歌搜索下struct2自定義攔截器


/**
 * Token 攔截器 用于檢測Token是否過去,過期直接不執(zhí)行Action
 * @author wsy
 *
 */
public class TokenInterceptor extends AbstractInterceptor {
    

    @Override
    public String intercept(ActionInvocation invocation) throws Exception {
        
        System.out.println("-- TokenInterceptor --");
        
        Object action = invocation.getAction();
        if (action instanceof MngUserAction || action instanceof AppUserManagerAction) {
            
            if (invocation.getProxy().getActionName().equals("login")) {
                System.out.println("-- TokenInterceptor -- " + "login 不需要攔截");
                return invocation.invoke();
            }
          
        }
        
        // 取得請求相關(guān)的ActionContext實(shí)例  
        ActionContext actionContext = invocation.getInvocationContext();  
        
        HttpServletRequest request = (HttpServletRequest) actionContext.get(StrutsStatics.HTTP_REQUEST);
        HttpServletResponse response = (HttpServletResponse)actionContext.get(org.apache.struts2.StrutsStatics.HTTP_RESPONSE);  
        
        if (TokenUtils.isVaild(request)) {
            System.out.println("token 有效");
            invocation.invoke();
        }else{
            System.out.println("token 過期");
            
            JSONObject json = new JSONObject();
            json.put("result", "002");
            json.put("reason", "Token 過期");

            response.setContentType("text/plain");
            response.setCharacterEncoding("UTF-8");
            response.getWriter().write(json.toString());
            response.getWriter().flush();
        }
        
        
        return null;
    }

}

(4)struct2 配置

    <!-- struts 設(shè)置默認(rèn)配置 -->
    <package name="struts-shop" extends="struts-default">
        <interceptors>
            <!-- 默認(rèn)攔截器 -->
            <interceptor name="authority" class="employee.utils.TokenInterceptor" />

            <!-- 攔截器棧 -->
            <interceptor-stack name="myStack">
                <interceptor-ref name="defaultStack" />
                <interceptor-ref name="authority" />
            </interceptor-stack>

        </interceptors>
        <default-interceptor-ref name="myStack" />
    </package>

    <package name="/employee/application/action" namespace="/employee/application/action"
        extends="struts-shop">

        <action name="login" class="employee.application.action.AppUserManagerAction"
            method="login" />
        <action name="getPersonInfo" class="employee.application.action.AppUserManagerAction"
            method="getPersonInfo"/>

    </package>

4、總結(jié)

當(dāng)然在正式平臺,加密方式不會這么簡單,具體可以看下
http://blog.csdn.net/jack85986370/article/details/51362278 這篇文章,非對稱加密就有很強(qiáng)的加密方式,通過公鑰與私鑰進(jìn)行信息加密。有什么問題可以聯(lián)系筆者,歡迎技術(shù)交流哈~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容