spring boot架構設計——權限驗證及API接口統一返回格式

昨天奮戰了一天才搞定,記錄一下。spring

 

權限驗證json

權限驗證明現須要截取request參數,這個實現很簡單,springboot中可使用interceptor,Aspect,filter實現.具體實現網上一大把,就懶得寫了,關鍵字搜就是。
api

經過request獲取到請求參數後,按照本身定義的規則計算出sign值,例如把token+timestamp+邏輯方法參數字典排序後md5+base64位,而後和客戶端傳過來的sign值對比。springboot

API接口統一返回格式服務器

這個纔是折騰人的玩意,在net下實現的時候以爲挺簡單的,在spring boot下作就要了老命了。app

統一返回格式的目的是把邏輯方法和系統返回格式解耦合。例如API接口返回格式以下:ide

 

//權限驗證失敗返回
{
    "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百度了一個下午,最後放棄。ui

而後選擇Aspect方式,利用round註解,很輕鬆的就拿到了返回值,一切看上去很順利,結果在修改返回值時,報錯了。。。由於產生響應時,Aspect執行順序在servlet前面,即邏輯方法返回model,而後Aspect執行,而後servlet再執行,而servlet默認會根據邏輯方法的返回值來對返回值進行序列化,這時候由於在Aspect中,我已經修改了返回值類型,因而就會出現類型轉換錯誤。Aspect達不到目標,也宣告放棄。this

最後看到外網一位網友建議使用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) {

    }
}
相關文章
相關標籤/搜索