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


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

    Token通用工具类

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

    使用方法:

    ①在输出前端页面的时候调用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>
  • 相关阅读:
    完美解决IE8有两个进程的问题
    用ccproxy + stunnel做个加密代理
    Hyper-V 共享式网络链接 端口映射
    NET Framework 4.0的安装失败处理
    c#控制IE浏览器自动点击等事件WebBrowser,mshtml.IHTMLDocument2 .
    设置IE8 多个Table只产生一个进程
    SSH Secure Shell Client中文乱码的解决办法
    OOD设计模式
    MVC设计模式
    乐观锁和悲观锁
  • 原文地址:https://www.cnblogs.com/jurendage/p/9219041.html
Copyright © 2020-2023  润新知