1. 程式人生 > >Java Token的原理和生成使用機制

Java Token的原理和生成使用機制

=== 舉例 dap 被人 depend 內容 cto contex jws

在此之前我們先了解一下什麽是Cookie、Session、Token 1、什麽是Cookie?
  1. cookie指的就是瀏覽器裏面能永久存儲數據的一種數據存儲功能。cookie由服務器生成,發送給瀏覽器,瀏覽器把cookie以kv形式保存到某個目錄下的文本文件內,下一次請求同一網站時會把該cookie發送給服務器。由於cookie是存在客戶端上的,所以瀏覽器加入了一些限制確保cookie不會被惡意使用,同時不會占據太多磁盤空間,所以每個域的cookie數量是有限的。
  2. Cookie有什麽功能特點呢?在同一個頁面中設置 Cookie,實際上是按從後往前的順序進行的。如果要先刪除一個 Cookie,再寫入一個 Cookie,則必須先寫寫入語句,再寫刪除語句,否則會出現錯誤 。
  3. Cookie是面向路徑的。缺省路徑 (path) 屬性時,Web 服務器頁會自動傳遞當前路徑給瀏覽器,指定路徑強制服務器使用設置的路徑。在一個目錄頁面裏設置的 Cookie 在另一個目錄的頁面裏是看不到的 。
  4. Cookie 必須在 HTML 文件的內容輸出之前設置;不同的瀏覽器 (Netscape Navigator、Internet Explorer) 對 Cookie 的處理不一致,使用時一定要考慮;客戶端用戶如果設置禁止 Cookie,則 Cookie 不能建立。 並且在客戶端,一個瀏覽器能創建的 Cookie 數量最多為 300 個,並且每個不能超過 4KB,每個 Web 站點能設置的 Cookie 總數不能超過 20 個 。
  5. Cookie的生命周期呢?:Cookie可以保持登錄信息到用戶下次與服務器的會話,換句話說,下次訪問同一網站時,用戶會發現不必輸入用戶名和密碼就已經登錄了(當然,不排除用戶手工刪除Cookie)。而還有一些Cookie在用戶退出會話的時候就被刪除了,這樣可以有效保護個人隱私。Cookie在生成時就會被指定一個Expire值,這就是Cookie的生存周期,在這個周期內Cookie有效,超出周期Cookie就會被清除。有些頁面將Cookie的生存周期設置為“0”或負值,這樣在關閉瀏覽器時,就馬上清除Cookie,不會記錄用戶信息,更加安全。
2、什麽是session?
  1. session就是會話。這個就類似於你和一個人交談,你怎麽知道當前和你交談的是張三而不是李四呢?對方肯定有某種特征(長相等)表明他就是張三。
  2. session 也是類似的道理,服務器要知道當前發請求給自己的是誰。為了做這種區分,服務器就要給每個客戶端分配不同的“身份標識sessionID”,然後客戶端每次向服務器發請求的時候,都帶上這個“身份標識sessionID”,服務器就知道這個請求來自於誰了。至於客戶端怎麽保存這個“身份標識sessionID”,可以有很多種方式,對於瀏覽器客戶端,大家都默認采用 cookie 的方式。
  3. 服務器使用session把用戶的信息臨時保存在了服務器上,用戶離開網站後session會被銷毀。這種用戶信息存儲方式相對cookie來說更安全,可是session有一個缺陷:如果web服務器做了負載均衡,那麽下一個操作請求到了另一臺服務器的時候session會丟失。
  4. JSP使用一個叫HttpSession的對象實現同樣的功能。HTTPSession 是一個建立在cookies 和URL-rewriting上的高質量的界面。Session的信息保存在服務器端,
  5. Session的id保存在客戶機的cookie中。事實上,在許多服務器上,如果瀏覽器支持的話它們就使用cookies,但是如果不支持或廢除了的話就自動轉化為URL-rewriting,
  6. session自動為每個流程提供了方便地存儲信息的方法。
