Java生鲜电商平台-Java后端生成Token架构与设计详解

目的:Java开源生鲜电商平台-Java后端生成Token目的是为了用于校验客户端,防止重复提交.

技术选型:用开源的JWT架构。

1.概述:在web项目中,服务端和前端经常需要交互数据,有的时候由于网络相应慢,客户端在提交某些敏感数据(比如按照正常的业务逻辑,此份数据只能保存一份)时,如果前端多次点击提交按钮会导致提交多份数据,这种情况我们是要防止发生的。

2.解决方法:

①前端处理:在提交之后通过js立即将按钮隐藏或者置为不可用。

②后端处理:对于每次提交到后台的数据必须校验,也就是通过前端携带的令牌(一串唯一字符串)与后端校验来判断当前数据是否有效。

3.总结:第一种方法相对来说比较简单,但是安全系数不高,第二种方法从根本上解决了问题,所以我推荐第二种方法。

4.核心代码:

生成Token的工具类:

  1. /**
  2. * 生成Token的工具类:
  3. */
  4. package red.hearing.eval.modules.token;
  5. import java.security.MessageDigest;
  6. import java.security.NoSuchAlgorithmException;
  7. import java.util.Random;
  8. import sun.misc.BASE64Encoder;
  9. /**
  10. * 生成Token的工具类
  11. * @author zhous
  12. * @since 2018-2-23 13:59:27
  13. *
  14. */
  15. public class TokenProccessor {
  16. private TokenProccessor(){};
  17. private static final TokenProccessor instance = new TokenProccessor();
  18. public static TokenProccessor getInstance() {
  19. return instance;
  20. }
  21. /**
  22. * 生成Token
  23. * @return
  24. */
  25. public String makeToken() {
  26. String token = (System.currentTimeMillis() + new Random().nextInt(999999999)) + "";
  27. try {
  28. MessageDigest md = MessageDigest.getInstance("md5");
  29. byte md5[] =  md.digest(token.getBytes());
  30. BASE64Encoder encoder = new BASE64Encoder();
  31. return encoder.encode(md5);
  32. } catch (NoSuchAlgorithmException e) {
  33. // TODO Auto-generated catch block
  34. e.printStackTrace();
  35. }
  36. return null;
  37. }
  38. }

Token通用工具类

  1. /**
  2. *
  3. */
  4. package red.hearing.eval.modules.token;
  5. import javax.servlet.http.HttpServletRequest;
  6. import org.apache.commons.lang3.StringUtils;
  7. /**
  8. * Token的工具类
  9. * @author zhous
  10. * @since 2018-2-23 14:01:41
  11. *
  12. */
  13. public class TokenTools {
  14. /**
  15. * 生成token放入session
  16. * @param request
  17. * @param tokenServerkey
  18. */
  19. public static void createToken(HttpServletRequest request,String tokenServerkey){
  20. String token = TokenProccessor.getInstance().makeToken();
  21. request.getSession().setAttribute(tokenServerkey, token);
  22. }
  23. /**
  24. * 移除token
  25. * @param request
  26. * @param tokenServerkey
  27. */
  28. public static void removeToken(HttpServletRequest request,String tokenServerkey){
  29. request.getSession().removeAttribute(tokenServerkey);
  30. }
  31. /**
  32. * 判断请求参数中的token是否和session中一致
  33. * @param request
  34. * @param tokenClientkey
  35. * @param tokenServerkey
  36. * @return
  37. */
  38. public static boolean judgeTokenIsEqual(HttpServletRequest request,String tokenClientkey,String tokenServerkey){
  39. String token_client = request.getParameter(tokenClientkey);
  40. if(StringUtils.isEmpty(token_client)){
  41. return false;
  42. }
  43. String token_server = (String) request.getSession().getAttribute(tokenServerkey);
  44. if(StringUtils.isEmpty(token_server)){
  45. return false;
  46. }
  47. if(!token_server.equals(token_client)){
  48. return false;
  49. }
  50. return true;
  51. }
  52. }

使用方法:

①在输出前端页面的时候调用TokenTools.createToken方法,会把本次生成的token放入session中。

②然后在前端页面提交数据时从session中获取token,然后添加到要提交的数据中。

③服务端接受数据后调用judgeTokenIsEqual方法判断两个token是否一致,如果不一致则返回,不进行处理。

备注:tokenClientkey和tokenServerkey自定义,调用judgeTokenIsEqual方法时的tokenClientkey一定要与前端页面的key一致。

基于微信原理JAVA实现线程安全Token验证-JWT,如果不清楚JWT TOKEN的原理机制,我的上一篇JWT-TOKEN博客有详细介绍,这篇博文主要是具体实现。

