后端生成token架构与设计详解-亚博电竞手机版
java学习
2020年12月14日 01:51
0
目的: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""> 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"" > pairs) {
this.pairs = pairs;
}
public linkedhashmap""> 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""> 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""> 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""> 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""> 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.jsonwebtokengroupid>
<artifactid>jjwtartifactid>
<version>0.7.0version>
dependency>
展开全文