API接口安全

token被劫持,DOS攻击,重复提交等,接口安全尤为重要,接口安全主要围绕Token、Timestamp和Sign三个机制展开设计,保证接口的数据不会被篡改和重复调用。

Token授权

token是客户端访问服务端的凭证。用户通过账号密码登录后,服务器返回token给客户端,并存储在缓存中,服务器接受到请求后进行token验证,如果token不存在说明未登录,

时间戳超时

时间戳超时机制是防御DOS攻击的有效手段。每次请求都带上当前时间的时间戳timestamp,服务端接收到timestamp后跟当前时间进行比对,如果时间差大于一定时间(比如5秒钟),则认为该请求失效。

签名

签名机制保证了数据不会被篡改。将Token和时间戳加上其他请求参数再用MD5或其他算法加密,加密后的数据就是本次请求的签名sign,服务端接收到请求后以同样的算法得到签名,并跟当前的签名进行比对,如果不一致,说明参数被更改过,直接返回错误。

IP限制

通过IP解密,防止token被截取后在别的网络环境发出请求,在服务器通过请求ip与这个ip必须一致才能解密

拒绝重复调用

客户端第一次访问时,将签名sign存放到缓存中,超时时间设定为5秒,则5秒内只能访问一次。如果使用同一个URL再次访问,发现缓存中已经存在了本次签名,则拒绝服务。
在这里插入图片描述

  1. 客户端通过用户名密码登录服务器并获取Token
import com.example.user.cache.CacheUtil;
import com.example.user.result.Result;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@RestController
public class UserController {

    @GetMapping("login")
    public Result login(@RequestParam String username, @RequestParam String password){
        String token = null;
        if("zhangshan".equals(username) && "123456".equals(password)){
            token = UUID.randomUUID().toString().replaceAll("-","");
            CacheUtil.newNoCache().put(token,username);
        }
        return Result.success(token);
    }
    
	@GetMapping("users/{id}")
    public Result users(@PathVariable String id){
        Map user = new HashMap();
        if("1".equals(id)){
            user.put("username","zhangsan");
            user.put("password","123456");
            user.put("sex","man");
        }
        return Result.success(user);
    }
}
  1. 客户端生成时间戳timestamp,并将timestamp作为其中一个参数

  2. 客户端将Token和timestamp按照自己的算法(这里使用MD5)进行排序加密得到签名sign

  3. 将token、timestamp和sign作为请求时必须携带的参数加在每个请求的URL后边(http://localhost/users/1?token=334d1e48834b494daef3657dfac47af7&timestamp=1618815569248&sign=30819c9c1504795b74a198e0f35b3789)

  4. 服务端写一个过滤器对token、timestamp和sign进行验证,只有在token有效、timestamp未超时、缓存服务器中不存在sign三种情况同时满足,本次请求才有效

import com.alibaba.fastjson.JSON;
import com.example.user.cache.CacheUtil;
import com.example.user.crypto.SignUtil;
import com.example.user.result.CodeMsg;
import com.example.user.result.Result;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;

/**
 * 登录过滤器
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getParameter("token");
        String timestamp = request.getParameter("timestamp");
        String sign = request.getParameter("sign");
        String uri = request.getRequestURI();
        if(null == token || null == timestamp || null == sign){
            render(response,CodeMsg.UNAUTHORIZED);
            return false;
        }
        //验证token
        if(null == CacheUtil.newNoCache().get(token)){
            render(response,CodeMsg.TOKEN_ERROR);
            return false;
        }
        //校验超时
        long currentTime = System.currentTimeMillis();
        long timeOut = currentTime-Long.valueOf(timestamp);
        if(timeOut > 5000 || timeOut < 0){
            render(response,CodeMsg.REQ_TIMEOUT);
            return false;
        }
        //验证签明
        String data = token+timestamp;
        if(!SignUtil.verify(data, sign)){
            render(response,CodeMsg.SIGN_ERROR);
            return false;
        }
        //防止重复提交
        if(null != CacheUtil.newNoCache().get(token+uri)){
            render(response,CodeMsg.DUPLICATE_SUBMISSION);
            return false;
        }else {
            CacheUtil.newNoCache().put(token+uri,"1",10L);
        }
        return true;
    }

    private void render(HttpServletResponse response, CodeMsg cm) throws Exception {
        response.setContentType("application/json;charset=UTF-8");
        OutputStream out = response.getOutputStream();
        String str  = JSON.toJSONString(Result.codeMsg(cm));
        out.write(str.getBytes("UTF-8"));
        out.flush();
        out.close();
    }
}

注册拦截器

import com.example.user.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {

    @Bean
    public LoginInterceptor getLoginInterceptor() {
        return new LoginInterceptor();
    }

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getLoginInterceptor()).addPathPatterns("/users/*");
        super.addInterceptors(registry);
    }

}

简化版签名工具类

import org.springframework.util.DigestUtils;

public class SignUtil {

    /**
     * 签名
     * @param data
     * @return
     */
    public static String sign(String data){
        String signed = DigestUtils.md5DigestAsHex(data.getBytes());
        return signed;
    }

    /**
     * 验证签名
     * @param data  元数据
     * @param signed    签名数据
     * @return
     */
    public static boolean verify(String data,String signed){
        String sign = SignUtil.sign(data);
        return sign.equals(signed);
    }

}

简化版统一返回

public enum CodeMsg {

    SUCCESS(200, "成功"),
    ERROR(500, "服务器异常"),
    UNAUTHORIZED(401,"您还未登录"),
    TOKEN_ERROR(401,"token校验失败"),
    SIGN_ERROR(401,"签名认证失败"),
    REQ_TIMEOUT(403,"请求超时"),
    DUPLICATE_SUBMISSION(403,"重复提交");

    private int code;

    private String msg;

    CodeMsg(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

}
public class Result<T> {

    private int code;

    private String msg;

    private T data;

    private Result() {
    }

    protected Result(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    protected Result(CodeMsg cm) {
        this.code = cm.getCode();
        this.msg = cm.getMsg();
    }

    protected Result(CodeMsg cm, T data) {
        this.code = cm.getCode();
        this.msg = cm.getMsg();
        this.data = data;
    }

    public static <T> Result success(T data) {
        return new Result(CodeMsg.SUCCESS, data);
    }

    public static Result codeMsg(CodeMsg cm) {
        return new Result(cm);
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值