这段时间做新的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<T> { 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 <T> */ public class GsonRequest<T> extends Request<T> { private static final String TAG = "GsonRequest"; private final Listener<T> mListener; private Gson mGson; private Class<T> mClass; private Object mRequest; private String mBody; public GsonRequest(int method, String url, Object request, Class<T> clazz, Listener<T> 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<T> clazz, Listener<T> 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<T> 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<String, String> getHeaders() throws AuthFailureError { HashMap<String, String> 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 + <span style="font-family: Arial, Helvetica, sans-serif;">AppConfig.SIGN_SECRET </span><span style="font-family: Arial, Helvetica, sans-serif;">));</span> } //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<LoginResponse>( AppConfig.SERVER_URL + "/login",loginRequest, LoginResponse.class, new Response.Listener<LoginResponse>() { @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"); } }
版权声明:本文为博主原创文章,转载请保留出处http://blog.csdn.net/offbye
基于Volley,Gson封装支持JWT无状态安全验证和数据防篡改的GsonRequest网络请求类
原文地址:http://blog.csdn.net/offbye/article/details/47750175