1. 程式人生 > >MD5加時間戳加密驗證簽名

MD5加時間戳加密驗證簽名

專案中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);
    }
	
}