3、Httpsession具有如下API:
  1. getId 此方法返回唯一的標識,這些標識為每個session而產生。當只有一個單一的值與一個session聯合時,或當日誌信息與先前的sessions有關時,它被當作鍵名用。
  2. GetCreationTime 返回session被創建的時間。最小單位為千分之一秒。為得到一個對打印輸出很有用的值,可將此值傳給Date constructor 或者GregorianCalendar的方法setTimeInMillis.
  3. GetLastAccessedTime 返回session最後被客戶發送的時間。最小單位為千分之一秒。
  4. GetMaxInactiveInterval 返回總時間(秒),負值表示session永遠不會超時。
  5. getAttribute 取一個session相聯系的信息。(在jsp1.0中為 getValue)
  6. Integer item = (Integer) session.getAttribute("item") //檢索出session的值並轉化為整型
  7. setAttribute 提供一個關鍵詞和一個值。會替換掉任何以前的值。(在jsp1.0中為putValue)
  8. session.setAttribute("ItemValue", itemName); // ItemValue 必須不是must簡單類型
  9. 在應用中使用最多的是getAttribute和setAttribute.
4、傳統身份驗證(session)
  1. HTTP 是一種沒有狀態的協議,也就是它並不知道是誰是訪問應用。這裏我們把用戶看成是客戶端,客戶端使用用戶名還有密碼通過了身份驗證,不過下回這個客戶端再發送請求時候,還得再驗證一下。
  2. 解決的方法就是,當用戶請求登錄的時候,如果沒有問題,我們在服務端生成一條記錄,這個記錄裏可以說明一下登錄的用戶是誰,然後把這條記錄的 ID 號發送給客戶端,客戶端收到以後把這個 ID 號存儲在 Cookie 裏,
  3. 下次這個用戶再向服務端發送請求的時候,可以帶著這個 Cookie ,這樣服務端會驗證一個這個 Cookie 裏的信息,看看能不能在服務端這裏找到對應的記錄,如果可以,說明用戶已經通過了身份驗證,就把用戶請求的數據返回給客戶端。
  4. 上面說的就是 Session,我們需要在服務端存儲為登錄的用戶生成的 Session ,這些 Session 可能會存儲在內存,磁盤,或者數據庫裏。我們可能需要在服務端定期的去清理過期的 Session
5、什麽是Token? Token是服務端生成的一串字符串,以作客戶端進行請求的一個令牌,當第一次登錄後,服務器生成一個Token便將此Token返回給客戶端,以後客戶端只需帶上這個Token前來請求數據即可,無需再次帶上用戶名和密碼。 基於 Token 的身份驗證
  1. 使用基於 Token 的身份驗證方法,在服務端不需要存儲用戶的登錄記錄。流程是這樣的:
  2. 客戶端使用用戶名跟密碼請求登錄
  3. 服務端收到請求,去驗證用戶名與密碼
  4. 驗證成功後,服務端會簽發一個 Token,再把這個 Token 發送給客戶端
  5. 客戶端收到 Token 以後可以把它存儲起來,比如放在 Cookie 裏或者 Local Storage 裏
  6. 客戶端每次向服務端請求資源的時候需要帶著服務端簽發的 Token
  7. 服務端收到請求,然後去驗證客戶端請求裏面帶著的 Token,如果驗證成功,就向客戶端返回請求的數據
  8. APP登錄的時候發送加密的用戶名和密碼到服務器,服務器驗證用戶名和密碼,如果成功,以某種方式比如隨機生成32位的字符串作為token,存儲到服務器中,並返回token到APP,以後APP請求時,
  9. 凡是需要驗證的地方都要帶上該token,然後服務器端驗證token,成功返回所需要的結果,失敗返回錯誤信息,讓他重新登錄。其中服務器上token設置一個有效期,每次APP請求的時候都驗證token和有效期。