Token主要是用于以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上密匙。

package com.franz.websocket;

import com.franz.common.utils.StringUtils;
import com.franz.weixin.p3.oauth2.util.MD5Util;
import io.jsonwebtoken.*;
import net.sf.json.JSONObject;
import org.apache.commons.codec.binary.Base64;
import org.jeecgframework.core.common.service.CommonService; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.DatatypeConverter;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map; /**
* OAuthTokenUtils
* Token管理
* @author nizhigengvip@163.com
* @version 2017-08-01
*/
public class OAuthTokenManager {
private String APP_ID = "";
private String APP_SECRET = "";
private String KEY_SING = ""; //用於存放TOKEN的標誌,Redis
private LinkedHashMap<string, object=""> pairs = new LinkedHashMap();//封装json的map
private CommonService service;
public static final int MINUTE_TTL = 60*1000; //millisecond
public static final int HOURS_TTL = 60*60*1000; //millisecond
public static final int DAY_TTL = 12*60*60*1000; //millisecond private OAuthTokenManager() {}
private static OAuthTokenManager single=null;
public static OAuthTokenManager getInstance() {
if (single == null) {
single = new OAuthTokenManager();
}
return single;
} public String getKEY_SING() {
return KEY_SING;
} public void setPairs(LinkedHashMap<string, object=""> pairs) {
this.pairs = pairs;
}
public LinkedHashMap<string, object=""> getPairs() {
return pairs;
} public void put(String key, Object value){//向json中添加属性,在js中访问,请调用data.map.key
pairs.put(key, value);
} public void remove(String key){
pairs.remove(key);
} /**
* 總體封裝
* @param appid
* @param secret
* @param logicInterface 回調函數
* @return
*/
public String token(String appid,String secret,LogicInterface logicInterface){
//获取appid和secret
this.accessPairs(appid,secret);
//验证appid和secretS,获取对象载体
Object subject = this.loginAuthentication(logicInterface);
//生成JWT签名数据ToKen
String token = this.createToken(this.generalSubject(subject),this.MINUTE_TTL);
return token;
} public void accessPairs(String APP_ID, String APP_SECRET) {
this.APP_ID = APP_ID;
this.APP_SECRET = APP_SECRET;
//this.KEY_SING = MD5Util.MD5Encode(APP_ID+"_"+APP_SECRET, "UTF-8").toUpperCase();//要用到的时候才用
} public Object loginAuthentication(LogicInterface logicInterface){
if (StringUtils.isNotBlank(APP_ID) && StringUtils.isNotBlank(APP_SECRET)) {
Map<string, object=""> map = new HashMap<>();
map.put("APP_ID",APP_ID);
map.put("APP_SECRET",APP_SECRET);
if(logicInterface == null || logicInterface.handler(map) == null){
return map;
}else {
return logicInterface.handler(map);
}
} else {
return null;
}
}
/**
* 由字符串生成加密key
* @return
*/
public SecretKey generalKey(){
String stringKey = APP_ID+APP_SECRET;
byte[] encodedKey = Base64.decodeBase64(stringKey);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 生成subject信息
* @param obj
* @return
*/
public static String generalSubject(Object obj){
if(obj != null ) {
JSONObject json = JSONObject.fromObject(obj);
return json.toString();
}else{
return "{}";
} } /**
* 创建token
* @param subject
* @param ttlMillis
* @return
* @throws Exception
*/
public String createToken(String subject, long ttlMillis) { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
SecretKey key = generalKey();
JwtBuilder builder = Jwts.builder()
.setId(APP_ID)
.setIssuedAt(now)
.setSubject(subject)
.signWith(signatureAlgorithm, key);
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp);
}
return builder.compact();
} /**
* 解密token
* @param token
* @return
* @throws Exception
*/
public Claims validateToken(String token) throws Exception{
Claims claims = Jwts.parser()
.setSigningKey(generalKey())
.parseClaimsJws(token).getBody();
/*System.out.println("ID: " + claims.getId());
System.out.println("Subject: " + claims.getSubject());
System.out.println("Issuer: " + claims.getIssuer());
System.out.println("Expiration: " + claims.getExpiration());*/
return claims;
}
}
import com.ewider.weixin.p3.oauth2.util.MD5Util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map; /**
* OAuthTokenController
*
* @author Franz.ge.倪志耿
* @version 2017-08-01
*/
@Scope("prototype")
@Controller
@RequestMapping("/oAuthToken")
public class OAuthToken { /**
* 獲取Token
* @param grant_type
* @param appid
* @param secret
* @return
*/
@RequestMapping(params = "token",method = RequestMethod.GET)
@ResponseBody
public Object token (@RequestParam(value = "grant_type") String grant_type, @RequestParam(value = "appid") String appid,
@RequestParam(value = "secret") String secret,HttpServletResponse response) {
Map<string, object=""> map = new HashMap<>();
switch (grant_type) {
case "authorization_code" : //授权码模式(即先登录获取code,再获取token)
break;
case "password" : //密码模式(将用户名,密码传过去,直接获取token)
break;
case "client_credentials" : //客户端模式(无用户,用户向客户端注册,然后客户端以自己的名义向’服务端’获取资源)
OAuthTokenManager oAuthTokenManager = OAuthTokenManager.getInstance();
String token = oAuthTokenManager.token(appid, secret,null);//loginInterface是业务逻辑回掉函数
//返回Token
map.put("access_token",token);
map.put("expires_in",OAuthTokenManager.MINUTE_TTL/1000);
break;
case "implicit" : //简化模式(在redirect_uri 的Hash传递token; Auth客户端运行在浏览器中,如JS,Flash)
break;
case "refresh_token" : //刷新access_token
break;
} return map;
} @RequestMapping(params = "loginAuth2",method = RequestMethod.GET)
@ResponseBody
public Object loginAuth2 (HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "accessToken") String accessToken ){
Map<string, object=""> map = new HashMap<>();
//COOKIE不存在:解析验证正确性
try {
OAuthTokenManager oAuthTokenManager = OAuthTokenManager.getInstance();
Claims claims = oAuthTokenManager.validateToken(accessToken);
if (claims != null ) {
map.put("state","success");
map.put("loginAuth","采用Token登录");
int validMillis = (int)(claims.getExpiration().getTime()-System.currentTimeMillis());
if(validMillis > 0) {
//交給容器管理,可以存放redis,這裡模擬是cookie
Cookie cookie = new Cookie(MD5Util.MD5Encode("MD5SING", "UTF-8").toUpperCase(), accessToken);
cookie.setMaxAge(validMillis/1000);
response.addCookie(cookie);
} }else{
map.put("state","fail");
}
}catch (MalformedJwtException | SignatureException e){
map.put("state","signature");//改造簽名,或者無效的Token
map.put("loginAuth","該Token無效");//改造簽名,或者無效的Token
}catch (ExpiredJwtException e){
map.put("state","expired");//改造簽名,或者無效的Token
map.put("loginAuth","Token已經過時");
}catch (Exception e) {
e.printStackTrace();
map.put("state","fail");
}
return map;
} @RequestMapping(params = "index",method = RequestMethod.GET)
@ResponseBody
public Object index (HttpServletRequest request, HttpServletResponse response){
Map<string, object=""> map = new HashMap<>();
//从COOKIE中查找,模拟访问,可以集成容器管理
Cookie[] cookies = request.getCookies();
if (cookies!=null) {
for (int i = cookies.length-1; i >= 0; i--) {
Cookie cookie = cookies[i];
if (cookie.getName().equals(MD5Util.MD5Encode("MD5SING", "UTF-8").toUpperCase())) {
//跳过登陆
map.put("index","采用Redis登录");
return map;
}
}
}
map.put("index","你的Token已经销毁");
return map;
} }
        <dependency>
