spring boot架构设计——权限验证及API接口统一返回格式
Posted Sun_Yang_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring boot架构设计——权限验证及API接口统一返回格式相关的知识,希望对你有一定的参考价值。
昨天奋战了一天才搞定,记录一下。
权限验证
权限验证实现需要截取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) { } }
以上是关于spring boot架构设计——权限验证及API接口统一返回格式的主要内容,如果未能解决你的问题,请参考以下文章
企业快速开发平台Spring Cloud+Spring Boot+Mybatis创业必备企业架构,可开发任意项目
企业快速开发平台Spring Cloud+Spring Boot+Mybatis创业必备企业架构,可开发任意项目
企业快速开发平台Spring Cloud+Spring Boot+Mybatis创业必备企业架构,可开发任意项目
企业快速开发平台Spring Cloud+Spring Boot+Mybatis+ElementUI