6、由此我們可以擴展一個問題:
  1. 服務器上的token存儲到數據庫中,每次查詢會不會很費時。如果不存儲到數據庫,應該存儲到哪裏呢。
  2. 客戶端得到的token肯定要加密存儲的,發送token的時候再解密。存儲到數據庫還是配置文件好呢
  3. token是個易失數據,丟了無非讓用戶重新登錄一下,可以放到 MSSQL/MySQL 的內存表裏(不過據說mysql的內存表性能提升有限),可以放到 Memcache裏,可以放到redis裏,放到 OpenResty 的變量字典裏(只要你有信心不爆內存)。
  4. 你認為用數據庫來保持token查詢時間太長,會成為你系統的瓶頸或者隱患,可以放在內存當中。
  5. 比如memcached、redis,KV方式很適合你對token查詢的需求。
  6. 這個不會太占內存,比如你的token是32位字符串,要是你的用戶量在百萬級或者千萬級,那才多少內存。
  7. 要是數據量真的大到單機內存扛不住,或者覺得一宕機全丟風險大,只要這個token生成是足夠均勻的,高低位切一下分到不同機器上就行,內存絕對不會是問題。
7、Token安全問題
  1. 在存儲的時候把token進行對稱加密存儲,用時解開。
  2. 將請求URL、時間戳、token三者進行合並加鹽簽名,服務端校驗有效性。
  3. 這兩種辦法的出發點都是:竊取你存儲的數據較為容易,而反匯編你的程序hack你的加密解密和簽名算法是比較難的。然而其實說難也不難,所以終究是防君子不防小人的做法。話說加密存儲一個你要是被人扒開客戶端看也不會被噴明文存儲……
  4. 方法1它拿到存儲的密文解不開、方法2它不知道你的簽名算法和鹽,兩者可以結合食用。
  5. 但是如果token被人拷走,他自然也能植入到自己的手機裏面,那到時候他的手機也可以以你的身份來用著,這你就瞎了。
  6. 於是可以提供一個讓用戶可以主動expire一個過去的token類似的機制,在被盜的時候能遠程止損。
  7. 在網絡層面上token明文傳輸的話會非常的危險,所以建議一定要使用HTTPS,並且把token放在post body裏
8、基於JWT的Token認證機制實現 JSON Web Token(JWT)是一個非常輕巧的規範。這個規範允許我們使用JWT在用戶和服務器之間傳遞安全可靠的信息。其JWT的組成一個JWT實際上就是一個字符串,它由三部分組成,頭部、載荷與簽名。
  • 載荷(Payload)
{ "iss": "Online JWT Builder", "iat": 1416797419, "exp": 1448333419, "aud": "www.example.com", "sub": "[email protected]", "GivenName": "Johnny", "Surname": "Rocket", "Email": "[email protected]", "Role": [ "Manager", "Project Administrator" ] } iss: 該JWT的簽發者,是否使用是可選的; sub: 該JWT所面向的用戶,是否使用是可選的; aud: 接收該JWT的一方,是否使用是可選的; exp(expires): 什麽時候過期,這裏是一個Unix時間戳,是否使用是可選的; iat(issued at): 在什麽時候簽發的(UNIX時間),是否使用是可選的; 其他還有:nbf (Not Before):如果當前時間在nbf裏的時間之前,則Token不被接受;一般都會留一些余地,比如幾分鐘;,是否使用是可選的; 將上面的JSON對象進行[base64編碼]可以得到下面的字符串。這個字符串我們將它稱作JWT的Payload(載荷)。 eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9 小知識:Base64是一種基於64個可打印字符來表示二進制數據的表示方法。由於2的6次方等於64,所以每6個比特為一個單元,對應某個可打印字符。三個字節有24個比特,對應於4個Base64單元,即3個字節需要用4個可打印字符來表示。JDK 中提供了非常方便的 BASE64Encoder 和 BASE64Decoder,用它們可以非常方便的完成基於 BASE64 的編碼和解碼
  • 頭部(Header)
