Java后端生成Token架构与设计详解

简介: Java后端生成Token架构与设计详解

前两天有朋友问到关于JWT的使用场景及使用方法,恰好看到此篇文章,就拿来与大家分享一下。关于JWT不仅可用于本文中提到的校验场景,还可以用到系统之内(比如微服务)调用的验证等。

作者:巨人大哥
来源:cnblogs.com/jurendage/p/9219041.html

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

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

1.概述:

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

2.解决方法:

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

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

3.总结:

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

4.核心代码:

生成Token的工具类:

/**  
 * 生成Token的工具类:  
 */  
package red.hearing.eval.modules.token;  
import java.security.MessageDigest;  
import java.security.NoSuchAlgorithmException;  
import java.util.Random;  
import sun.misc.BASE64Encoder;  
/**  
 * 生成Token的工具类  
 * @author zhous  
 * @since 2018-2-23 13:59:27  
 *  
 */  
public class TokenProccessor {  
     private TokenProccessor(){};  
     private static final TokenProccessor instance = new TokenProccessor();  
    public static TokenProccessor getInstance() {  
        return instance;  
    }  
    /**  
     * 生成Token  
     * @return  
     */  
    public String makeToken() {  
        String token = (System.currentTimeMillis() + new Random().nextInt(999999999)) + "";  
         try {  
            MessageDigest md = MessageDigest.getInstance("md5");  
            byte md5[] =  md.digest(token.getBytes());  
            BASE64Encoder encoder = new BASE64Encoder();  
            return encoder.encode(md5);  
        } catch (NoSuchAlgorithmException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
         return null;  
    }  
}

Token通用工具类

/**  
 *   
 */  
package red.hearing.eval.modules.token;  
import javax.servlet.http.HttpServletRequest;  
import org.apache.commons.lang3.StringUtils;  
/**  
 * Token的工具类  
 * @author zhous  
 *  
 */  
public class TokenTools {  
    /**  
     * 生成token放入session  
     * @param request  
     * @param tokenServerkey  
     */  
    public static void createToken(HttpServletRequest request,String tokenServerkey){  
        String token = TokenProccessor.getInstance().makeToken();  
        request.getSession().setAttribute(tokenServerkey, token);  
    }  
    /**  
     * 移除token  
     * @param request  
     * @param tokenServerkey  
     */  
    public static void removeToken(HttpServletRequest request,String tokenServerkey){  
        request.getSession().removeAttribute(tokenServerkey);  
    }  
    /**  
     * 判断请求参数中的token是否和session中一致  
     * @param request  
     * @param tokenClientkey  
     * @param tokenServerkey  
     * @return  
     */  
    public static boolean judgeTokenIsEqual(HttpServletRequest request,String tokenClientkey,String tokenServerkey){  
        String token_client = request.getParameter(tokenClientkey);  
        if(StringUtils.isEmpty(token_client)){  
            return false;  
        }  
        String token_server = (String) request.getSession().getAttribute(tokenServerkey);  
        if(StringUtils.isEmpty(token_server)){  
            return false;  
        }  
        if(!token_server.equals(token_client)){  
            return false;  
        }  
        return true;  
    }  
}

使用方法:

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

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

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

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

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
 */
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.倪志耿
 */
@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>
目录
相关文章
|
6天前
|
存储 监控 安全
构建现代后端系统:架构与实践
【8月更文挑战第12天】本文旨在探讨构建现代后端系统时需考虑的架构和实践问题。我们将通过分析现代后端系统的核心组件、设计原则、以及如何应对可扩展性、安全性、性能等挑战,提供一个全面的视角。文章不包含代码示例,而是侧重于理论与策略层面的讨论,以期为后端开发人员提供有价值的参考和指导。
|
3天前
|
运维 监控 API
后端开发中的微服务架构:优势与挑战
【8月更文挑战第16天】在软件开发的世界中,微服务架构已经成为一种流行和强大的设计模式。它通过将应用程序分解为一组小型、独立的服务来促进敏捷开发和快速迭代。本文旨在深入探讨微服务架构的核心优势以及实施过程中可能遇到的挑战,帮助读者更好地理解这一现代软件设计方法。
|
5天前
|
存储 NoSQL Java
一天五道Java面试题----第十一天(分布式架构下,Session共享有什么方案--------->分布式事务解决方案)
这篇文章是关于Java面试中的分布式架构问题的笔记,包括分布式架构下的Session共享方案、RPC和RMI的理解、分布式ID生成方案、分布式锁解决方案以及分布式事务解决方案。
一天五道Java面试题----第十一天(分布式架构下,Session共享有什么方案--------->分布式事务解决方案)
|
1天前
|
消息中间件 Java API
解密微服务架构:如何在Java中实现高效的服务通信
微服务架构作为一种现代软件开发模式,通过将应用拆分成多个独立的服务,提升了系统的灵活性和扩展性。然而,实现微服务之间的高效通信仍然是许多开发者面临的挑战。本文将探讨在Java环境中实现微服务架构时,如何使用不同的通信机制来优化服务之间的交互,包括同步和异步通信的方法,以及相关的最佳实践。
|
5天前
|
存储 算法 前端开发
JVM架构与主要组件:了解Java程序的运行环境
JVM的架构设计非常精妙,它确保了Java程序的跨平台性和高效执行。通过了解JVM的各个组件,我们可以更好地理解Java程序的运行机制,这对于编写高效且稳定的Java应用程序至关重要。
17 3
|
7天前
|
存储 缓存 数据库
后端开发的艺术:打造高效、稳定的服务架构
【8月更文挑战第12天】 在数字化浪潮中,后端开发如同搭建一座桥梁,连接用户与数据的无限可能。本文将带你领略后端开发的精髓,从基础的数据库设计到复杂的系统架构,我们将一步步探索如何构建一个既高效又稳定的后端服务。正如甘地所言,“你必须成为你希望在世界上看到的改变”,在后端的世界里,我们正是那些创造改变的人。让我们开始这段技术之旅,发现后端开发的真正魅力所在。
24 5
|
6天前
|
消息中间件 设计模式 API
后端开发中的微服务架构设计原则
【8月更文挑战第13天】在软件工程的世界中,微服务架构已经成为一种流行的设计模式,它通过将复杂的应用程序分解成一组小的服务来简化开发和部署。本文探讨了微服务背后的设计理念,以及如何在后端开发实践中应用这些原则来构建可扩展、灵活且易于维护的系统。我们将深入讨论服务的划分、通信协议的选择、数据一致性的保障以及容错性策略的实施,旨在为后端开发人员提供一套实用的微服务架构设计指导。
17 1
|
7天前
|
负载均衡 监控 API
构建高效微服务架构:后端开发的新趋势
【7月更文挑战第43天】 在当今软件开发领域,微服务架构已成为推动技术创新与实现业务敏捷性的一股不可忽视的力量。本文旨在深入剖析构建高效微服务架构的核心原则、关键技术和实践挑战,为后端开发者提供一套行之有效的解决方案。通过精心设计的模块化服务、智能的服务发现机制、弹性的负载均衡策略以及持续的性能优化实践,我们能够确保系统的可扩展性、稳定性及快速响应市场变化的能力。文章将详细探讨如何通过容器化技术、服务网格、API网关等现代工具和框架,来构建和维护一个高效的微服务系统。
|
10天前
|
消息中间件 监控 API
后端开发之巅:探索微服务架构的奥秘
在数字化浪潮中,微服务架构成为软件开发的宠儿。它以灵活性、可扩展性和独立性著称,让复杂应用的管理变得简单。本文将带你走进微服务的世界,从基础概念到设计原则,再到实际应用案例,一步步揭开微服务架构的神秘面纱。让我们一起思考甘地的话:“你必须成为你希望在世界上看到的改变。”在后端开发领域,这意味着我们需要拥抱变化,用微服务架构创造更好的软件生态。
|
13天前
|
敏捷开发 消息中间件 持续交付
探索后端开发中的微服务架构
在数字化时代,后端开发的复杂性与日俱增。微服务架构作为一种解决方案,旨在通过将复杂的应用程序分解为一组小的、独立的服务来简化开发和部署。这种架构模式不仅提高了系统的可伸缩性和灵活性,还促进了持续交付的能力。本文将深入探讨微服务的概念、优势以及面临的挑战,并通过实际案例分析其在现代后端开发中的应用。