后端生成Token架构与设计详解
點(diǎn)擊上方?好好學(xué)java?,選擇?星標(biāo)?公眾號(hào)
重磅資訊、干貨,第一時(shí)間送達(dá)今日推薦:硬剛一周,3W字總結(jié),一年的經(jīng)驗(yàn)告訴你如何準(zhǔn)備校招!
個(gè)人原創(chuàng)100W+訪問(wèn)量博客:點(diǎn)擊前往,查看更多作者:巨人大哥
cnblogs.com/jurendage/p/9219041.html
目的:Java開(kāi)源生鮮電商平臺(tái)-Java后端生成Token目的是為了用于校驗(yàn)客戶(hù)端,防止重復(fù)提交.
技術(shù)選型:用開(kāi)源的JWT架構(gòu)。
1.概述:
在web項(xiàng)目中,服務(wù)端和前端經(jīng)常需要交互數(shù)據(jù),有的時(shí)候由于網(wǎng)絡(luò)相應(yīng)慢,客戶(hù)端在提交某些敏感數(shù)據(jù)(比如按照正常的業(yè)務(wù)邏輯,此份數(shù)據(jù)只能保存一份)時(shí),如果前端多次點(diǎn)擊提交按鈕會(huì)導(dǎo)致提交多份數(shù)據(jù),這種情況我們是要防止發(fā)生的。
2.解決方法:
①前端處理:在提交之后通過(guò)js立即將按鈕隱藏或者置為不可用。
②后端處理:對(duì)于每次提交到后臺(tái)的數(shù)據(jù)必須校驗(yàn),也就是通過(guò)前端攜帶的令牌(一串唯一字符串)與后端校驗(yàn)來(lái)判斷當(dāng)前數(shù)據(jù)是否有效。
3.總結(jié):
第一種方法相對(duì)來(lái)說(shuō)比較簡(jiǎn)單,但是安全系數(shù)不高,第二種方法從根本上解決了問(wèn)題,所以我推薦第二種方法。
4.核心代碼:
生成Token的工具類(lèi):
/**??*?生成Token的工具類(lèi):??*/?? package?red.hearing.eval.modules.token;??import?java.security.MessageDigest;?? import?java.security.NoSuchAlgorithmException;?? import?java.util.Random;??import?sun.misc.BASE64Encoder;??/**??*?生成Token的工具類(lèi)??*?@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通用工具類(lèi)
/**??*???*/?? package?red.hearing.eval.modules.token;??import?javax.servlet.http.HttpServletRequest;??import?org.apache.commons.lang3.StringUtils;??/**??*?Token的工具類(lèi)??*?@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);??}??/**??*?判斷請(qǐng)求參數(shù)中的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;??}??}??使用方法:
①在輸出前端頁(yè)面的時(shí)候調(diào)用TokenTools.createToken方法,會(huì)把本次生成的token放入session中。
②然后在前端頁(yè)面提交數(shù)據(jù)時(shí)從session中獲取token,然后添加到要提交的數(shù)據(jù)中。
③服務(wù)端接受數(shù)據(jù)后調(diào)用judgeTokenIsEqual方法判斷兩個(gè)token是否一致,如果不一致則返回,不進(jìn)行處理。
備注:tokenClientkey和tokenServerkey自定義,調(diào)用judgeTokenIsEqual方法時(shí)的tokenClientkey一定要與前端頁(yè)面的key一致。
Token主要是用于以作客戶(hù)端進(jìn)行請(qǐng)求的一個(gè)令牌,當(dāng)?shù)谝淮蔚卿浐?#xff0c;服務(wù)器生成一個(gè)Token便將此Token返回給客戶(hù)端,以后客戶(hù)端只需帶上這個(gè)Token前來(lái)請(qǐng)求數(shù)據(jù)即可,無(wú)需再次帶上密匙。
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的標(biāo)誌,Redisprivate?LinkedHashMap<string,?object="">?pairs?=?new?LinkedHashMap();//封裝json的mapprivate?CommonService?service;public?static?final?int?MINUTE_TTL?=?60*1000;??//millisecondpublic?static?final?int?HOURS_TTL?=?60*60*1000;??//millisecondpublic?static?final?int?DAY_TTL?=?12*60*60*1000;??//millisecondprivate?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中訪問(wèn),請(qǐng)調(diào)用data.map.keypairs.put(key,?value);}public?void?remove(String?key){pairs.remove(key);}/***?總體封裝*?@param?appid*?@param?secret*?@param?logicInterface?回調(diào)函數(shù)*?@return*/public?String?token(String?appid,String?secret,LogicInterface?logicInterface){//獲取appid和secretthis.accessPairs(appid,secret);//驗(yàn)證appid和secretS,獲取對(duì)象載體Object?subject?=?this.loginAuthentication(logicInterface);//生成JWT簽名數(shù)據(jù)ToKenString?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();//要用到的時(shí)候才用}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?"{}";}}/***?創(chuàng)建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)@ResponseBodypublic?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"?:?//授權(quán)碼模式(即先登錄獲取code,再獲取token)break;case?"password"?:?//密碼模式(將用戶(hù)名,密碼傳過(guò)去,直接獲取token)break;case?"client_credentials"?:?//客戶(hù)端模式(無(wú)用戶(hù),用戶(hù)向客戶(hù)端注冊(cè),然后客戶(hù)端以自己的名義向’服務(wù)端’獲取資源)OAuthTokenManager?oAuthTokenManager?=?OAuthTokenManager.getInstance();String?token?=?oAuthTokenManager.token(appid,?secret,null);//loginInterface是業(yè)務(wù)邏輯回掉函數(shù)//返回Tokenmap.put("access_token",token);map.put("expires_in",OAuthTokenManager.MINUTE_TTL/1000);break;case?"implicit"?:?//簡(jiǎn)化模式(在redirect_uri?的Hash傳遞token;?Auth客戶(hù)端運(yùn)行在瀏覽器中,如JS,Flash)break;case?"refresh_token"?:?//刷新access_tokenbreak;}return?map;}@RequestMapping(params?=?"loginAuth2",method?=?RequestMethod.GET)@ResponseBodypublic?Object?loginAuth2?(HttpServletRequest?request,?HttpServletResponse?response,?@RequestParam(value?=?"accessToken")?String?accessToken?){Map<string,?object="">?map?=?new?HashMap<>();//COOKIE不存在:解析驗(yàn)證正確性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,這裡模擬是cookieCookie?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");//改造簽名,或者無(wú)效的Tokenmap.put("loginAuth","該Token無(wú)效");//改造簽名,或者無(wú)效的Token}catch?(ExpiredJwtException?e){map.put("state","expired");//改造簽名,或者無(wú)效的Tokenmap.put("loginAuth","Token已經(jīng)過(guò)時(shí)");}catch?(Exception?e)?{e.printStackTrace();map.put("state","fail");}return?map;}@RequestMapping(params?=?"index",method?=?RequestMethod.GET)@ResponseBodypublic?Object?index?(HttpServletRequest?request,?HttpServletResponse?response){Map<string,?object="">?map?=?new?HashMap<>();//從COOKIE中查找,模擬訪問(wèn),可以集成容器管理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()))?{//跳過(guò)登陸map.put("index","采用Redis登錄");return?map;}}}map.put("index","你的Token已經(jīng)銷(xiāo)毀");return?map;}} <dependency><groupid>io.jsonwebtoken</groupid><artifactid>jjwt</artifactid><version>0.7.0</version> </dependency>推薦文章
-
硬剛一周,3W字總結(jié),一年的經(jīng)驗(yàn)告訴你如何準(zhǔn)備校招!
-
今年的校招,Java 好拿 offer 嗎?
-
10月了,該聊聊今年秋招了!
-
聊聊在騰訊實(shí)習(xí)快一個(gè)月的感受
總結(jié)
以上是生活随笔為你收集整理的后端生成Token架构与设计详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 工资倒挂你怎么看?
- 下一篇: 笔记本敲代码真香,包邮送一个!