JWT還需要一個頭部,頭部用於描述關於該JWT的最基本的信息,例如其類型以及簽名所用的算法等。這也可以被表示成一個JSON對象。 { "typ": "JWT", "alg": "HS256" } 在頭部指明了簽名算法是HS256算法。當然頭部也要進行BASE64編碼,編碼後的字符串如下:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
  • 簽名(Signature)
將上面的兩個編碼後的字符串都用句號.連接在一起(頭部在前),就形成了: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0
  • 最後,我們將上面拼接完的字符串用HS256算法進行加密。
在加密的時候,我們還需要提供一個密鑰(secret)。如果我們用mystar作為密鑰的話,那麽就可以得到我們加密後的內容:rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM
  • 最後將這一部分簽名也拼接在被簽名的字符串後面,我們就得到了完整的JWT:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM
  • 在我們的請求URL中會帶上這串JWT字符串:
https://your.awesome-app.com/make-friend/?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM 9、認證過程 下面我們從一個實例來看如何運用JWT機制實現認證:
  • 登錄
  1. 第一次認證:第一次登錄,用戶從瀏覽器輸入用戶名/密碼,提交後到服務器的登錄處理的Action層(Login Action);
  2. Login Action調用認證服務進行用戶名密碼認證,如果認證通過,Login Action層調用用戶信息服務獲取用戶信息(包括完整的用戶信息及對應權限信息);
  3. 返回用戶信息後,Login Action從配置文件中獲取Token簽名生成的秘鑰信息,進行Token的生成;
  4. 生成Token的過程中可以調用第三方的JWT Lib生成簽名後的JWT數據;
  5. 完成JWT數據簽名後,將其設置到COOKIE對象中,並重定向到首頁,完成登錄過程;
  • 請求認證
  1. 基於Token的認證機制會在每一次請求中都帶上完成簽名的Token信息,這個Token信息可能在COOKIE中,也可能在HTTP的Authorization頭中;
  2. 客戶端(APP客戶端或瀏覽器)通過GET或POST請求訪問資源(頁面或調用API);
  3. 認證服務作為一個Middleware HOOK 對請求進行攔截,首先在cookie中查找Token信息,如果沒有找到,則在HTTP Authorization Head中查找;
  4. 如果找到Token信息,則根據配置文件中的簽名加密秘鑰,調用JWT Lib對Token信息進行解密和解碼;
  5. 完成解碼並驗證簽名通過後,對Token中的exp、nbf、aud等信息進行驗證;
  6. 全部通過後,根據獲取的用戶的角色權限信息,進行對請求的資源的權限邏輯判斷;
  7. 如果權限邏輯判斷通過則通過Response對象返回;否則則返回HTTP 401;
  8. 對Token認證的五點認識直接註意的地方:
  • 一個Token就是一些信息的集合;
  • 在Token中包含足夠多的信息,以便在後續請求中減少查詢數據庫的幾率;
  • 服務端需要對cookie和HTTP Authrorization Header進行Token信息的檢查;
  • 基於上一點,你可以用一套token認證代碼來面對瀏覽器類客戶端和非瀏覽器類客戶端;
  • 因為token是被簽名的,所以我們可以認為一個可以解碼認證通過的token是由我們系統發放的,其中帶的信息是合法有效的
10、JWT的JAVA實現 Java中對JWT的支持可以考慮使用JJWT開源庫;JJWT實現了JWT, JWS, JWE 和 JWA RFC規範;下面將簡單舉例說明其使用:
  • 生成Token
