使用JWT确保API的安全

未经安全保护的API很是的危险

未经安全保护的API很是的危险,其和裸奔无异。即便API文档没有被人为泄露,经过简单的抓包也能够很是容易的获取到API的URL以及对应的请求参数。下面举几个未经保护的API可能将会形成的安全事故:java

  1. 经过抓包,找到发送短信验证码的API。而后利用该API恶意的发送短信验证码。而发送短信验证码是须要收费的,这样的恶意攻击,将会致使无端的损失发送短信的费用。还会让不少不明真相的吃瓜群众收到奇怪的短信验证码,进而对产品产生很差的印象。git

  2. 经过抓包,找到获取用户信息的API。该API的暴露,将会让该平台上全部用户的信息被毫无保留的暴露在互联网上。若是用户信息中还涉及到一些重要的我的信息。好比身份证号,手机号等。将会让用户受到人生和财产的损失。github

  3. 经过抓包,找到和财产相关的API。好比说修改订单状态的API。一旦该API暴露,黑客能够恶意的修改用户的订单状态。好比说将订单的状态修改成取消。这可能将会直接对用户形成财产损失。web

上面这些例子仅仅只是笔者临时想到的一些利用裸露API进行恶意攻击的方式,实际中还存在着更多因为未对API进行加密,而形成损失的状况。所以,未经安全保护的API很是的危险,对API进行安全保护异常重要。算法

什么是JWT

JWT是json web token的缩写。关于其如何确保数据传输的安全性的文章你能够在搜索引擎上找到不少,在这里我将仅仅简单介绍我对此的理解。spring

  1. JWT能够理解为一串经过特定算法生成的字符串,在API的请求中,将这段字符串放入请求参数中。API Server经过判断这段字符串是合法的仍是伪造的,来肯定此次API请求是否有效。经过该安全措施,将确保即便API被暴露,没有生成JWT字符串的算法,也没有办法成功调用API。json

  2. JWT字符串分为两个部分(官方的说法是分为3个部分),分别是‘未加密部分’和‘加密部分’。而‘加密部分’的内容其实是‘未加密部分’加密获得的。API Server检查JWT字符串是否有效的第一步是将‘加密部分’解密而后与‘未加密部分’进行比较,查看是否内容一致。若是内容不一致,则说明该JWT字符串是伪造的。安全

  3. JWT字符串中包括一个‘过时时间’的字段,当API Server获取到JWT字符串后,能够经过检查该字段与当前时间相比,是否已经处于过时的状态。若是‘过时时间’字段早于当前时间,则说明此次API请求是无效的。gradle

  4. 你也能够在JWT字段种加入自定义的字段。而后在API Server获取到JWT字段后,经过这些自定义的字段判断是否是符合具体的业务逻辑,进而判断此次请求是否是有效。ui

利用jjwt实现JWT对API的保护

jjwtjava对JWT的封装,下面的方法将会演示。在java中如何利用jjwt实现API的保护

gradle依赖

compile 'io.jsonwebtoken:jjwt:0.7.0'

生成JWT字符串

public String buildJwt(Date exp) {
    String jwt = Jwts.builder()
            .signWith(SignatureAlgorithm.HS256,SECRET_KEY)//SECRET_KEY是加密算法对应的密钥,这里使用额是HS256加密算法
            .setExpiration(exp)//expTime是过时时间
            .claim("key","vaule")//该方法是在JWT中加入值为vaule的key字段
            .compact();
    return jwt;
}

判断JWT是否有效

public boolean isJwtValid(String jwt) {
    try {
        //解析JWT字符串中的数据,并进行最基础的验证
        Claims claims = Jwts.parser()
                .setSigningKey(SECRET_KEY)//SECRET_KEY是加密算法对应的密钥,jjwt能够自动判断机密算法
                .parseClaimsJws(jwt)//jwt是JWT字符串
                .getBody();
        String vaule = claims.get("key", String.class);//获取自定义字段key
        //判断自定义字段是否正确
        if ("vaule".equals(vaule)) {
            return true;
        } else {
            return false;
        }
    }
    //在解析JWT字符串时,若是密钥不正确,将会解析失败,抛出SignatureException异常,说明该JWT字符串是伪造的
    //在解析JWT字符串时,若是‘过时时间字段’已经早于当前时间,将会抛出ExpiredJwtException异常,说明本次请求已经失效
    catch (SignatureException|ExpiredJwtException e) {
        return false;
    }
}