<groupid>io.jsonwebtoken</groupid>
<artifactid>jjwt</artifactid>
<version>0.7.0</version>
</dependency>

Java生鲜电商平台-Java后端生成Token架构与设计详解的更多相关文章

  1. Java生鲜电商平台-订单配送模块的架构与设计

    Java生鲜电商平台-订单配送模块的架构与设计 生鲜电商系统最终的目的还是用户下单支付购买, 所以订单管理系统是电商系统中最为复杂的系统,其作为中枢决定着整个商城的运转, 本文将对于生鲜类电商平台的订 ...

  2. Java生鲜电商平台-高可用微服务系统如何设计?

    Java生鲜电商平台-高可用微服务系统如何设计? 说明:Java生鲜电商平台高可用架构往往有以下的要求: 高可用.这类的系统往往需要保持一定的 SLA,7*24 时不间断运行不代表完全不挂,而是有一定 ...

  3. Java生鲜电商平台-秒杀系统微服务架构设计与源码解析实战

    Java生鲜电商平台-秒杀系统微服务架构设计与源码解析实战 Java生鲜电商平台-  什么是秒杀 通俗一点讲就是网络商家为促销等目的组织的网上限时抢购活动 比如说京东秒杀,就是一种定时定量秒杀,在规定 ...

  4. Java生鲜电商平台-小程序或者APP优惠券的设计与源码实战

    Java生鲜电商平台-小程序或者APP优惠券的设计与源码实战 说明:Java生鲜电商平台-小程序或者APP优惠券的设计与源码实战,优惠券是一种常见的促销方式,在规定的周期内购买对应商品类型和额度的商品 ...

  5. Java生鲜电商平台-服务化后的互联网架构实战(针对生鲜电商小程序或者APP)

    Java生鲜电商平台-服务化后的互联网架构实战(针对生鲜电商小程序或者APP) “微服务架构”的话题非常之火,很多朋友都在小窗我,说怎么做服务化?解答“怎么做”之前,先得了解“为什么做”. 画外音:做 ...

  6. Java生鲜电商平台-深入订单拆单架构与实战

    Java生鲜电商平台-深入订单拆单架构与实战 Java生鲜电商中在做拆单的需求,细思极恐,思考越深入,就会发现里面涉及的东西越来越多,要想做好订单拆单的功能,还是相当有难度, 因此总结了一下拆单功能细 ...

  7. Java生鲜电商平台-redis缓存在商品中的设计与架构

    Java生鲜电商平台-redis缓存在商品中的设计与架构 说明:Java开源生鲜电商平台-redis缓存在商品中的设计与架构. 1. 各种计数,商品维度计数和用户维度计数 说起电商,肯定离不开商品,而 ...

  8. Java开源生鲜电商平台-性能优化以及服务器优化的设计与架构(源码可下载)

    Java开源生鲜电商平台-性能优化以及服务器优化的设计与架构(源码可下载) 说明:Java开源生鲜电商平台-性能优化以及服务器优化的设计与架构,我采用以下三种维度来讲解 1.  代码层面. 2.  数 ...

  9. Java生鲜电商平台-生鲜系统中微服务架构设计与分析实战

    Java生鲜电商平台-生鲜系统中微服务架构设计与分析实战 说明: Java生鲜系统中微服务的拆分应该如何架构设计与分析呢?以下是我的实战中的设计与经验分析. 目录 1. 微服务简介2. 当前现状3. ...

