注:本章代碼基於《第五章 企業項目開發--mybatis註解與xml並用》的代碼,連接以下:html
http://www.cnblogs.com/java-zhao/p/5120792.htmljava
在實際項目中,咱們會存儲用戶狀態信息,基本使用兩種手段:cookie和sessionmysql
一、cookie:web
1.一、流程:算法
流程與下邊的例子對應一下,馬上就清楚了。spring
1.二、包含選項:(當前使用最多的就是version0版本的cookie,這裏僅說version0的幾個最經常使用的屬性)sql
對於domain,一個例子解釋清楚:chrome
假設:www.baidu.com和e.baidu.com兩個域,path="/",數據庫
1)當domain="baidu.com"時,兩個域均可以訪問生成的cookieapache
2)當domain="www.baidu.com"時,只有前一個域有生成cookie
對於path,用一個例子解釋清楚:
假設http://www.baidu.com/dev/zz/和http://www.baidu.com/dev/xx/,
1)當path="/dev",生成的cookie上邊的兩個地址共享;
2)當path="/dev/zz",生成的cookie只有第一個地址有,第二個地址沒有。
根據上邊的例子,對於path與domain是相互依賴的,共同決定某個頁面地址是否能夠生成指定的cookie
1.三、優勢:
1.四、缺點:
二、項目中使用cookie存儲用戶會話狀態
代碼實現:本章代碼基於上一章代碼實現,下面只列出修改或添加的一些類。
一般狀況下,若是工具類不少,咱們能夠重開一個項目,例如:ssmm0-util,而後在這個子項目中添加類,固然,若是項目自己也不大,直接將工具類放在ssmm0-data子項目下便可。
2.一、ssmm0-data:
2.1.一、pom.xml:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4 5 <modelVersion>4.0.0</modelVersion> 6 7 <!-- 指定父模塊 --> 8 <parent> 9 <groupId>com.xxx</groupId> 10 <artifactId>ssmm0</artifactId> 11 <version>1.0-SNAPSHOT</version> 12 </parent> 13 14 <groupId>com.xxx.ssmm0</groupId> 15 <artifactId>ssmm0-data</artifactId> 16 17 <name>ssmm0-data</name> 18 <packaging>jar</packaging><!-- 只是做爲其餘模塊使用的工具 --> 19 20 <!-- 引入實際依賴 --> 21 <dependencies> 22 <!-- mysql --> 23 <dependency> 24 <groupId>mysql</groupId> 25 <artifactId>mysql-connector-java</artifactId> 26 </dependency> 27 <!-- 數據源 --> 28 <dependency> 29 <groupId>org.apache.tomcat</groupId> 30 <artifactId>tomcat-jdbc</artifactId> 31 </dependency> 32 <!-- mybatis --> 33 <dependency> 34 <groupId>org.mybatis</groupId> 35 <artifactId>mybatis</artifactId> 36 </dependency> 37 <dependency> 38 <groupId>org.mybatis</groupId> 39 <artifactId>mybatis-spring</artifactId> 40 </dependency> 41 <!-- servlet --><!-- 爲了會用cookie --> 42 <dependency> 43 <groupId>javax.servlet</groupId> 44 <artifactId>javax.servlet-api</artifactId> 45 </dependency> 46 <!-- bc-加密 --> 47 <dependency> 48 <groupId>org.bouncycastle</groupId> 49 <artifactId>bcprov-jdk15on</artifactId> 50 </dependency> 51 <!-- cc加密 --> 52 <dependency> 53 <groupId>commons-codec</groupId> 54 <artifactId>commons-codec</artifactId> 55 </dependency> 56 </dependencies> 57 </project>
說明:
注意:
2.1.二、AESUtil:(AES加密)
1 package com.xxx.util; 2 3 import java.io.UnsupportedEncodingException; 4 import java.security.InvalidAlgorithmParameterException; 5 import java.security.InvalidKeyException; 6 import java.security.Key; 7 import java.security.NoSuchAlgorithmException; 8 import java.security.NoSuchProviderException; 9 import java.security.Security; 10 import java.security.spec.InvalidKeySpecException; 11 12 import javax.crypto.BadPaddingException; 13 import javax.crypto.Cipher; 14 import javax.crypto.IllegalBlockSizeException; 15 import javax.crypto.KeyGenerator; 16 import javax.crypto.NoSuchPaddingException; 17 import javax.crypto.SecretKey; 18 import javax.crypto.spec.SecretKeySpec; 19 20 import org.apache.commons.codec.binary.Base64; 21 import org.bouncycastle.jce.provider.BouncyCastleProvider; 22 23 /** 24 * 基於JDK或BC的AES算法,工做模式採用ECB 25 */ 26 public class AESUtil { 27 private static final String ENCODING = "UTF-8"; 28 private static final String KEY_ALGORITHM = "AES";//產生密鑰的算法 29 private static final String CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";//加解密算法 格式:算法/工做模式/填充模式 注意:ECB不使用IV參數 30 private static final String ENCRYPT_KEY = "WAUISIgpBh1R/+3f2Ze4csUU/tl/O8x56DbVb7mTs7w=";//這個是先運行一遍getKey()方法產生的,而後卸載了這裏。 31 32 /** 33 * 產生密鑰(線下產生好,而後配在項目中) 34 */ 35 public static byte[] getKey() throws NoSuchAlgorithmException{ 36 Security.addProvider(new BouncyCastleProvider());//在BC中用,JDK下去除 37 KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM); 38 keyGenerator.init(256);//初始化密鑰長度,128,192,256(選用192和256的時候須要配置無政策限制權限文件--JDK6) 39 SecretKey key =keyGenerator.generateKey();//產生密鑰 40 return key.getEncoded(); 41 } 42 43 /** 44 * 還原密鑰:二進制字節數組轉換爲Java對象 45 */ 46 public static Key toKey(byte[] keyByte){ 47 Security.addProvider(new BouncyCastleProvider());//在BC中用,JDK下去除 48 return new SecretKeySpec(keyByte, KEY_ALGORITHM); 49 } 50 51 /** 52 * AES加密,並轉爲16進制字符串或Base64編碼字符串 53 */ 54 public static String encrypt(String data, byte[] keyByte) throws InvalidKeyException, 55 NoSuchAlgorithmException, 56 InvalidKeySpecException, 57 NoSuchPaddingException, 58 IllegalBlockSizeException, 59 BadPaddingException, 60 UnsupportedEncodingException, 61 NoSuchProviderException, 62 InvalidAlgorithmParameterException { 63 Key key = toKey(keyByte);//還原密鑰 64 //Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);//JDK下用 65 Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM,"BC");//BC下用 66 cipher.init(Cipher.ENCRYPT_MODE, key);//設置加密模式而且初始化key 67 byte[] encodedByte = cipher.doFinal(data.getBytes(ENCODING)); 68 return Base64.encodeBase64String(encodedByte);//藉助CC的Base64編碼 69 } 70 71 /** 72 * AES解密 73 * @param data 待解密數據爲字符串 74 * @param keyByte 密鑰 75 */ 76 public static String decrypt(String data, byte[] keyByte) throws InvalidKeyException, 77 NoSuchAlgorithmException, 78 InvalidKeySpecException, 79 NoSuchPaddingException, 80 IllegalBlockSizeException, 81 BadPaddingException, 82 UnsupportedEncodingException, 83 NoSuchProviderException, 84 InvalidAlgorithmParameterException { 85 Key key = toKey(keyByte);//還原密鑰 86 //Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);//JDK下用 87 Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM,"BC");//BC下用 88 cipher.init(Cipher.DECRYPT_MODE, key); 89 byte[] decryptByte = cipher.doFinal(Base64.decodeBase64(data));//注意data不能夠直接採用data.getByte()方法轉化爲字節數組,不然會拋異常 90 return new String(decryptByte, ENCODING);//這裏注意用new String() 91 } 92 93 /** 94 * 測試 95 */ 96 public static void main(String[] args) throws NoSuchAlgorithmException, 97 InvalidKeyException, 98 InvalidKeySpecException, 99 NoSuchPaddingException, 100 IllegalBlockSizeException, 101 BadPaddingException, 102 UnsupportedEncodingException, 103 NoSuchProviderException, 104 InvalidAlgorithmParameterException { 105 String data = "找一個好姑娘作老婆是個人夢 想!"; 106 /*************測試encryptAESHex()、decrypt()**************/ 107 System.out.println("原文-->"+data); 108 byte[] keyByte3 = Base64.decodeBase64(ENCRYPT_KEY); 109 System.out.println("密鑰-->"+Base64.encodeBase64String(keyByte3));//這裏將二進制的密鑰使用base64加密保存,這也是在實際中使用的方式 110 String encodedStr = AESUtil.encrypt(data, keyByte3); 111 System.out.println("加密後-->"+encodedStr); 112 System.out.println("解密String後-->"+AESUtil.decrypt(encodedStr, keyByte3)); 113 } 114 }
說明:
關於AES的具體內容,能夠參照"Java加密與解密系列的第八章",具體連接以下:
http://www.cnblogs.com/java-zhao/p/5087046.html
AESUtil這個類就是根據上邊這篇博客的AESJDK這個類進行修改封裝的。
注意:
AESUtil類的ENCRYPT_KEY變量的值是先運行了其中的getKey()方法,而後又經過Base64編碼產生的,產生這個密鑰後,咱們將這個密鑰寫在類中。簡單來講,就是線下先產生密鑰,而後寫在程序中,以後getKey()方法就再沒有用了,能夠直接刪掉。
關於生成密鑰的這段代碼以下:
String key = Base64.encodeBase64String(AESUtil.getKey());
2.1.三、CookieUtil:(cookie的基本操做:增刪查,注意沒有改)
1 package com.xxx.util; 2 3 import javax.servlet.http.Cookie; 4 import javax.servlet.http.HttpServletRequest; 5 import javax.servlet.http.HttpServletResponse; 6 7 /** 8 * cookie操做相關類 9 */ 10 public class CookieUtil { 11 12 /** 13 * 增長cookie 14 * @param name cookie名 15 * @param value cookie值 16 * @param domain 指定cookie寫在哪一個域下 17 * @param path 指定cookie存在那個路徑下(其實就是一個uri) 18 * @param expiry 指定cookie過時時間 19 */ 20 public static void addCookie(String name, 21 String value, 22 String domain, 23 String path, 24 int expiry, 25 HttpServletResponse response){ 26 Cookie cookie = new Cookie(name, value); 27 28 cookie.setDomain(domain); 29 cookie.setPath(path); 30 cookie.setMaxAge(expiry); 31 32 response.addCookie(cookie); 33 } 34 35 /** 36 * 獲取cookie 37 * @param request 38 * @param key 將要查找的cookie的名 39 * @return 返回cookie的值 40 */ 41 public static String getCookie(HttpServletRequest request, String key){ 42 Cookie[] cookies = request.getCookies(); 43 /* 44 * 注意在執行循環以前,不須要判斷cookies是否爲空,由於iterator會在循環前執行hasNext; 45 * 可是,最好判斷一下,這樣若是cookies爲null的話,咱們就能夠直接返回,不須要執行循環, 46 * 這樣就不須要平白的建立一個iterator對象並執行一次hasNext。 47 * 48 * 下邊的判斷也能夠換成這樣CollectionUtils.isEmpty(Arrays.asList(cookies)); 49 */ 50 if(cookies == null || cookies.length == 0){ 51 return null; 52 } 53 54 for(Cookie cookie : cookies){ 55 if(cookie.getName().equals(key)){ 56 return cookie.getValue(); 57 } 58 } 59 return null; 60 } 61 62 /** 63 * 清空指定cookie 64 * 值得注意的是,清空cookie不是隻將相應的cookie的value置空便可,其餘信息依舊設置, 65 * 最後加在響應頭中,去覆蓋瀏覽器端的相應的cookie 66 */ 67 public static void removeCookie(String name, 68 String domain, 69 String path, 70 HttpServletResponse response){ 71 Cookie cookie = new Cookie(name, null); 72 73 cookie.setMaxAge(0); 74 cookie.setDomain(domain); 75 cookie.setPath(path); 76 77 response.addCookie(cookie); 78 } 79 }
注意:
前兩條注意點能夠查看後邊的截圖來驗證。
2.1.四、Admin:
1 package com.xxx.model.userManagement; 2 3 import com.alibaba.fastjson.JSON; 4 5 /** 6 * 管理員 7 */ 8 public class Admin { 9 private int id; 10 private String username; 11 private String password; 12 13 public int getId() { 14 return id; 15 } 16 17 public void setId(int id) { 18 this.id = id; 19 } 20 21 public String getUsername() { 22 return username; 23 } 24 25 public void setUsername(String username) { 26 this.username = username; 27 } 28 29 public String getPassword() { 30 return password; 31 } 32 33 public void setPassword(String password) { 34 this.password = password; 35 } 36 37 //將json串轉爲Admin 38 public static Admin parseJsonToAdmin(String jsonStr){ 39 try { 40 return JSON.parseObject(jsonStr, Admin.class); 41 } catch (Exception e) { 42 e.printStackTrace(); 43 return null; 44 } 45 } 46 47 //將當前實例轉化爲json串 48 public String toJson(){ 49 return JSON.toJSONString(this); 50 } 51 }
說明:在Admin中,增長了兩個方法,一個是將當前實例轉化爲json串,一個是將json串轉化爲Admin;這是由於cookie的傳輸只能傳遞字符串而不能傳遞對象。
2.二、ssmm0-userManagement:
2.2.一、pom.xml
注意:這個類沒有改動,之因此列出來,是要提醒去注意compile的傳遞性與provided的不可傳遞性
2.2.二、AdminCookieUtil
1 package com.xxx.util.admin; 2 3 import javax.servlet.http.HttpServletRequest; 4 import javax.servlet.http.HttpServletResponse; 5 6 import org.apache.commons.codec.binary.Base64; 7 import org.apache.commons.lang.StringUtils; 8 9 import com.xxx.model.userManagement.Admin; 10 import com.xxx.util.AESUtil; 11 import com.xxx.util.CookieUtil; 12 13 /** 14 * Admin的cookie操做類 15 */ 16 public class AdminCookieUtil { 17 private static final String COOKILE_NAME = "allinfo"; 18 private static final String USER_NAME = "username"; 19 private static final String DOMAIN = "";//when working on localhost the cookie-domain must be set to "" or NULL or FALSE 20 private static final String PATH = "/"; 21 private static final int EXPIRY = -1;//關閉瀏覽器,則cookie消失 22 23 private static final String ENCRYPT_KEY = "gEfcsJx8VUT406qI4r6/3104noOzI/YAaS98cToY+nI=";//加解密密鑰 24 25 public static void addLoginCookie(Admin admin, HttpServletResponse response){ 26 try{ 27 CookieUtil.addCookie(COOKILE_NAME, AESUtil.encrypt(admin.toJson(), Base64.decodeBase64(ENCRYPT_KEY)), DOMAIN, PATH, EXPIRY, response); 28 CookieUtil.addCookie(USER_NAME, admin.getUsername(), DOMAIN, PATH, EXPIRY, response); 29 }catch (Exception e) { 30 e.printStackTrace(); 31 } 32 } 33 34 public static Admin getLoginCookie(HttpServletRequest request){ 35 String json = CookieUtil.getCookie(request, COOKILE_NAME); 36 if(StringUtils.isNotBlank(json)){ 37 try{ 38 return Admin.parseJsonToAdmin(AESUtil.decrypt(json, Base64.decodeBase64(ENCRYPT_KEY))); 39 }catch (Exception e) { 40 e.printStackTrace(); 41 return null; 42 } 43 } 44 return null; 45 } 46 }
注意點:
步驟:(這就是cookie的使用流程)
2.2.三、AdminController
1 package com.xxx.web.admin; 2 3 import java.util.List; 4 5 import javax.servlet.http.HttpServletRequest; 6 import javax.servlet.http.HttpServletResponse; 7 8 import org.springframework.beans.factory.annotation.Autowired; 9 import org.springframework.stereotype.Controller; 10 import org.springframework.web.bind.annotation.RequestMapping; 11 import org.springframework.web.bind.annotation.RequestParam; 12 import org.springframework.web.bind.annotation.ResponseBody; 13 import org.springframework.web.servlet.ModelAndView; 14 15 import com.xxx.model.userManagement.Admin; 16 import com.xxx.service.userManagement.AdminService; 17 import com.xxx.util.admin.AdminCookieUtil; 18 19 /** 20 * adminController 21 */ 22 @Controller 23 @RequestMapping("/admin") 24 public class AdminController { 25 26 @Autowired 27 private AdminService adminService; 28 29 /** 30 * 管理員註冊 31 */ 32 @ResponseBody 33 @RequestMapping("/register") 34 public boolean register(@RequestParam("username") String username, 35 @RequestParam("password") String password){ 36 Admin admin = new Admin(); 37 admin.setUsername(username); 38 admin.setPassword(password); 39 40 boolean isRegisterSuccess = adminService.register(admin); 41 42 return isRegisterSuccess; 43 } 44 45 /** 46 * 管理員登陸 47 */ 48 @RequestMapping("/login") 49 public ModelAndView login(@RequestParam("username") String username, 50 @RequestParam("password") String password, 51 HttpServletResponse response){ 52 53 54 Admin admin = adminService.login(username, password); 55 56 ModelAndView modelAndView = new ModelAndView(); 57 if(admin == null){ 58 modelAndView.addObject("message", "用戶不存在或者密碼錯誤!請從新輸入"); 59 modelAndView.setViewName("error"); 60 }else{ 61 modelAndView.addObject("admin", admin); 62 modelAndView.setViewName("userinfo"); 63 /* 64 * 這爲何不直接傳一個username,而傳了一個admin, 65 * 是由於在實際開發中,你傳過去的信息可能不僅是username,還有用戶手機號、地址等等 66 */ 67 AdminCookieUtil.addLoginCookie(admin, response); 68 } 69 70 return modelAndView; 71 } 72 73 /*****************************mybatis xml方式解決的問題*******************************/ 74 /** 75 * 根據username或password查找List<Admin> 76 */ 77 @ResponseBody 78 @RequestMapping("/findAdmin") 79 public List<Admin> findAdmin(@RequestParam(value="username",required=false) String username, 80 @RequestParam(value="password",required=false) String password, 81 @RequestParam("start") int start, 82 @RequestParam("limit") int limit, 83 HttpServletRequest request){ 84 Admin admin = AdminCookieUtil.getLoginCookie(request); 85 if(admin == null){//未登陸 86 return null; 87 } 88 List<Admin> adminList = adminService.findAdmin(username, password, start, limit); 89 return adminList; 90 } 91 92 /** 93 * 插入一個用戶並返回主鍵 94 * 注意:get請求也會自動裝配(即將前臺傳入的username和password傳入admin) 95 */ 96 @ResponseBody 97 @RequestMapping("/insert") 98 public Admin insertAdminWithBackId(Admin admin){ 99 return adminService.insertAdminWithBackId(admin); 100 } 101 }
說明,這個類只修改了兩個方法login()和findAdmin()。
測試:
注意:
String key = Base64.encodeBase64String(AESUtil.getKey());
總結:
疑問:
解答:方案
一、使用權限框架shiro
注意:shiroshiro保存了你的帳戶名(編碼)和可記住密碼的有效時間到cookie裏,下次會把帳戶名帶過來,能夠容許執行一些非敏感操做。根據你後臺設定的權限;固然對於敏感權限的話,必定要從新登陸才行。(固然,因爲仍是存在cookie裏,可能仍是存在會被劫持的狀況)
具體看下邊圖片:("記住我"的功能)
二、https(即便被劫取到,也不能重傳)
這個是能夠解決上邊問題的,可是https沒鬧懂。
三、IP變更
1)客戶端登錄的時候,記錄客戶端IP到數據庫
2)下次登陸從request中獲取IP並與前一次記錄的IP做對比,若是IP沒有發生變化,則得直接進入已登陸狀態了;若是IP發生了變化,則從新登陸,登陸以後,修改IP
3)以後的過程如上
固然,對於以上這種方式,還有一種改進,就是將用戶登陸的IP放入緩存(固然能夠指定緩存時間),這樣每次就能夠從緩存中查IP了。
可是這種方式有個問題,就是說同一臺電腦換一個網絡環境,這時候IP就變了,就得從新登錄了。
四、數字簽名算法
數字簽名的三個做用中的一個就是"認證數據來源",感受這會是一個思路,可是具體怎麼實現還沒想好。若是你們有想好的,幫指點一下!
數字簽名算法的具體實現見"Java加密與解密"《第十四章 數字簽名算法--RSA》,連接以下:
http://www.cnblogs.com/java-zhao/p/5091363.html
有關於session的細節與解決方案,之後再說!