您的位置:首页 > 编程语言 > Java开发

spring boot架构设计——权限验证及API接口统一返回格式

2017-10-20 17:06 1006 查看
昨天奋战了一天才搞定,记录一下。

权限验证

权限验证实现需要截取request参数,这个实现很简单,springboot中可以使用interceptor,Aspect,filter实现.具体实现网上一大把,就懒得写了,关键字搜就是。

通过request获取到请求参数后,按照自己定义的规则计算出sign值,例如把token+timestamp+逻辑方法参数字典排序后md5+base64位,然后和客户端传过来的sign值对比。

API接口统一返回格式

这个才是折腾人的玩意,在net下实现的时候觉得挺简单的,在spring boot下做就要了老命了。

统一返回格式的目的是把逻辑方法和系统返回格式解耦合。例如API接口返回格式如下:

//权限验证失败返回
{
"data": null,
"code": 1,
"msg": "服务器权限验证失败"
}


//发生未处理异常事件的返回
{
"data": null,
"code": -1,
"msg": "服务器异常,请稍后再试"
}


//请求成功返回
{
"data": {
"id": 1,
"userid": "test1",
"name": "孙杨",
"phone": "183*******",
"sourcetype": 1,
"loginname": "test1",
"loginpwd": "test1",
"powertype": 1,
"registertime": "2017-10-19"
},
"code": 10000,
"msg": ""
}


这种格式由2部分组成,第一部分就是最外层的data,code,msg。我称之为系统级返回参数,data里面的是方法级返回参数。处理逻辑是捕获逻辑方法返回值,然后转换为标准格式返回给客户端。

spring boot的问题是interceptor不能获取到返回值,restAPI方式的请求,ModelAndView这个参数返回的是null,也没法从response参数里面取出返回值。google百度了一个下午,最后放弃。

然后选择Aspect方式,利用round注解,很轻松的就拿到了返回值,一切看上去很顺利,结果在修改返回值时,报错了。。。因为产生响应时,Aspect执行顺序在servlet前面,即逻辑方法返回model,然后Aspect执行,然后servlet再执行,而servlet默认会根据逻辑方法的返回值来对返回值进行序列化,这时候因为在Aspect中,我已经修改了返回值类型,于是就会出现类型转换错误。Aspect达不到目标,也宣告放弃。

最后看到外网一位网友建议使用filter。filter执行顺序在servlet之后,试了下,ok了,能捕获,能修改。不过还有个问题,因为是在servlet之后执行,而servlet默认是会把逻辑方法的返回值序列化的。。。于是data参数里面装的就是一个字符串,然后filter返回的时候再序列化一次,data里面的数据格式就惨不忍睹了,客户端还不骂死啊。夜深人懒,不想再累了,就直接把获取到的逻辑方法的json字符串反序列化为object,然后装配到data中,然后再把filter的参数序列化。。。最后接口返回的效果就是目前看的样子,效率低,达到最低效果要求,日后有空再优化吧。。。。。。下面贴下代码

过滤器代码:

/**
* @author 孙杨
* @date Created in 下午6:01 17/10/19
*/
@WebFilter(filterName = "全局过滤器", urlPatterns = "/*")
public class GlobalFilter implements Filter {

private final String JsonArraySign = "[";
private final boolean DEBUG = true;

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
ResponseModel response = new ResponseModel();
LogUtil logger = new LogUtil();
StringBuilder sb = new StringBuilder();
Gson gson = new GsonBuilder().serializeNulls().create();

//权限校验参数
String token = "";
String timestamp = "";
String path = "";
String sign = "";

HttpServletRequest request = (HttpServletRequest) servletRequest;
String[] paths = request.getRequestURL().toString().split("/api/sx");
if (paths.length > 1) {
path = paths[1];
}

long startTime = System.currentTimeMillis();
sb.append("\n路径: " + request.getRequestURL() + "\n");
sb.append("method: " + request.getMethod() + "\n");
sb.append("QueryString:  " + request.getQueryString() + "\n");
sb.append("请求参数:\n");
Enumeration<String> paras = request.getParameterNames();
while (paras.hasMoreElements()) {
String name = paras.nextElement();
String value = request.getParameter(name);
if ("token".equals(name)) {
token = value;
}
if ("timestamp".equals(name)) {
timestamp = value;
}
if ("sign".equals(name)) {
sign = value;
}
sb.append(name + ":" + value + "\n");
}
if (!DEBUG && !sign.equals(AuthVerificationUtil.countSign(token, timestamp, path))) {
sb.append("方法耗时: " + (System.currentTimeMillis() - startTime) + "毫秒\n");
response.setCode(CodeTable.ACCESSDENIED);
response.setMsg("服务器权限验证失败");
response.setData(null);
sb.append("逻辑错误:" + "服务器权限验证失败: token:" + token +
" timestamp:" + timestamp + " path:" + path
+ " 服务器端sign: " + AuthVerificationUtil.countSign(token, timestamp, path));
logger.error(sb.toString());
} else {
MyResponseWrapper responseWrapper = new MyResponseWrapper((HttpServletResponse) servletResponse);
try {
filterChain.doFilter(servletRequest, responseWrapper);
String responseContent = new String(responseWrapper.getDataStream());
//判断返回值是jsonObject还是jsonArray
if (responseContent.startsWith(JsonArraySign)) {
response.setData(new JsonParser().parse(responseContent).getAsJsonArray());
} else {
response.setData(new JsonParser().parse(responseContent).getAsJsonObject());
}
response.setCode(CodeTable.SUCCESS);
response.setMsg("");
sb.append("方法耗时: " + (System.currentTimeMillis() - startTime) + "毫秒\n");
sb.append("响应参数: " + responseContent);
logger.info(sb.toString());
} catch (Exception e) {
sb.append("方法耗时: " + (System.currentTimeMillis() - startTime) + "毫秒\n");
if (e instanceof MyException) {
response.setCode(((MyException) e).getCode());
response.setMsg(((MyException) e).getMsg());
response.setData(null);
sb.append("逻辑错误:" + ((MyException) e).getLog());
logger.error(sb.toString());
} else {
response.setCode(CodeTable.UNKNOWERROR);
response.setMsg("服务器异常,请稍后再试");
response.setData(null);
sb.append("未捕获异常:" + e.getMessage());
logger.error(sb.toString());
}
}
}
((HttpServletResponse) servletResponse).setHeader("Content-type", "application/json;charset=UTF-8");
servletResponse.getOutputStream().write(gson.toJson(response).getBytes());
}

@Override
public void destroy() {

}
}


逻辑方法类似如下:

@RequestMapping("login")
public User login(LoginModel loginModel) {
User user = userRepository.findByLoginnameAndLoginpwd(loginModel.getLoginname(), loginModel.getLoginpwd());
if (user == null) {
throw new MyException(10001, "用户名或密码不对");
}
return user;
}


折腾了这么多东西,目的就是彻底简化逻辑方法代码难度~定义好接口文档和路径,逻辑代码编写就可以丢给实习生了,又可以偷懒了~~

还有2个辅助类我也贴一下:

public class MyResponseWrapper extends HttpServletResponseWrapper {
ByteArrayOutputStream output;
FilterServletOutputStream filterOutput;

public MyResponseWrapper(HttpServletResponse response) {
super(response);
output = new ByteArrayOutputStream();
}

@Override
public ServletOutputStream getOutputStream() throws IOException {
if (filterOutput == null) {
filterOutput = new FilterServletOutputStream(output);
}
return filterOutput;
}

public byte[] getDataStream() {
return output.toByteArray();
}
}


public class FilterServletOutputStream extends ServletOutputStream {
DataOutputStream output;

public FilterServletOutputStream(OutputStream output) {
this.output = new DataOutputStream(output);
}

@Override
public void write(int arg0) throws IOException {
output.write(arg0);
}

@Override
public void write(byte[] arg0, int arg1, int arg2) throws IOException {
output.write(arg0, arg1, arg2);
}

@Override
public void write(byte[] arg0) throws IOException {
output.write(arg0);
}

@Override
public boolean isReady() {
return false;
}

@Override
public void setWriteListener(WriteListener writeListener) {

}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