随机推荐

  1. 你可能会忽略的 Git 提交规范

    一.为什么需要规范? 无规矩不成方圆,编程也一样. 如果你有一个项目,从始至终都是自己写,那么你想怎么写都可以,没有人可以干预你.可是如果在团队协作中,大家都张扬个性,那么代码将会是一团糟,好好的项目 ...

  2. PAT 1006 Sign In and Sign Out 查找元素

    At the beginning of every day, the first person who signs in the computer room will unlock the door, ...

  3. Kafka基本知识整理

    首先Kafka是一个分布式消息队列中间件,Apache顶级项目,https://kafka.apache.org/   高性能.持久化.多副本备份.横向扩展. 生产者Producer往队列里发送消息, ...

  4. Kubernetes的ConfigMap对象使用

    ConfigMap和Secret几乎一样,只是Secret会用base64加密,创建方式也可以彩yaml或者文件方式 下面演示一下通过文件创建configmap 创建配置文件my.yaml name: ...

  5. css基础,css选择器

    07.29自我总结 css基础 一.什么是CSS CSS是级联样式表 CSS术语标记语言,没有逻辑 CSS作用 完成网页内容的样式与布局 二.CSS的三种引入方式 1. 内联式 书写位置:在 head ...

  6. Beyond Compare 4.X 破解方法(亲测有效)

    Windows下Beyond Compare 4 30天评估到期了的话,可以尝试下面两种方式: 破解方式把Beyond Compare 4安装文件夹下面的BCUnrar.dll文件删掉就行了,但是这种 ...

  7. OpenCV:图像的颜色空间转换

    导包: import numpy as np import cv2 import matplotlib.pyplot as plt def show(image): plt.imshow(image) ...

  8. 调试Android Framework的Java部分代码

    DebugAndroidFramework 说明:调试Android Framework的Java部分代码,以调试源码android-28为例,需要一个API 28的模拟器配合使用. 一.下载源码 下 ...

  9. 如何开发优质的 Flutter App:Flutter App 软件调试指南

    本次博主带来的是<深入 Flutter 系列课程>第三讲,主要聊聊如何进行 Flutter App 代码的调试.本次课程将在GitChat平台上免费进行,通过本场 Chat,您将获得以下技 ...

  10. Linux系统学习 七、网络基础—常用网络命令

    1.ifconfig                 #查看网络(设置IP临时生效) 2.hostname [主机名]            #查看或设置主机名       默认的是localhost ...