手机端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 { ...
随机推荐
- 关于IBatisNet的配置文件中数据库连接字符串加密处理
我们通常在IBatisNet配置文件 properties.config 加入数据库连接字符串.数据库连接字符串直接放在里面,没有被加密,很不安全.如果我们把 properties.config 文件 ...
- SVN中检出 和 导出 的区别
SVN中检出 和 导出 的区别:检出得到的文件夹中,是受SVN客户端控制的,对其进行文件或文件夹的增删改操作都会被SVN客户端识别出来,对其可以进行update.commit操作.其中含有.svn隐藏 ...
- 2018.06.29 NOIP模拟 旅馆(线段树)
旅馆 [问题描述] OIEROIEROIER 们最近的旅游计划,是到长春净月潭,享受那里的湖光山色,以及明 媚的阳光.你作为整个旅游的策划者和负责人,选择在潭边的一家著名的旅馆住 宿.这个巨大的旅馆一 ...
- 2018.09.11 bzoj3629: [JLOI2014]聪明的燕姿(搜索)
传送门 一道神奇的搜索. 直接枚举每个质因数的次数,然后搜索就行了. 显然质因数k次数不超过logkn" role="presentation" style=" ...
- 2018.07.07 洛谷 P3939 数颜色(主席树)
P3939 数颜色 题目背景 大样例下发链接:http://pan.baidu.com/s/1c0LbQ2 密码:jigg 题目描述 小 C 的兔子不是雪白的,而是五彩缤纷的.每只兔子都有一种颜色,不 ...
- Netty学习第二节Java IO通信
一.Java IO通信 名词解释: BIO通信: 采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端连接,在接收到客户端请求后,为每一个客户端建立一个新的线程负 ...
- 疯狂安装oracle 12c,此版本没有scott这个用户
今天要学习oracle,然后寻思下个吧,结果出现了很多问题,在此分享一下,搞疯了,太痛苦了,学的教程是用的 Oracle 11g,我去官网下载的Oracle 12g,文件很大,好不容易装好了,寻思就这 ...
- 池建强 博客 Mac使用技巧 第一季
第1天: 今天推送的Mac技巧: 使用OS X,我们可以充分利用系统提供的多个Space,把不同的程序放到不同的Space,让我们的系统更有扩展性.如何增加Space呢?四指上推,在桌面的最上方会出现 ...
- Google Map API申请
https://code.google.com/apis/console 当然需要先有个Google账户登录. 然后需要建一个项目. 然后根据package+sha1码获取密钥key 然后就可以创建凭 ...
- D3_book 7 area
<!-- area的例子csv使用node.js提供的 --> <!DOCTYPE html> <meta charset="utf-8"> & ...