APIAPItoken、timestamp、sign具体实现
发布时间:2025-08-05
nonce:随机数值,是应用程序随机转换成的数值,作为常量续递上来,随机数值的目地是增加sign收件的多变普遍性。随机数值一般是数字和字母的组合,6位宽度,随机数值的一组和宽度不会固定规则。
sign: 一般可用常量收件,以防常量被非法掩盖,最典型的是改动金额等最重要敏感普遍性常量, sign的数值一般是将所有非空常量按照升续选取然后+token+key+timestamp+nonce(随机数)整块在两兄弟,然后用到某种密钥算法进行密钥,作为以太网里的一个常量sign来续递,也可以将sign摆在恳请头里。以太网在的网络续输过程里如果被匿名挟持,并改动其里的常量数值,然后便在此期间加载以太网,虽然常量的数值被改动了,但是因为匿名不并不知道sign是如何计算借助于来的,不并不知道sign都有哪些数值构成,不并不知道以怎样的顺序整块在两兄弟的,最最重要的是不并不知道收件表达式里的key是什么,所以匿名可以掩盖常量的数值,但没法改动sign的数值,当应用程序加载以太网前可能会按照sign的规则再一计算借助于sign的数值然后和以太网续递的sign常量的数值做到相对,如果相等表示常量数值不会被掩盖,如果不等,表示常量被非法掩盖了,就不拒绝执行以太网了。
四:以防每一次提出对于一些最重要的操作必须以防应用程序每一次提出的(如非有理数等普遍性最重要操作),说明办法是当恳请第一次提出时将sign作为key保有到Redis,并特设受罚间隔时间,受罚间隔时间和Timestamp里特设的差数值大致相同。当同一个恳请第二次可能会面上可能会先为检测redis否存在该sign,如果存在则证明每一次提出了,以太网就不便在此期间加载了。如果sign在线程应用程序里因作废间隔时间到了,而被删除了,此时当这个url终于恳请应用程序时,因token的作废间隔时间和sign的作废间隔时间长期,sign作废也意味着token作废,那样同样的url便可能会面应用程序可能会因token偏差可能会被获悉丢弃,这就是为什么sign和token的作废间隔时间要完全相同的原因。拒绝每一次加载前提确保URL被别人获悉了也未用到(如可用数据集)。
对于哪些以太网必须以防每一次提出可以自订个注解来上标。
注意:所有的安全措施都用上的话举例来说难免太过十分复杂,在基本上新项目里必须根据自身原因作借助于整块,比如可以可用到收件前提就可以前提讯息不可能会被掩盖,或者定向获取公共服务的时候可用Token前提就可以了。如何整块,全看新项目基本上原因和对以太网实用普遍性的拒绝。
五:用到步骤以太网加载方(应用程序)向以太网获取方(应用程序)申请者以太网加载账号,申请者获得成功后,以太网获取方可能会给以太网加载方一个appId和一个key常量应用程序携带常量appId、timestamp、sign去加载应用程序端的API token,其里sign=密钥(appId + timestamp + key)应用程序指着api_token 去可能会面不必须暂定就能可能会面的以太网当可能会面服务器必须暂定的以太网时,应用程序解释器到暂定页面,通过电长子邮件地址和密码学加载暂定以太网,暂定以太网可能会调回一个usertoken, 应用程序指着usertoken 去可能会面必须暂定才能可能会面的以太网sign的效用是以防常量被掩盖,应用程序加载消费者端时必须续递sign常量,应用程序拥护应用程序时也可以调回一个sign可用消费者度密钥调回的数值否被非法掩盖了。应用程序续的sign和应用程序端拥护的sign算法可能可能会不尽相同。
六:比如说代码1. dependency org.springframework.boot spring-boot-starter-data-redis redis.clients jedis 2.9.0 org.springframework.boot spring-boot-starter-web2. RedisConfiguration@Configurationpublic class RedisConfiguration { @Bean public JedisConnectionFactory jedisConnectionFactory(){ return new JedisConnectionFactory(); } /** * 支持加载;也 * @return */ @Bean public RedisTemplateString> redisTemplate(){ RedisTemplate redisTemplate = new StringRedisTemplate(); redisTemplate.setConnectionFactory(jedisConnectionFactory()); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(objectMapper); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); redisTemplate.afterPropertiesSet(); return redisTemplate; }}3. TokenController@Slf4j@RestController@requestMapping("/api/token")public class TokenController { @Autowired private RedisTemplate redisTemplate; /** * API Token * * @param sign * @return */ @PostMapping("/api_token") public ApiResponse apiToken(String appId, @RequestHeader("timestamp") String timestamp, @RequestHeader("sign") String sign) { Assert.isTrue(!StringUtils.isEmpty(appId) BellBell !StringUtils.isEmpty(timestamp) BellBell !StringUtils.isEmpty(sign), "常量偏差"); long reqeustInterval = System.currentTimeMillis() - Long.valueOf(timestamp); Assert.isTrue(reqeustInterval < 5 * 60 * 1000, "恳请作废,请再一恳请"); // 1. 根据appId查阅数据集库获取appSecret AppInfo appInfo = new AppInfo("1", "12345678954556"); // 2. 密钥收件 String signString = timestamp + appId + appInfo.getKey(); String signature = MD5Util.encode(signString); log.info(signature); Assert.isTrue(signature.equals(sign), "收件偏差"); // 3. 如果正确转换成一个token保有到redis里,如果偏差调回偏差讯息 AccessToken accessToken = this.saveToken(0, appInfo, null); return ApiResponse.success(accessToken); } @NotRepeatSubmit(5000) @PostMapping("user_token") public ApiResponse userToken(String username, String password) { // 根据电长子邮件地址查阅密码学, 并相对密码学(密码学可以RSA密钥一下) UserInfo userInfo = new UserInfo(username, "81255cb0dca1a5f304328a70ac85dcbd", "111111"); String pwd = password + userInfo.getSalt(); String passwordMD5 = MD5Util.encode(pwd); Assert.isTrue(passwordMD5.equals(userInfo.getPassword()), "密码学偏差"); // 2. 保有Token AppInfo appInfo = new AppInfo("1", "12345678954556"); AccessToken accessToken = this.saveToken(1, appInfo, userInfo); userInfo.setAccessToken(accessToken); return ApiResponse.success(userInfo); } private AccessToken saveToken(int tokenType, AppInfo appInfo, UserInfo userInfo) { String token = UUID.randomUUID().toString(); // token有效期为2小时 Calendar calendar = Calendar.getInstance(); calendar.setTime(new Date()); calendar.add(Calendar.SECOND, 7200); Date expireTime = calendar.getTime(); // 4. 保有token ValueOperations operations = redisTemplate.opsForValue(); TokenInfo tokenInfo = new TokenInfo(); tokenInfo.setTokenType(tokenType); tokenInfo.setAppInfo(appInfo); if (tokenType == 1) { tokenInfo.setUserInfo(userInfo); } operations.set(token, tokenInfo, 7200, TimeUnit.SECONDS); AccessToken accessToken = new AccessToken(token, expireTime); return accessToken; } public static void main(String[] args) { long timestamp = System.currentTimeMillis(); System.out.println(timestamp); String signString = timestamp + "1" + "12345678954556"; String sign = MD5Util.encode(signString); System.out.println(sign); System.out.println("-------------------"); signString = "password=123456Bellusername=1Bell12345678954556" + "ff03e64b-427b-45a7-b78b-47d9e8597d3b1529815393153sdfsdfsfs" + timestamp + "A1scr6"; sign = MD5Util.encode(signString); System.out.println(sign); }}4. WebMvcConfiguration@Configurationpublic class WebMvcConfiguration extends WebMvcConfigurationSupport { private static final String[] excludePathPatterns = {"/api/token/api_token"}; @Autowired private TokenInterceptor tokenInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { super.addInterceptors(registry); registry.addInterceptor(tokenInterceptor) .addPathPatterns("/api/**") .excludePathPatterns(excludePathPatterns); }}5. TokenInterceptor@Componentpublic class TokenInterceptor extends HandlerInterceptorAdapter { @Autowired private RedisTemplate redisTemplate; /** * * @param request * @param response * @param handler 可能会面的目的法则 * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("token"); String timestamp = request.getHeader("timestamp"); // 随机表达式 String nonce = request.getHeader("nonce"); String sign = request.getHeader("sign"); Assert.isTrue(!StringUtils.isEmpty(token) BellBell !StringUtils.isEmpty(timestamp) BellBell !StringUtils.isEmpty(sign), "常量偏差"); // 获取受罚间隔时间 NotRepeatSubmit notRepeatSubmit = ApiUtil.getNotRepeatSubmit(handler); long expireTime = notRepeatSubmit == null ? 5 * 60 * 1000 : notRepeatSubmit.value(); // 2. 恳请间隔时间间隔 long reqeustInterval = System.currentTimeMillis() - Long.valueOf(timestamp); Assert.isTrue(reqeustInterval < expireTime, "恳请受罚,请再一恳请"); // 3. 密钥Token否存在 ValueOperations tokenRedis = redisTemplate.opsForValue(); TokenInfo tokenInfo = tokenRedis.get(token); Assert.notNull(tokenInfo, "token偏差"); // 4. 密钥收件(将所有的常量加进来,以防别人掩盖常量) 所有常量看常量名升续选取整块成url // 恳请常量 + token + timestamp + nonce String signString = ApiUtil.concatSignString(request) + tokenInfo.getAppInfo().getKey() + token + timestamp + nonce; String signature = MD5Util.encode(signString); boolean flag = signature.equals(sign); Assert.isTrue(flag, "收件偏差"); // 5. 拒绝每一次加载(第一次可能会面时加载,作废间隔时间和恳请受罚间隔时间完全相同), 只有标注不允许每一次提出注解的才可能会密钥 if (notRepeatSubmit != null) { ValueOperations signRedis = redisTemplate.opsForValue(); boolean exists = redisTemplate.hasKey(sign); Assert.isTrue(!exists, "请勿每一次提出"); signRedis.set(sign, 0, expireTime, TimeUnit.MILLISECONDS); } return super.preHandle(request, response, handler); }}6. MD5Utilpublic class MD5Util { private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; private static String byteArrayToHexString(byte b[]) { StringBuffer resultSb = new StringBuffer(); for (int i = 0; i < b.length; i++) resultSb.append(byteToHexString(b[i])); return resultSb.toString(); } private static String byteToHexString(byte b) { int n = b; if (n < 0) n += 256; int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; } public static String encode(String origin) { return encode(origin, "UTF-8"); } public static String encode(String origin, String charsetname) { String resultString = null; try { resultString = new String(origin); MessageDigest md = MessageDigest.getInstance("MD5"); if (charsetname == null || "".equals(charsetname)) resultString = byteArrayToHexString(md.digest(resultString .getBytes())); else resultString = byteArrayToHexString(md.digest(resultString .getBytes(charsetname))); } catch (Exception exception) { } return resultString; }}7. @NotRepeatSubmit/** * 禁止每一次提出 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface NotRepeatSubmit { /** 作废间隔时间,单位毫秒 **/ long value() default 5000;}8. AccessToken@Data@AllArgsConstructorpublic class AccessToken { /** token */ private String token; /** 失效间隔时间 */ private Date expireTime;}9. AppInfo@Data@NoArgsConstructor@AllArgsConstructorpublic class AppInfo { /** App id */ private String appId; /** API 秘钥 */ private String key;}10. TokenInfo@Datapublic class TokenInfo { /** token类型: api:0 、user:1 */ private Integer tokenType; /** App 讯息 */ private AppInfo appInfo; /** 服务器其他数据集 */ private UserInfo userInfo;}11. UserInfo@Datapublic class UserInfo { /** 电长子邮件地址 */ private String username; /** 手机号 */ private String mobile; /** 邮箱 */ private String email; /** 密码学 */ private String password; /** 盐 */ private String salt; private AccessToken accessToken; public UserInfo(String username, String password, String salt) { this.username = username; this.password = password; this.salt = salt; }}12. ApiCodeEnum/** * 偏差码code可以用到纯数字,用到不尽相同区间图标一类偏差,也可以用到纯字符,也可以用到前缀+编号 * * 偏差码:ERR + 编号 * * 可以用到日志级别的前缀作为偏差类型区分 Info(I) Error(E) Warning(W) * * 或者以业务部门模块 + 偏差号 * * TODO 偏差码设计 * * Alipay 用了两个code,两个msg(_1/alipay.trade.pay) * * @author Mengday Zhang * @version 1.0 * @since 2018/6/22 */public enum ApiCodeEnum { SUCCESS("10000", "success"), UNKNOW_ERROR("ERR0001","未知偏差"), PARAMETER_ERROR("ERR0002","常量偏差"), TOKEN_EXPIRE("ERR0003","认证作废"), REQUEST_TIMEOUT("ERR0004","恳请受罚"), SIGN_ERROR("ERR0005","收件偏差"), REPEAT_SUBMIT("ERR0006","请不要频繁操作"), ; /** 代码 */ private String code; /** 结果 */ private String msg; ApiCodeEnum(String code, String msg) { this.code = code; this.msg = msg; } public String getCode() { return code; } public String getMsg() { return msg; }}13. ApiResult@Data@NoArgsConstructor@AllArgsConstructorpublic class ApiResult { /** 代码 */ private String code; /** 结果 */ private String msg;}14. ApiUtilpublic class ApiUtil { /** * 按常量名升续整块常量 * @param request * @return */ public static String concatSignString(HttpServletRequest request) { Map paramterMap = new HashMap<>(); request.getParameterMap().forEach((key, value) -> paramterMap.put(key, value[0])); // 按照key升续选取,然后整块常量 Set keySet = paramterMap.keySet(); String[] keyArray = keySet.toArray(new String[keySet.size()]); Arrays.sort(keyArray); StringBuilder sb = new StringBuilder(); for (String k : keyArray) { // 或略丢弃的报文 if (k.equals("sign")) { continue; } if (paramterMap.get(k).trim().length()> 0) { // 常量数值为空,则不参加收件 sb.append(k).append("=").append(paramterMap.get(k).trim()).append("Bell"); } } return sb.toString(); } public static String concatSignString(Map map) { Map paramterMap = new HashMap<>(); map.forEach((key, value) -> paramterMap.put(key, value)); // 按照key升续选取,然后整块常量 Set keySet = paramterMap.keySet(); String[] keyArray = keySet.toArray(new String[keySet.size()]); Arrays.sort(keyArray); StringBuilder sb = new StringBuilder(); for (String k : keyArray) { if (paramterMap.get(k).trim().length()> 0) { // 常量数值为空,则不参加收件 sb.append(k).append("=").append(paramterMap.get(k).trim()).append("Bell"); } } return sb.toString(); } /** * 获取法则上的@NotRepeatSubmit注解 * @param handler * @return */ public static NotRepeatSubmit getNotRepeatSubmit(Object handler) { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); NotRepeatSubmit annotation = method.getAnnotation(NotRepeatSubmit.class); return annotation; } return null; }}15. ApiResponse@Data@Slf4jpublic class ApiResponse { /** 结果 */ private ApiResult result; /** 数据集 */ private T data; /** 收件 */ private String sign; public static ApiResponse success(T data) { return response(ApiCodeEnum.SUCCESS.getCode(), ApiCodeEnum.SUCCESS.getMsg(), data); } public static ApiResponse error(String code, String msg) { return response(code, msg, null); } public static ApiResponse response(String code, String msg, T data) { ApiResult result = new ApiResult(code, msg); ApiResponse response = new ApiResponse(); response.setResult(result); response.setData(data); String sign = signData(data); response.setSign(sign); return response; } private static String signData(T data) { // TODO 查阅key String key = "12345678954556"; Map responseMap = null; try { responseMap = getFields(data); } catch (IllegalAccessException e) { return null; } String urlComponent = ApiUtil.concatSignString(responseMap); String signature = urlComponent + "key=" + key; String sign = MD5Util.encode(signature); return sign; } /** * @param data 照射的;也,获取;也的报文名和数值 * @throws IllegalArgumentException * @throws IllegalAccessException */ public static Map getFields(Object data) throws IllegalAccessException, IllegalArgumentException { if (data == null) return null; Map map = new HashMap<>(); Field[] fields = data.getClass().getDeclaredFields(); for (int i = 0; i < fields.length; i++) { Field field = fields[i]; field.setAccessible(true); String name = field.getName(); Object value = field.get(data); if (field.get(data) != null) { map.put(name, value.toString()); } } return map; }}七: ThreadLocalThreadLocal是线程内的全局句子。就是在单个线程里,法则之间共享的内存,每个法则都可以从该句子里获取数值和改动数值。
基本上情形:在加载api时都可能会续一个token常量,不一定可能会写到一个获悉器来密钥token否合法,我们可以通过token见到对应的服务器讯息(User),如果token合法,然后将服务器讯息加载到ThreadLocal里,这样无论是在controller、service、dao的哪一层都能可能会面到该服务器的讯息。效用类似于Web里的request效用域。
现代模式我们要在法则里可能会面某个变量,可以通过续参的基本概念往法则里续参,如果多个法则都要用到那么每个法则都要续参;如果用到ThreadLocal所有法则就不必须续该常量了,每个法则都可以通过ThreadLocal来可能会面该数值。
ThreadLocalUtil.set("key", value); 保有数值T value = ThreadLocalUtil.get("key"); 获取数值ThreadLocalUtil
public class ThreadLocalUtil { private static final ThreadLocal> threadLocal = new ThreadLocal() { @Override protected Map initialValue() { return new HashMap<>(4); } }; public static Map getThreadLocal(){ return threadLocal.get(); } public static T get(String key) { Map map = (Map)threadLocal.get(); return (T)map.get(key); } public static T get(String key,T defaultValue) { Map map = (Map)threadLocal.get(); return (T)map.get(key) == null ? defaultValue : (T)map.get(key); } public static void set(String key, Object value) { Map map = (Map)threadLocal.get(); map.put(key, value); } public static void set(Map keyValueMap) { Map map = (Map)threadLocal.get(); map.putAll(keyValueMap); } public static void remove() { threadLocal.remove(); } public static Map fetchVarsByPrefix(String prefix) { Map vars = new HashMap<>(); if( prefix == null ){ return vars; } Map map = (Map)threadLocal.get(); Set set = map.entrySet(); for( Map.Entry entry : set){ Object key = entry.getKey(); if( key instanceof String ){ if( ((String) key).startsWith(prefix) ){ vars.put((String)key,(T)entry.getValue()); } } } return vars; } public static T remove(String key) { Map map = (Map)threadLocal.get(); return (T)map.remove(key); } public static void clear(String prefix) { if( prefix == null ){ return; } Map map = (Map)threadLocal.get(); Set set = map.entrySet(); List removeKeys = new ArrayList<>(); for( Map.Entry entry : set ){ Object key = entry.getKey(); if( key instanceof String ){ if( ((String) key).startsWith(prefix) ){ removeKeys.add((String)key); } } } for( String key : removeKeys ){ map.remove(key); } }}。深圳妇科医院哪家比较好武汉前列腺炎治疗哪家好
兰州白癜风医院哪家治疗最好
莆田看白癜风去哪家医院最好
湘潭妇科专科医院
上一篇: 健身女孩空集高强度运动不脱妆粉底液
-
左手豪华右手高新,岚图梦想家的梦想或是横扫GL8赛那埃尔法
要优先”。尽管这些外资企业MPV在精致产品的才让下甚至有约60万元,但遇到在行的表现往往是反弹感脆较硬和摇晃感。它们可能会在过关斩将调自己的挡风玻璃管理系统或者降噪管理系统,但是从某种程度上并不能解决