Client端

Client端须要作的就是,根据API的需求将JWT字符串放入http请求中。个人作法是对于全部的API,在Client端生成JWT字段,而后将其添加到http请求的header中,确保全部的API都得到保护。对于一些比较敏感的信息,再用加一层JWT验证。好比说用户信息,在调用登陆API后,API Server将会返回一个特定的JWT字符串,该JWT字段总将会包含该用户的userId。若是要获取用户信息,除了要将Client端生成的JWT字段放入请求,还须要将该JWT字符串放入请求。接下来展现一下利用OKHttp在http请求的header中加入JWT字段的代码:

//该方法将会在全部请求的header中加入jwt
public Response call(Request request) throws IOException {
    OkHttpClient client = new OkHttpClient();
    Request.Builder requestBuilder = request.newBuilder()
            .addHeader("commonJwt", jwtService.makeJwt());//加入Client本地生成的JWT字符串
    //加入登陆成功后获取到的JWT字符串
    String userJwt = jwtService.getUserJwt();
    if (!StringUtils.isSpace(userJwt))
        requestBuilder.addHeader("userJwt", userJwt);
    request = requestBuilder.build();
    return client.newCall(request).execute();
}

API Server

API Server端须要作的就是,在收到API请求时,首先检查client端生成的JWT字段是否有效,而后若是该API涉及敏感信息,则检查检测特定的JWT字段是否有效。接下来展现一下在spring中利用aop进行JWT字段的验证:

@Pointcut("@annotation(org.springframework.web.bind.annotation.ResponseBody)")
public void onCommonAuth(){}

//全部的API都须要验证client生成的JWT字段是否有效
@Order(1)
@Around("onCommonAuth()")
public Object onCommonAuth(ProceedingJoinPoint joinPoint) throws Throwable {
    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    String commonJwt = request.getHeader("commonJwt");
    if (jwtService.isCommonJwtValid(commonJwt)) {
        return joinPoint.proceed();
    } else {
        return "没有访问该API的权限";
    }
}

@Pointcut("execution(* com.demo.controller.UserController.getUserInfo(..))")
public void onGetByUserInfo() {}

//对获取用户信息API,坚持userJwt是否有效
@Order(2)
@Around("onGetByUserInfo()&&args(userId,..)")
public Object onGetByUserInfo(ProceedingJoinPoint joinPoint, Long userId) throws Throwable {
    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    String userJwt = request.getHeader("userJwt");
    if (jwtService.isUserJwtValid(userJwt, userId)) {
        return joinPoint.proceed();
    } else {
        return "没有访问该API的权限";
    }
}

补充

  • JWT在必定程度上,保护了API的安全。可是其自己仍是存在必定的缺陷的。好比说,必定JWT的加密密钥一旦被泄露,那么黑客就能够生成JWT字符串了,所以保护好JWT加密密钥很是重要。

  • 在上面的例子当中,介绍了获取用户信息API须要加入userJwt的例子。userJwt其实就是在JWT字符串中加入了userId字段,继而保证一个userJwt只能访问一个用户的信息。对于其余的API,好比说PUT和POST操做,须要新增和修改数据的API。能够将请求参数一并放入jwt中,以此来确保数据的安全性。不然黑客还能够在JWT字符串尚未过时的时间段内,修改请求中的参数,达到攻击的目的。

  • 另外还要防止重复式攻击,黑客还能够在JWT字符串尚未过时的时间段内,重复提交请求,达到攻击的目的。好比说新增订单的API,若是被黑客采用重复式攻击的方式,就会生成多个订单。

参考

RESTful Api 身份认证中的安全性设计探讨
JSON Web Token - 在Web应用间安全地传递信息
八幅漫画理解使用JSON Web Token设计单点登陆系统
How to Create and verify JWTs in Java
jwt官方介绍
jwt官网
jjwt在github上的地址

相关文章
相关标签/搜索