问题背景:

后端服务对手机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

 import com.alibaba.fastjson.JSONObject;
import com.woasis.wos.api.UserClaim;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key; public class JwtHandler { //签发者
private static final String ISSUER = "iss";
//签发时间
private static final String ISSUED_AT = "iat";
//过期时间
private static final String EXPIRATION_TIME = "exp";
private static final Long EXPIRATION_TIME_VALUE = 1000*30L;
//JWT ID
private static final String JWT_ID = "jti";
//密钥
private static final String SECRET = "AAAABBBCCC"; /**
* 构造Token
* @param userId 用户ID
* @param userName 用户名称
* @param phone 手机号
* @return
*/
public static String createToken(Integer userId, String userName, String phone) { //采用HS256签名算法对token进行签名
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; //当前系统时间
long nowMillis = System.currentTimeMillis(); //采用密钥对JWT加密签名
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SECRET);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); //构造payload
JSONObject payload = new JSONObject();
payload.put(ISSUER, "ESBP");
payload.put(ISSUED_AT, nowMillis/1000);
payload.put(JWT_ID, userId);
payload.put("account", phone);
payload.put("name",userName);
//设置过期时间
long expMillis = nowMillis + EXPIRATION_TIME_VALUE;
payload.put(EXPIRATION_TIME, expMillis/1000); //设置JWT参数
JwtBuilder builder = Jwts.builder()
.setPayload(payload.toJSONString())
.signWith(signatureAlgorithm, signingKey);
//构造token字符串
return builder.compact();
}
}

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

手机端API接口验证及参数签名验证的更多相关文章

  1. api接口验证shal()

    就安全来说,所有客户端和服务器端的通信内容应该都要通过加密通道(HTTPS)传输,明文的HTTP通道将会是man-in-the- middle及其各种变种攻击的温床.所谓man-in-the-midd ...

  2. Swagger解决你手写API接口文档的痛

    首先,老规矩,我们在接触新事物的时候, 要对之前学习和了解过的东西做一个总结. 01 痛     苦 不做.不行 之前,前后端分离的系统由前端和后端不同的编写,我们苦逼的后端工程师会把自己已经写完的A ...

  3. WebApi系列~通过HttpClient来调用Web Api接口~续~实体参数的传递

    回到目录 上一讲中介绍了使用HttpClient如何去调用一个标准的Web Api接口,并且我们知道了Post,Put方法只能有一个FromBody参数,再有多个参数时,上讲提到,需要将它封装成一个对 ...

  4. 分享api接口验证模块

    一.前言 权限验证在开发中是经常遇到的,通常也是封装好的模块,如果我们是使用者,通常指需要一个标记特性或者配置一下就可以完成,但实际里面还是有许多东西值得我们去探究.有时候我们也会用一些开源的权限验证 ...

  5. API接口验证

    一.前言 权限验证在开发中是经常遇到的,通常也是封装好的模块,如果我们是使用者,通常指需要一个标记特性或者配置一下就可以完成,但实际里面还是有许多东西值得我们去探究.有时候我们也会用一些开源的权限验证 ...

  6. ASP.NET MVC API 接口验证

    项目中有一个留言消息接口,接收其他系统的留言和展示留言,参考了网上的一些API验证方法,发现使用通用权限管理系统提供的验证方法最完美(http://www.cnblogs.com/jirigala/p ...

  7. 通过HttpClient来调用Web Api接口~续~实体参数的传递

    并且我们知道了Post,Put方法只能有一个FromBody参数,再有多个参数时,上讲提到,需要将它封装成一个对象进行传递,而这讲主要围绕这个话题来说,接口层添加一个新类User_Info,用来进行数 ...

  8. 通过HttpClient来调用Web Api接口,实体参数的传递

    下面定义一个复杂类型对象 public class User_Info { public int Id { get; set; } public string Name { get; set; } p ...

  9. WebApi系列~通过HttpClient来调用Web Api接口~续~实体参数的传递 【转】

    原文:http://www.cnblogs.com/lori/p/4045633.html 下面定义一个复杂类型对象 public class User_Info { public int Id { ...

随机推荐

  1. xxnet 360浏览器设置

    开xxnet全局pac只能代理. 然后选择360浏览器里面使用ie代理设置就行

  2. form 表单添加 enctype ="multipart/form-data" 属性后后台接收中文乱码

    解决办法: new String( request.getParameter("title").getBytes("ISO-8859-1"),"utf ...

  3. css3新增功能

    CSS3新增功能 1 CSS3选择器详解 1.1 基础选择器 通配选择器* 元素选择器E ID选择器#id CLASS选择器.class 群组选择器select1,selectN 1.2 层次选择器 ...

  4. sqlserver将数据库的数据导成excel文档方法

    sqlserver将数据库的数据导成excel文档方法 最近公司需要下载uniport的数据跟之前的数据进行对比,所以避免不了需要将数据库的数据导出来,把SQLServer表中的数据导出为Excel文 ...

  5. 进度条ProgressBar

    在本节中,作者只写出了进度条的各种样式,包括圆形.条形,还有自定义的条形,我想如果能让条形进度条走满后再继续从零开始,于是我加入了一个条件语句.作者的代码中需要学习的是handler在主线程和子线程中 ...

  6. centos7安装 docker

    centos7安装 docker 切换到 root用户,执行: [root@localhost frinder]# yum install docker已加载插件:fastestmirror, lan ...

  7. (线段树)Just a Hook -- hdu -- 1689

    链接: http://acm.hdu.edu.cn/showproblem.php?pid=1698 思路: 我的想法很简单,像上一题一样从后面向前面来算,前面已经覆盖的,后面自然不能再来计算了,具体 ...

  8. MySQL 分表和分区

    1.为什么需要分表和分区 在开发的过程中,经常会遇到大表的情况,所谓的大表是指存储了百万级乃至千万级条记录的表.这样的表过于庞大,导致数据库在查询和插入的时候耗时太长,如果涉及联合查询的情况,性能更加 ...

  9. Scala类型检查与转换

    Scala类型检查与转换 isInstanceOf:检查某个对象是否属于某个给定的类. asInstanceOf:将引用转换为子类的引用. classOf:如果想测试p指向的是一个Employee对象 ...

  10. Java内存模型(一)

    主存储器和工作存储器 Java虚拟机在执行Java程序的过程中会把它管理的内存划分为若干个不同的数据区域,这些区域包括方法区,堆,虚拟机栈,本地方法栈,程序计数器.方法区存储类信息,常量,字节码等数据 ...