import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; import java.security.Key; import io.jsonwebtoken.*; import java.util.Date; //Sample method to construct a JWT private String createJWT(String id, String issuer, String subject, long ttlMillis) { //The JWT signature algorithm we will be using to sign the token SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); //We will sign our JWT with our ApiKey secret byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(apiKey.getSecret()); Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); //Let‘s set the JWT Claims JwtBuilder builder = Jwts.builder().setId(id) .setIssuedAt(now) .setSubject(subject) .setIssuer(issuer) .signWith(signatureAlgorithm, signingKey); //if it has been specified, let‘s add the expiration if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); builder.setExpiration(exp); } //Builds the JWT and serializes it to a compact, URL-safe string return builder.compact(); }
  • 解碼和驗證Token碼
import javax.xml.bind.DatatypeConverter; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Claims; //Sample method to validate and read the JWT private void parseJWT(String jwt) { //This line will throw an exception if it is not a signed JWS (as expected) Claims claims = Jwts.parser() .setSigningKey(DatatypeConverter.parseBase64Binary(apiKey.getSecret())) .parseClaimsJws(jwt).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()); }
  • 基於JWT的Token認證的安全問題
如何保證用戶名/密碼驗證過程的安全性;因為在驗證過程中,需要用戶輸入用戶名和密碼,在這一過程中,用戶名、密碼等敏感信息需要在網絡中傳輸。因此,在這個過程中建議采用HTTPS,通過SSL加密傳輸,以確保通道的安全性。 11、Spring實現Token攔截機制 1、JWT 簡介 組成部分: Header 頭部:一般為token類型和加密算法 Payload 負載:一些用戶信息和額外的聲明數據 Signature 簽名:簽名需要使用編碼後的header和payload以及一個秘鑰 (很安全),前兩段的結合加密 2:jar包依賴 <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.6.0</version> </dependency> 3:jwt加密和解密的工具類 package com.alienlab.news.utils; import com.alibaba.fastjson.JSONObject; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; import java.security.Key; import java.util.Date; /** * Created by Msater Zg on 2017/3/13. jwt實現方式 */ public class JwtUtils { public static Claims parseJWT(String jsonWebToken, String base64Security) { try { Claims claims = Jwts.parser() .setSigningKey(DatatypeConverter.parseBase64Binary(base64Security)) .parseClaimsJws(jsonWebToken).getBody(); return claims; } catch (Exception ex) { return null; } } 前三個參數為自己用戶token的一些信息比如id,權限,名稱等。不要將隱私信息放入(大家都可以獲取到) public static String createJWT(String name, String userId, String role, String audience, String issuer, long TTLMillis, String base64Security) { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); //生成簽名密鑰 就是一個base64加密後的字符串? byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary (base64Security); Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm. getJcaName()); JSONObject jsonObject = new JSONObject(); jsonObject.put("userName", name); jsonObject.put("userLoginName", userId); //添加構成JWT的參數 JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT") .setIssuedAt(now) //創建時間 .setSubject(jsonObject.toString()) //主題,也差不多是個人的一些信息 .setIssuer(issuer) //發送誰 .setAudience(audience) //個人簽名 .signWith(signatureAlgorithm, signingKey); //估計是第三段密鑰 //添加Token過期時間 if (TTLMillis >= 0) { //過期時間 long expMillis = nowMillis + TTLMillis; //現在是什麽時間 Date exp = new Date(expMillis); //系統時間之前的token都是不可以被承認的 builder.setExpiration(exp).setNotBefore(now); } //生成JWT return builder.compact(); } } 4:使用的條件(該接口允許跨域 cors來配置跨域) 1:cors配置允許跨域 import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import java.util.ArrayList; import java.util.List; /** * Created by Msater Zg on 2017/4/3. */ @Configuration public class CorsConfig extends WebMvcConfigurerAdapter { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowCredentials(true) .allowedMethods("GET", "POST", "DELETE", "PUT") .maxAge(3600); } private CorsConfiguration buildConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); List<String> list = new ArrayList<>(); list.add("*"); corsConfiguration.setAllowedOrigins(list); corsConfiguration.addAllowedOrigin("*"); // 1 corsConfiguration.addAllowedHeader("*"); // 2 corsConfiguration.addAllowedMethod("*"); // 3 return corsConfiguration; } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", buildConfig()); // 4 return new CorsFilter(source); } } 2:攔截器攔截方法獲取token 1:攔截器配置 import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Created by Msater Zg on 2017/4/5. *攔截器 */ public class ApiInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("攔截了"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("攔截了"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("攔截了"); } } 2: 攔截器管理工具 import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; /** * Created by Msater Zg on 2017/4/5. *攔截器管理工具 */ @Configuration public class MyWebAppConfigurer extends WebMvcConfigurerAdapter { @Override public void addInterceptors(InterceptorRegistry registry) { //多個攔截器組成一個攔截器鏈 // addPathPatterns用於添加攔截規則 // excludePathPatterns用戶排除攔截 registry.addInterceptor(new ApiInterceptor()).addPathPatterns("/**"); //對來自/user/** 這個鏈接來的請求進行攔截 super.addInterceptors(registry); } } 5:token的發送與獲取 ajax為例子:beforeSend:function(request) { // token,為登陸時獲取到 request.setRequestHeader("token",token); }, 後臺獲取:request.getHeader("token"); 6:token驗證機制 1:通過token解密是否成功可以判斷token是否正確或者是否過期 2:解密完成,可以對比用戶屬性或者用戶的固定token(緩存中或者放入數據庫) 12、生成Token app登陸驗證不能使用session來判斷了這裏,我定義了一個token類來存儲token。 就是一個字符串+創建的時間戳。然後定義一個管理類來維護token。簡單的實現了,但還有很多問題。比如,我對session的理解(是否可以放session,放session之後什麽狀態), 比如這定義的這個類在調用的時候加載,在不用的時間結束,而我希望一直存在,這個維護類怎麽確保存在,這是類的聲明周期問題,比如加載到內存和緩存的實現,緩存用的太少。 Token.java package com.tixa.wedding.util; import java.io.Serializable; public class Token implements Serializable { /** * @Fields serialVersionUID : TODO */ private static final long serialVersionUID = -754659525548951914L; private String signature; private long timestamp; public Token(String signature, long timestamp) { if (signature == null) throw new IllegalArgumentException("signature can not be null"); this.timestamp = timestamp; this.signature = signature; } public Token(String signature) { if (signature == null) throw new IllegalArgumentException("signature can not be null"); this.signature = signature; } /** * Returns a string containing the unique signatureentifier assigned to this token. */ public String getSignature() { return signature; } public long getTimestamp() { return timestamp; } /** * timestamp 不予考慮, 因為就算 timestamp 不同也認為是相同的 token. */ public int hashCode() { return signature.hashCode(); } public boolean equals(Object object) { if (object instanceof Token) return ((Token)object).signature.equals(this.signature); return false; } @Override public String toString() { return "Token [signature=" + signature + ", timestamp=" + timestamp + "]"; } } TokenUtil.java package com.tixa.wedding.util; import java.security.MessageDigest; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.log4j.Logger; public class TokenUtil { private static final int INTERVAL = 7;// token過期時間間隔 天 private static final String YAN = "testMRf1$789787aadfjkds//*-+‘[]jfeu;384785*^*&%^%$%";// 加鹽 private static final int HOUR = 3;// 檢查token過期線程執行時間 時 private static Logger logger = Logger.getLogger("visit"); private static Map<Integer, Token> tokenMap = new HashMap<Integer, Token>(); private static TokenUtil tokenUtil = null; static ScheduledExecutorService scheduler =Executors.newSingleThreadScheduledExecutor(); static { logger.info("\n===============進入TokenUtil靜態代碼塊=================="); listenTask(); } public static TokenUtil getTokenUtil() { if (tokenUtil == null) { synInit(); } return tokenUtil; } private static synchronized void synInit() { if (tokenUtil == null) { tokenUtil = new TokenUtil(); } } public TokenUtil() { } public static Map<Integer, Token> getTokenMap() { return tokenMap; } /** * 產生一個token */ public static Token generateToken(String uniq,int id) { Token token = new Token(MD5(System.currentTimeMillis()+YAN+uniq+id), System.currentTimeMillis()); synchronized (tokenMap) { tokenMap.put(id, token); } return token; } /** * @Title: removeToken * @Description: 去除token * @param @param nonce * @param @return 參數 * @return boolean 返回類型 */ public static boolean removeToken(int id) { synchronized (tokenMap) { tokenMap.remove(id); logger.info(tokenMap.get(id) == null ? "\n=========已註銷========": "\n++++++++註銷失敗+++++++++++++++"); } return true; } /** * @Title: volidateToken * @Description: 校驗token * @param @param signature * @param @param nonce * @param @return 參數 * @return boolean 返回類型 */ public static boolean volidateToken(String signature, int id) { boolean flag = false; Token token = (Token) tokenMap.get(id); if (token != null && token.getSignature().equals(signature)) { logger.info("\n=====已在線======="); flag = true; } return flag; } /** * * @Title: MD5 * @Description: 加密 * @param @param s * @param @return 參數 * @return String 返回類型 */ public final static String MD5(String s) { try { byte[] btInput = s.getBytes(); // 獲得MD5摘要算法的 MessageDigest 對象 MessageDigest mdInst = MessageDigest.getInstance("MD5"); // 使用指定的字節更新摘要 mdInst.update(btInput); // 獲得密文 return byte2hex(mdInst.digest()); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 將字節數組轉換成16進制字符串 * @param b * @return */ private static String byte2hex(byte[] b) { StringBuilder sbDes = new StringBuilder(); String tmp = null; for (int i = 0; i < b.length; i++) { tmp = (Integer.toHexString(b[i] & 0xFF)); if (tmp.length() == 1) { sbDes.append("0"); } sbDes.append(tmp); } return sbDes.toString(); } /** * @Title: listenTask * @Description: 定時執行token過期清除任務 * @param 參數 * @return void 返回類型 */ public static void listenTask(){ Calendar calendar = Calendar.getInstance(); int year = calendar.get(Calendar.YEAR); int month = calendar.get(Calendar.MONTH); int day = calendar.get(Calendar.DAY_OF_MONTH); //定制每天的HOUR點,從明天開始 calendar.set(year, month, day+1, HOUR, 0, 0); // calendar.set(year, month, day, 17, 11, 40); Date date = calendar.getTime(); scheduler.scheduleAtFixedRate( new ListenToken(), (date.getTime()-System.currentTimeMillis())/1000, 60*60*24, TimeUnit.SECONDS); } /** * @ClassName: ListenToken * @Description: 監聽token過期線程runnable實現 * @author mrf * @date 2015-10-21 下午02:22:24 * */ static class ListenToken implements Runnable { public ListenToken() { super(); } public void run() { logger.info("\n**************************執行監聽token列表****************************"); try { synchronized (tokenMap) { for (int i = 0; i < 5; i++) { if (tokenMap != null && !tokenMap.isEmpty()) { for (Entry<Integer, Token> entry : tokenMap.entrySet()) { Token token = (Token) entry.getValue(); logger.info("\n==============已登錄用戶有:"+entry + "====================="); // try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();} int interval = (int) ((System.currentTimeMillis() - token.getTimestamp()) / 1000 / 60 / 60 / 24); if (interval > INTERVAL) { tokenMap.remove(entry.getKey()); logger.info("\n==============移除token:" + entry+ "====================="); } } } } } } catch (Exception e) { logger.error("token監聽線程錯誤:"+e.getMessage()); e.printStackTrace(); } } } public static void main(String[] args) { System.out.println(generateToken( "s",1)); System.out.println(generateToken( "q",1)); System.out.println(generateToken( "s3",2)); System.out.println(generateToken( "s4",3)); System.out.println(removeToken(3)); System.out.println(getTokenMap()); } }

Java Token的原理和生成使用機制