手机端API接口验证及参数签名验证
问题背景:
后端服务对手机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接口验证及参数签名验证的更多相关文章
- api接口验证shal()
就安全来说,所有客户端和服务器端的通信内容应该都要通过加密通道(HTTPS)传输,明文的HTTP通道将会是man-in-the- middle及其各种变种攻击的温床.所谓man-in-the-midd ...
- Swagger解决你手写API接口文档的痛
首先,老规矩,我们在接触新事物的时候, 要对之前学习和了解过的东西做一个总结. 01 痛 苦 不做.不行 之前,前后端分离的系统由前端和后端不同的编写,我们苦逼的后端工程师会把自己已经写完的A ...
- WebApi系列~通过HttpClient来调用Web Api接口~续~实体参数的传递
回到目录 上一讲中介绍了使用HttpClient如何去调用一个标准的Web Api接口,并且我们知道了Post,Put方法只能有一个FromBody参数,再有多个参数时,上讲提到,需要将它封装成一个对 ...
- 分享api接口验证模块
一.前言 权限验证在开发中是经常遇到的,通常也是封装好的模块,如果我们是使用者,通常指需要一个标记特性或者配置一下就可以完成,但实际里面还是有许多东西值得我们去探究.有时候我们也会用一些开源的权限验证 ...
- API接口验证
一.前言 权限验证在开发中是经常遇到的,通常也是封装好的模块,如果我们是使用者,通常指需要一个标记特性或者配置一下就可以完成,但实际里面还是有许多东西值得我们去探究.有时候我们也会用一些开源的权限验证 ...
- ASP.NET MVC API 接口验证
项目中有一个留言消息接口,接收其他系统的留言和展示留言,参考了网上的一些API验证方法,发现使用通用权限管理系统提供的验证方法最完美(http://www.cnblogs.com/jirigala/p ...
- 通过HttpClient来调用Web Api接口~续~实体参数的传递
并且我们知道了Post,Put方法只能有一个FromBody参数,再有多个参数时,上讲提到,需要将它封装成一个对象进行传递,而这讲主要围绕这个话题来说,接口层添加一个新类User_Info,用来进行数 ...
- 通过HttpClient来调用Web Api接口,实体参数的传递
下面定义一个复杂类型对象 public class User_Info { public int Id { get; set; } public string Name { get; set; } p ...
- WebApi系列~通过HttpClient来调用Web Api接口~续~实体参数的传递 【转】
原文:http://www.cnblogs.com/lori/p/4045633.html 下面定义一个复杂类型对象 public class User_Info { public int Id { ...
随机推荐
- 给自己名字abel.这个好,怎么字母排序都第一
给自己名字abel.这个好,怎么字母排序都第一
- Android Activity切换(跳转)时出现黑屏的解决方法
在两个Activity跳转时,由于第二个Activity在启动时加载了较多数据,就会在启动之前出现一个短暂的黑屏时间,解决这个问题比较简单的处理方法是将第二个Activity的主题设置成透明的,这样在 ...
- HDFS系列 -- HDFS预研
1 HDFS概述 由于传统集中式的物理服务器在存储容量和数据传输速度等方面都有限制,故而越来越不符合这些数据的实际存储需要. 在大数据时代,大数据处理需要解决的首要问题是:如何高效地存储所产生的规模庞 ...
- kbmMWEncodeEscapes 中汉字编码的问题及解决办法
kbmMWEncodeEscapes 是kbmmw 里面的一个函数,用来对URL 中的汉字进行编码,例如 http://127.0.0.1/getname?name=春节,由于'春节'是汉字,浏览器向 ...
- 2018.07.22 洛谷P4316 绿豆蛙的归宿(概率dp)
传送门 简单的递推. 由于是DAG" role="presentation" style="position: relative;">DAGDA ...
- 8) pom.xml
http://maven.apache.org/ref/3.3.3/maven-model/maven.html 执行mvn命令的时候默认文件名pom.xml 也可以通过 -f 指定 比如 mvn - ...
- spring boot打包后windows启动乱码
事情的起因什么的就不多表了,直接进入主题... 项目都要上线了,结果发现使用 idea mvn install之后的 jar在windows下启动乱码,而使用idea启动却没有问题!!! 这是神马情况 ...
- Codeforces 706C Hard problem 2016-09-28 19:47 90人阅读 评论(0) 收藏
C. Hard problem time limit per test 1 second memory limit per test 256 megabytes input standard inpu ...
- node API assert
1.assert.throws(block, [error], [message]): assert.throws( function(){ throw new Error('wrong'); }, ...
- 软件工程项目基于java的wc实现
WC软件工程项目JAVA实现博客 github地址:https://github.com/liudaohu/myrepository.git 功能实现 · -w 统计单词数 -c 统计字符数 - ...