版权声明:本文为博主原创文章,如需转载请标明来源。 https://blog.csdn.net/sinat_35821285/article/details/82111098
项目中App与系统进行数据交互(数据的存储,短信验证的发送)时需要对交互的数据进行签名校验,防止数据被篡改。
自己单独写了个小例子
第一步:pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>springboot_valid</groupId>
<artifactId>sign_valid</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web </artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc </artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
<!-- 添加热部署的依赖 spring-boot-devtools 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<scope>true </scope>
</dependency>
</dependencies>
<!-- spring-boot-devtool plugin -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
</project>
第二步:Springboot启动类
package com;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ApplicationController {
public static void main(String[] args) {
SpringApplication.run(ApplicationController.class, args);
}
}
第三步:验证参数实体(这里暂只有主要的时间戳和签名,一般会连带交互的数据一起)
package com.sign.param;
import com.fasterxml.jackson.annotation.JsonProperty;
public class VerificationCodeParam {
//签名
@JsonProperty(value = "Sign")
private String sign;
//时间戳
@JsonProperty(value = "TimeStamp")
private long TimeStamp;
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public long getTimeStamp() {
return TimeStamp;
}
public void setTimeStamp(long timeStamp) {
TimeStamp = timeStamp;
}
}
第四步:控制层,接口
package com.sign.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.sign.param.VerificationCodeParam;
import com.sign.util.SignUtil;
import com.sign.util.TimestampUtil;
@RestController
@RequestMapping(value = "/sign" , produces = "application/json")
public class signValidateController {
@Value("${ticketSecret}")
private String ticketSecret;
//签名校验
@RequestMapping(value = "/valid")
public boolean signValid(@RequestBody VerificationCodeParam param) {
// 验证信息是否被篡改
if(!SignUtil.validateMessage(param, ticketSecret)) {
return false;
}
// 验证时间戳,防止重复提交
Boolean validateResult = TimestampUtil.validateTimestamp("verificationCode", param.getTimeStamp());
if(!validateResult) {
return false;
}
return true;
}
}
第五步:工具类
SignUtil签名验证工具类,其中调用了CheckUtil类(计算新的签名,拼接数据等操作)MD5Util类(加密),MapKeyComparator(使用treeMap进行排序时传入自定义比较器【可省略】)
TimestampUtil时间戳验证类 ,其中调用了RedisUtil工具类(将时间戳存入redis),其中RedisUtil中使用SpringContextUtil类获取redis实例
package com.sign.util;
import com.sign.param.VerificationCodeParam;
public class SignUtil {
public static boolean validateMessage(VerificationCodeParam param,String secretKey) {
CheckUtil check = new CheckUtil(secretKey);
check.setValue("timeStamp", param.getTimeStamp());
boolean result = check.checkSign(param.getSign());
return result;
}
}
package com.sign.util;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
public class CheckUtil {
private String secret;
private Map<String,Object> map = new HashMap<String,Object>();
public CheckUtil(String secret) {
this.secret = secret;
}
/**
* 添加参数
* @param key
* @param value
*/
public void setValue(String key,Object value) {
map.put(key, value);
}
/**
* 检验签名是否正确
* @param sign
* @return
*/
public boolean checkSign(String sign) {
if(sign == null || sign == "") {
return false;
}
//本地计算新的签名
String cal_sign = makeSign();
if(cal_sign.equals(sign)) {
return true;
}else {
return false;
}
}
/**
* 生成签名
* @return
*/
public String makeSign() {
//拼接数据
String str = buildData();
//在拼接的数据后拼入API KEY
str += "&key=" + secret;
//MD5加密
String re = MD5Util.encrypt(str);
//所有字符串转成大写
return re.toUpperCase();
}
/**
* 拼接数据
* @return
*/
private String buildData() {
String str = "";
Map<String,Object> resultMap = sortMapByKey(map);
Iterator<String> it = resultMap.keySet().iterator();
while(it.hasNext()) {
String key = it.next();
Object value = resultMap.get(key);
str += key + "=" + value +"&";
}
str = str.substring(0,str.length() - 1);
return str;
}
/**
* 使用 Map按key进行排序(这里重写了比较器的compare方法按升序排序)
* @param map
* @return
*/
public static Map<String,Object> sortMapByKey(Map<String,Object> map){
if(map == null || map.isEmpty()) {
return null;
}
Map<String,Object> sortMap = new TreeMap<String,Object>(new MapKeyComparator());
sortMap.putAll(map);
return sortMap ;
}
}
package com.sign.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Util {
public final static String encrypt(String s) {
char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
byte [] byteInput = s.getBytes();
//获取MD5摘要算法的MessageDigest对象
try {
MessageDigest mdInst = MessageDigest.getInstance("MD5");
//使用指定的字节更新摘要
mdInst.update(byteInput);
//获得密文
byte[] md = mdInst.digest();
// 把密文转换成十六进制的字符串形式
int j = md.length;
char str[] = new char[j *2]; //char占两个字节
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf]; //右移四位,高四位清空 取低四位的值
}
return new String(str);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
}
package com.sign.util;
import java.util.Comparator;
public class MapKeyComparator implements Comparator<String> {
public int compare(String str1, String str2) {
return str1.compareTo(str2); //升序排序
//return str2.compareTo(str1); 降序排序
}
}
时间戳验证
package com.sign.util;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class TimestampUtil {
/**
* 验证时间戳是否合法
* @param cacheKey
* @param currentTimestamp
* @return
*/
public static Boolean validateTimestamp(String cacheKey,long currentTimestamp) {
long beforeTimeStamp = 0;
String timestamp = RedisUtil.Instance().getCacheAccessToken(cacheKey);
if(timestamp != null && timestamp != "") {
beforeTimeStamp = Long.parseLong(timestamp);
}
// //当前时间戳小于或等于之前的时间戳。说明是重复的
if(currentTimestamp < beforeTimeStamp && beforeTimeStamp > 0) {
return false;
}
//session可能会超时
long nowTimeStamp = getSecondTimestampTwo(new Date());
if(nowTimeStamp - currentTimestamp > 3000000) {
return false;
}
//redis存储的key,value,失效时间,时间单位
RedisUtil.Instance().setCacheAccessToken(cacheKey, String.valueOf(currentTimestamp), 3, TimeUnit.DAYS);
return true;
}
/**
* 精确到秒的时间戳
* @param date
* @return
*/
private static long getSecondTimestampTwo(Date date) {
if(null == date) {
return 0;
}
String timestamp = String.valueOf(date.getTime()/1000);
return Long.parseLong(timestamp);
}
}
package com.sign.util;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String,String> redisTemplate;
private static RedisUtil _instance;
public static RedisUtil Instance() {
if(_instance == null) {
_instance = SpringContextUtil.getBean(RedisUtil.class);
}
return _instance;
}
/**
* 缓存AccessToken
* @param redisKey
* @param token
* @param expireTime
* @param timeUnit
*/
public void setCacheAccessToken(String redisKey,String token,long expireTime,TimeUnit timeUnit) {
ValueOperations<String,String> ho = redisTemplate.opsForValue();
//存储用户资源权限到redis,3天失效
ho.set(redisKey, token, expireTime, timeUnit);
}
/**
* 缓存中读取AccessToken
* @param redisKey
* @return
*/
public String getCacheAccessToken(String redisKey) {
ValueOperations<String, String> ho = redisTemplate.opsForValue();
return ho.get(redisKey);
}
}
package com.sign.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringContextUtil implements ApplicationContextAware{
private static ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext arg0) throws BeansException {
if(SpringContextUtil.applicationContext == null) {
SpringContextUtil.applicationContext = arg0;
}
System.out.println("********ApplicationContext配置成功,在普通类可以通过调用SpringUtils.getAppContext()获取applicationContext对象,applicationContext=\"+SpringContextUtil.applicationContext+\"**********");
}
//获取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
//通过name获取 Bean.
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
//通过class获取Bean.
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}
//通过name,以及Clazz返回指定的Bean
public static <T> T getBean(String name,Class<T> clazz){
return getApplicationContext().getBean(name, clazz);
}
}