问题背景:

后端服务对手机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. WCF TOOL CODE

    .HTML <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WCFTool. ...

  2. 【commons-io】File对文件与目录的处理&FileUtis,IOUtils,FilenameUtils工具的使用

    -------------------File的使用-------------- 1.File类对文件的处理 1.1目录结构:  1.2测试对文件Test.txt处理: // 测试文件 @Test p ...

  3. Duplicate entry '1' for key 'PRIMARY'

    这个bug产生在你建立主键的时候,就是说主键的id重复了,有两个同名的id,需要删除一个,才能满足主键的唯一性

  4. SpringMVC零碎笔记

    在web.xml里可以配置webapp的默认首页,格式如下: <welcome-file-list> <welcome-file>index.html</welcome- ...

  5. java编程规范-阿里

  6. Spring3.x错误----Bean named "txAdvice" must be of type[org.aopallibance.aop.Advice

    Spring3.x错误: 解决方法: aopalliance-1.0.jar 和 aopalliance-alpha1.jar之间的冲突.

  7. Chat Server's Outgoing Traffic

    http://acm.hust.edu.cn/vjudge/contest/view.action?cid=93359#problem/A (456321) http://codeforces.com ...

  8. 使用Octopress博客 搭建博客

    Octopress介绍 Octopress是一款优秀的静态化博客系统,也是一个本地化的博客系统,之前部落在介绍免费开源Github Pages空间时有提到过Octopress,Github为我们提供长 ...

  9. HDU6023 Automatic Judge 2017-05-07 18:30 73人阅读 评论(0) 收藏

    Automatic Judge Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others ...

  10. 动态类型和匿名类型在asp.net webapi中的应用

    1.  动态类型用于webapi调用 假设需要调用一个webapi,webapi返回了一个json字符串.字符串如下: {"ProductId":"AN002501&qu ...