手机端API接口验证及参数签名验证

匿名 (未验证) 提交于 2019-12-02 21:53:52

问题背景:

后端服务对手机APP端开放API,没有基本的校验就是裸奔,别人抓取接口后容易恶意请求,不要求严格的做的安全,但是简单的基础安全屏障是要建立的,再配合HTTPS使用,这样使后端服务尽可能的安全。

对接口安全问题,采用JWT对接口进行token验证,判断请求的有效性,目前对JWT解释的博客文章很多,对JWT不了解的可以查找相关资料,JWT官网

JWT是JSON Web Token的简写,一些是JWT官网的解释:


 

什么是JWT?

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

看不懂的可以用Google翻译:

JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方式,可以在各方之间作为JSON对象安全地传输信息。 此信息可以通过数字签名进行验证和信任。 JWT可以使用密钥(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥进行签名。

JWT的结构是怎样的?

JWT主要由三部分构成,

  • Header  头部,说明使用JWT的类型,和使用的算法
  • Payload  中间体,定义的一些有效数据,比如签发者,签发时间,过期时间等等,具体可查看RFC7519,除了一些公共的属性外,可以定义一些私有属性,用于自己的业务逻辑。
  • Signature  签名,创建签名,base64UrlEncode对header和Payload进行处理后,再根据密钥和头部中定义的算法进行签名。如下格式:
HMACSHA256(   base64UrlEncode(header) + "." +   base64UrlEncode(payload),   secret)
//生成的Token如下样式eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJFU0JQIiwibmFtZSI6IuWImOWFhuS8nyIsImV4cCI6MTUzMTQ0OTExNSwiaWF0IjoxNTMxNDQ5MDg1LCJqdGkiOjEsImFjY291bnQiOiIxNTAwMTEwMTUzNiJ9.4IEi95xcOQ4SfXvjz34bBC8ECej56jiMuq7Df4Vd9YQ

 


 

具体实现:

1. maven构建,可以查看Github

        <dependency>             <groupId>io.jsonwebtoken</groupId>             <artifactId>jjwt</artifactId>             <version>0.9.1</version>         </dependency>

2. 创建Token

 1 import com.alibaba.fastjson.JSONObject;  2 import com.woasis.wos.api.UserClaim;  3 import io.jsonwebtoken.Claims;  4 import io.jsonwebtoken.JwtBuilder;  5 import io.jsonwebtoken.Jwts;  6 import io.jsonwebtoken.SignatureAlgorithm;  7   8 import javax.crypto.spec.SecretKeySpec;  9 import javax.xml.bind.DatatypeConverter; 10 import java.security.Key; 11  12 public class JwtHandler { 13  14     //签发者 15     private static final String ISSUER = "iss"; 16     //签发时间 17     private static final String ISSUED_AT = "iat"; 18     //过期时间 19     private static final String EXPIRATION_TIME = "exp"; 20     private static final Long EXPIRATION_TIME_VALUE = 1000*30L; 21     //JWT ID 22     private static final String JWT_ID = "jti"; 23     //密钥 24     private static final String SECRET = "AAAABBBCCC"; 25  26     /** 27      * 构造Token 28      * @param userId 用户ID 29      * @param userName  用户名称 30      * @param phone  手机号 31      * @return 32      */ 33     public static String createToken(Integer userId, String userName, String phone) { 34  35         //采用HS256签名算法对token进行签名 36         SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; 37  38         //当前系统时间 39         long nowMillis = System.currentTimeMillis(); 40  41         //采用密钥对JWT加密签名 42         byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SECRET); 43         Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); 44  45         //构造payload 46         JSONObject payload = new JSONObject(); 47         payload.put(ISSUER, "ESBP"); 48         payload.put(ISSUED_AT, nowMillis/1000); 49         payload.put(JWT_ID, userId); 50         payload.put("account", phone); 51         payload.put("name",userName); 52         //设置过期时间 53         long expMillis = nowMillis + EXPIRATION_TIME_VALUE; 54         payload.put(EXPIRATION_TIME, expMillis/1000); 55  56         //设置JWT参数 57         JwtBuilder builder = Jwts.builder() 58                 .setPayload(payload.toJSONString()) 59                 .signWith(signatureAlgorithm, signingKey); 60         //构造token字符串 61         return builder.compact(); 62     } 63 }

3. 解析JWT

    private static Logger logger = LoggerFactory.getLogger(JwtHandler.class);      /**      * JWT解析      * @param jwt      * @return      */     public static UserClaim parseJWT(String jwt) {         Claims claims = Jwts.parser()                 .setSigningKey(DatatypeConverter.parseBase64Binary(SECRET))                 .setAllowedClockSkewSeconds(100) //设置允许过期市场,在构造token的时候有设置过期时间,此处是指到了过期时间之后还允许多少秒次token可以解析                 .parseClaimsJws(jwt).getBody();          UserClaim userClaim = new UserClaim();         userClaim.setAccount((String) claims.get("account"));         userClaim.setName((String) claims.get("name"));         userClaim.setJti(claims.getId());         userClaim.setIss(claims.getIssuer());         userClaim.setIat(claims.getIssuedAt());         userClaim.setExp(claims.getExpiration());         logger.debug("parseJWT UserClaim:"+JSONObject.toJSONString(userClaim));         return userClaim;     }

特别说明:

在jjwt源码文件JwtMap.java中有这么个方法toDate(),在解析数据的时候这个地方按秒对时间处理的,所以在设置签发时间或过期时间的时候要设置秒。

 protected static Date toDate(Object v, String name) {         if (v == null) {             return null;         } else if (v instanceof Date) {             return (Date) v;         } else if (v instanceof Number) {             // https://github.com/jwtk/jjwt/issues/122:             // The JWT RFC *mandates* NumericDate values are represented as seconds.             // Because Because java.util.Date requires milliseconds, we need to multiply by 1000:             long seconds = ((Number) v).longValue();             long millis = seconds * 1000;             return new Date(millis);         } else if (v instanceof String) {             // https://github.com/jwtk/jjwt/issues/122             // The JWT RFC *mandates* NumericDate values are represented as seconds.             // Because Because java.util.Date requires milliseconds, we need to multiply by 1000:             long seconds = Long.parseLong((String) v);             long millis = seconds * 1000;             return new Date(millis);         } else {             throw new IllegalStateException("Cannot convert '" + name + "' value [" + v + "] to Date instance.");         }     }

4. 拦截器使用

要想对api进行控制,就要使用拦截器,或是过滤器,提问:拦截器和过滤器的区别是什么?此处采用拦截器进行控制。

拦截器具体实现代码:

import com.woasis.wos.api.UserClaim; import com.woasis.wos.common.exception.ExceptionEnum; import com.woasis.wos.common.exception.WosException; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.SignatureException; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView;  import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;  /**  * Token验证拦截器  */ public class TokenInterceptor implements HandlerInterceptor {      private static Logger logger = LoggerFactory.getLogger(TokenInterceptor.class);      @Override     public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {          logger.debug("path:"+httpServletRequest.getRequestURI());         String token = httpServletRequest.getParameter("token");         String userId = httpServletRequest.getParameter("id");          if (!StringUtils.isBlank(token)){             UserClaim claim = null;             try {                 claim = JwtHandler.parseJWT(token);             }catch (ExpiredJwtException e){//token过期                 throw new WosException(ExceptionEnum.EXPIRATION_TIME);             }catch (SignatureException e){//签名被篡改                 throw new WosException(ExceptionEnum.SIGNATUREEXCEPTION);             }             if (claim != null && userId != null){                 if (userId.equals(claim.getJti())){                      return true;                 }else {//token用户非请求用户,非法请求                     throw new WosException(ExceptionEnum.ILLEGAL_REQUEST);                 }             }else {                 throw new WosException(ExceptionEnum.ILLEGAL_REQUEST);             }         }else {//token为空,非法请求             throw new WosException(ExceptionEnum.ILLEGAL_REQUEST);         }     }      @Override     public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {      }      @Override     public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {      } }

在Spring Boot中拦截器的使用:

import com.woasis.wos.api.util.TokenInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;  @Configuration public class WosAppConfigurer extends WebMvcConfigurerAdapter {      //排除拦截的请求路径     private static String[] excludePatterns = new String[]{"/oauth/login"};      @Override     public void addInterceptors(InterceptorRegistry registry) {          registry.addInterceptor(new TokenInterceptor()).addPathPatterns("/**").excludePathPatterns(excludePatterns);          super.addInterceptors(registry);      } }

5. 效果测试

模拟获取token

模拟token过期

模拟token中签名被篡改


参数签名://TODO

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!