基于Volley,Gson封装支持JWT无状态安全验证和数据防篡改的GsonRequest网络请求类

      这段时间做新的Android项目的客户端和和REST API通讯框架架构设计,使用了很多新技术,最终的方案也相当简洁优雅,客户端只需要传Java对象,服务器端返回json字符串,自动解析成Java对象, 无状态安全验证基于JWT实现,JWT规范的细节可以参考我前面的文章。JWT的token和数据防篡改签名统一放在HTTP Header中,这样就实现了对请求内容和返回结果的无侵入性,服务器端也可以在全局过滤器中统一处理安全验证。

      Android客户端使用了Volley网络请求框架和Gson解析库,基于这2个Goolge的框架封装了自己的GsonRequest网络请求基类, 网络请求类没有做特别处理,网络response返回消息,做了个带泛型的基类,统一处理错误码,消息和返回结果,Android端的代码如下:


package com.zxt.network;

/**
 * HTTP返回基类,返回正确结果status>0,错误结果status<0,
 * message为服务器端返回消息,客户端直接显示
 * T data为返回的json对象或数组,自动解析为Java对象
 * errorCodes为服务器端返回的Dubbo接口错误码,逗号分隔,仅做调试用途
 * Created by zhangxitao on 15/8/17.
 */
public class BaseResponse {
    public int status;
    public String message;
    public String errorCodes;
    public T data ;

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getErrorCodes() {
        return errorCodes;
    }

    public void setErrorCodes(String errorCodes) {
        this.errorCodes = errorCodes;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

package com.zxt.network;

import android.text.TextUtils;
import android.util.Log;

import com.android.volley.AuthFailureError;
import com.android.volley.NetworkResponse;
import com.android.volley.ParseError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;
import com.android.volley.toolbox.HttpHeaderParser;
import com.google.gson.Gson;
import com.qianmi.qmpos.AppConfig;

import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

/**
 * Gson请求类,传入请求对象和返回类名,返回解析好的对象,封装JWT安全验证,数据签名
 * @param 
 */
public class GsonRequest extends Request {

    private static final String TAG = "GsonRequest";

    private final Listener mListener;

    private Gson mGson;

    private Class mClass;
    private Object mRequest;
    private String mBody;

    public GsonRequest(int method, String url, Object request, Class clazz, Listener listener, ErrorListener errorListener) {
        super(method, url, errorListener);
        mGson = new Gson();
        mClass = clazz;
        mListener = listener;
        mRequest = request;
        if (null != request) {
            mBody = mGson.toJson(mRequest);
        }
    }

    public GsonRequest(String url, Object request, Class clazz, Listener listener, ErrorListener errorListener) {
        this(Method.POST, url, request, clazz, listener, errorListener);
        Log.d(TAG, "-----url is:" + url);
    }

    @Override
    public byte[] getBody() throws AuthFailureError {
        if (null == mRequest) {
            return null;
        }
        Log.d(TAG, "-----request json: " + mBody);
        return mBody.getBytes();
    }

    @Override
    protected Response parseNetworkResponse(NetworkResponse response) {
        try {
            String jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
            return Response.success(mGson.fromJson(jsonString, mClass), HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        }
    }

    @Override
    protected void deliverResponse(T response) {
        mListener.onResponse(response);
        Log.i(TAG, "-----response:" + new Gson().toJson(response));
    }

    @Override
    public Map getHeaders() throws AuthFailureError {
        HashMap extraHeaders = new HashMap<>();
        extraHeaders.put("Content-Type", "application/json");
        extraHeaders.put("Accept", "application/json");

        //Data Sign
        if (!TextUtils.isEmpty(mBody)) {
            extraHeaders.put("Sign", MD5Util.stringToMD5(AppConfig.SIGN_SECRET + mBody + AppConfig.SIGN_SECRET ));
        }
        //JWT token
        if (!TextUtils.isEmpty(AppConfig.TOKEN)) {
            extraHeaders.put("Authorization", "Bearer " + AppConfig.TOKEN);
        }
        Log.d(TAG, "-----extra headers: " + extraHeaders.toString());

        return extraHeaders;
    }
}

一个简单的调用例子

  LoginRequest loginRequest = new LoginRequest();
 
        loginRequest.setNickName(username);
        loginRequest.setPassword(RSAUtils.getEncryptPwd(password));

        Request request = new GsonRequest(
               AppConfig.SERVER_URL + "/login",loginRequest, LoginResponse.class,
                new Response.Listener() {
                    @Override
                    public void onResponse(LoginResponse resp) {
                        L.d("TAG", "user is " + resp.data);
                        //TODO 

                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.e("TAG", error.getMessage(), error);
            }
        });
mVolleyQueve.add(request)



服务器端使用Spring Boot,Spring MVC, JPA,JWT等技术构建,同样的简洁优雅, Spring Boot微服务技术确实简化了接口的开发,可以做到零XML配置文件。由于暂时权限这边不够复杂,暂时没有引入Spring Security框架,安全验证通过一个全局的过滤器完成。API接口通过使用@RestController注解实现。具体的参考代码如下:


import com.zxt.domain.BaseResponse;
import com.zxt.domain.pos.Device;
import com.zxt.service.DeviceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/device")
public class DeviceController {

    @Autowired
    DeviceService deviceService;

    @RequestMapping("activate")
    @ResponseBody
    public BaseResponse activate(@RequestBody @Validated Device device) {

        try {
            this.deviceService.activateDevice(device);
        } catch (Exception e) {
            return new BaseResponse(-1,"activate failed");
        }

        return new BaseResponse(1,"device activated");
    }

}

Google Volley这个框架除了解决了频繁网络请求情景的性能问题之外,可定制性也是很强大的,通过定制我们自己的GsonRequest类,实现了对传输数据无侵入性的JWT安全验证和数据防篡改, 双向自动化的JSON解析能力,极大的简化了Android网络App的开发工作。



你可能感兴趣的:(Android)