问题背景:

后端服务对手机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. 设计模式之java源码-工厂方法模式

    工厂方法模式 8.1 女娲造人的故事 东汉<风俗通>记录了一则神话故事:“开天辟辟,未有人民,女娲搏,黄土作人……”,讲述的内容就是大家非常熟悉的女娲造人的故事.开天辟地之初,大地上并没有 ...

  2. DevExpress TextEdit Focus问题

    在标签切换时设置第一个TextEdit获取输入焦点无效,需要采用消息Post方式设置 //标签切换事件 xtraTabControl1.Selected += (s, e) => { if (e ...

  3. Spring MVC controller 被执行两次

    interceptor 被执行两次 后来发现 时controller被执行两次 后来发现是jsp页面有个: <img src="#" > 导致被执行两次. 解决方案:去 ...

  4. Shell编程-07-Shell中的case语句

    目录 基本语法 case示例 case语句总结     case语句相当于多分支的if/elif/else语句,而在使用case会让脚本看起来更简单工整.在case语句中,程序会将获取到的值与case ...

  5. android中Actionbar详解

    1.什么是Action BarAction Bar被认为是新版Android系统中最重要的交互元素,在程序运行中一直置于顶部,主要起到的作用在于:1)突出显示一些重要操作(如“最新”.“搜索”等)2) ...

  6. hdu 1058

    这道题有很多种做法,但是思路大都是一样的,代码有点类似于poj2591这道题. 题意:问因子只含有2,3,5,7的第k个数是什么? #include<stdio.h> int f[5843 ...

  7. poj2462

    看八戒在做这个题,我也做了做.. 坑很多,还是要注意细节.不得不吐槽,难道又到了计算几何只能套模板否则就一串WA的情况了么! 要不是八戒做出来了,这题我估计我也就扔到这里了..哥不服啊~所以得做出来! ...

  8. Tomcat服务器(一)

    一.tomcat目录中重要的文件: bin 存放启动和关闭的脚本 conf  存放配置文件 logs 日志文件 webapps 存放部署的项目 work 工作目录 Web应用开发好后,若想供外界访问, ...

  9. 更改GeoServer的端口号

    更改GeoServer的端口号,这一问题在不同的GeoServer版本上的解决办法不禁相同.本文记录GeoServer2.7.6(独立安装)版本更改其端口号的办法. GeoServer默认端口为808 ...

  10. Cocos2d-三维拾取Ray-AABB碰撞检测算法【转】

    1.三维拾取技术 在3D游戏中通常会有这样的需求,用户可以选取3D世界中的某些物体进行如拖拽等操作,这时便需要程序通过将二维屏幕上的点坐标转换为三维世界中的坐标,并进行比对,这个过程就需要用到三维拾取 ...