這一篇博客來講解下babasport這個項目中使用的Login功能, 當然這里說的只是其中的一些簡單的部分, 記錄在此 方便以后查閱.
一: 去登錄頁面
首先我們登錄需要注意的事項是, 當用戶點擊登錄按鈕時,轉入登錄頁面時也要記住之前用戶是從哪個頁面發送請求過來的, 這樣登錄成功后還能繼續跳回到用戶之前瀏覽的那個頁面.
我們頁面展示顯示的登錄按鈕都是集成在一個common的jsp中, 前台每個頁面都是引用的這個jsp, 所以需要在這個common的jsp中直接添加點擊登錄按鈕跳轉的頁面.
這里點擊登錄按鈕后 就會使用window.location.href="http://localhost:8081/login.aspx?returnUrl="+encodeURIComponent(window.location.href);跳轉到新的頁面, 且這里傳入的參數 是瀏覽器的url, 這個就是為了登錄成功后 還能繼續跳轉到這個頁面來. 而encodeURIComponent是 js自帶的轉義類, 轉義的好處是能夠在url中帶中文重定向后無法接收 且url帶多參數解決&被轉義而無效的情況.
下圖就是跳轉到login頁面前的window.location.href屬性:
二, 處理登錄操作
到了登錄界面后, 查看登陸界面圖, 這里的url參數是經過轉義的:
點擊登錄按鈕 會進入到LoginController.java中:
1 //去登錄頁面 2 @RequestMapping(value="/login.aspx",method=RequestMethod.GET) 3 public String login(){ 4 return "login"; 5 } 6 7 @Autowired 8 private BuyerService buyerService; 9 10 @Autowired 11 private SessionProviderService sessionProviderService; 12 //執行登錄操作 13 @RequestMapping(value="/login.aspx",method=RequestMethod.POST) 14 public String login(String username, String password, String returnUrl,Model model, 15 HttpServletRequest request, HttpServletResponse response){ 16 //1: 判斷用戶名不能為空 17 if(null != username){ 18 //2:判斷密碼不能為空 19 if (null != password){ 20 //3:用戶名必須正確 21 Buyer buyer = buyerService.selectBuyerByusername(username); 22 if(buyer != null){ 23 //4:密碼必須正確 24 if(encodePassword(password).equals(buyer.getPassword())){ 25 //5:設置用戶到Session 26 sessionProviderService.setAttributerForUsername(RequestUtils.getCSessionId(request, response), buyer.getUsername()); 27 //6:回跳之前訪問頁面 28 if(null != returnUrl){ 29 return "redirect:"+returnUrl; 30 }else{ 31 return "redirect:http://localhost:8082/"; 32 } 33 }else { 34 model.addAttribute("error", "密碼輸入錯誤!"); 35 } 36 }else { 37 model.addAttribute("error", "用戶名輸入錯誤!"); 38 } 39 40 }else { 41 model.addAttribute("error", "密碼不能為空!"); 42 } 43 }else { 44 model.addAttribute("error", "用戶名不能為空!"); 45 } 46 47 48 return "login"; 49 } 50 51 //加密 52 public String encodePassword(String password){ 53 54 String algorithm = "MD5"; 55 char[] encodeHex = null; 56 //MD5 57 try { 58 MessageDigest instance = MessageDigest.getInstance(algorithm); 59 byte[] digest = instance.digest(password.getBytes()); 60 61 //十六進制, 在MD5加密的基礎上再次加密 62 encodeHex = Hex.encodeHex(digest); 63 } catch (NoSuchAlgorithmException e) { 64 // TODO Auto-generated catch block 65 e.printStackTrace(); 66 } 67 68 return new String(encodeHex); 69 }
這里使用了MD5加密, 經過MD5加密后在使用十六進制進行加密.
如果登陸成功, 調用sessionProviderService.setAttributerForUsername(RequestUtils.getCSessionId(request, response), buyer.getUsername());
SessionProviderImpl.java:
1 //session存活時間, 單位是分鍾. 2 private Integer exp = 30; 3 public void setExp(Integer exp) { 4 this.exp = exp; 5 } 6 7 8 @Autowired 9 private Jedis jedis; 10 //保存用戶到redis中 注冊: 保存用戶到mysql的同時保存用戶名作為Key 用戶Id當做value 到redis中 11 //jessionId value==用戶名 12 public void setAttributerForUsername(String jessionId, String value){ 13 jedis.set(jessionId + ":USER_NAME", value); 14 jedis.expire(jessionId + ":USER_NAME", 60*exp); 15 }
將username信息保存到Redis中, key是CSessionId:USER_NAME, value是username.
三: 驗證用戶是否登錄
首先看下沒有Login的時候最原始的頁面:
那么顯然這里就不對了, 如果沒有登錄, 那么就只應該顯示[登錄]和[免費注冊], 后面的[退出]和[我的訂單]就不應該顯示的, 那么怎么來驗證是否登錄呢?
這里頭部顯示的內容全都是引用的同一個common的jsp文件, 首先在頁面加載的時候我們應該判斷用戶是否登錄:
如果這里我們直接使用ajax異步去調用獲取用戶是否已經登錄, 這里dataType暫時使用json(jsonp是為了解決跨域問題)
如果我們代碼中也是這樣改動的, 那么會發生什么事情呢?
這里提示不能夠跨域訪問? 那么該怎么去做呢?
上面的截圖已經給出了, 我們傳遞的dataType類型是jsonp, 就意味着我們這個ajax請求時跨域請求.
這里又引出一個新問題, 關於多服務器的問題, 如果用戶登錄時所處的服務器是Tomcat1, 那么登錄后當用戶再次訪問頁面時同樣會做登錄驗證, 這個時候如果是Tomcat2呢?
所以這里就引出了拋棄使用jesseionId的想法,具體的解決方法如圖:
我們自己創建一個CsessionId, 當用戶第一次訪問時, CsessionId為空, 那么 在Tomcat1總創建一個CsessionId, 並且將此CsessionId保存到Redis服務器中, 且返回給瀏覽器.
當用戶第二次訪問, 且由Tomcat2 負責處理時, Tomcat2 通過CsessionId去Redis服務器中查找已存在, 然后就知道了此用戶已經登錄.
下面就看看對於這個CsessionId是如何操作的:
跨域請求后, isLogin接收的參數有一個callBack屬性, 如果是跨域請求, 那么這個參數就會有值.
1 //是否登錄 2 @RequestMapping(value="/isLogin.aspx") 3 public @ResponseBody MappingJacksonValue isLogin(String callback, HttpServletRequest request, HttpServletResponse response) throws IOException{ 4 Integer result = 0; 5 //判斷用戶是否登錄 6 String username = sessionProviderService.getAttributterForUsername(RequestUtils.getCSessionId(request, response)); 7 if (null != username) { 8 result = 1; 9 } 10 11 //返回<script> 類, 這個類支持跨域請求 12 MappingJacksonValue mjv = new MappingJacksonValue(result); 13 //設置jsonpFunction 14 mjv.setJsonpFunction(callback); 15 return mjv; 16 }
這個地方 是先通過RequestUtils獲取CSessionId, 然后再通過CSessionId去獲取到對應的username.
RequestUtils.java:
1 public class RequestUtils { 2 3 //獲取CSessionID 4 public static String getCSessionId(HttpServletRequest request, HttpServletResponse response){ 6 //1, 從Request中取Cookie 7 Cookie[] cookies = request.getCookies(); 8 //2, 從Cookie數據中遍歷查找, 並取CSessionID 9 if (null != cookies && cookies.length > 0) { 10 for (Cookie cookie : cookies) { 11 if ("CSESSIONID".equals(cookie.getName())) { 12 //有, 直接返回 13 return cookie.getValue(); 14 } 15 } 16 } 17 //沒有, 創建一個CSessionId, 並且放到Cookie再返回瀏覽器.返回新的CSessionID 18 String csessionid = UUID.randomUUID().toString().replaceAll("-", ""); 19 //並且放到Cookie中 20 Cookie cookie = new Cookie("CSESSIONID", csessionid); 21 //cookie 每次都帶來, 設置路徑 22 cookie.setPath("/"); 23 //0:關閉瀏覽器 銷毀cookie. 0:立即消失. >0 存活時間,秒 24 cookie.setMaxAge(-1); 25 26 return csessionid; 27 } 28 }
先查看cookies中是否保存的有CSessionId, 如果沒有就新創建一個, 且保存到Cookies中.
SessionProviderImpl.java:
1 public class SessionProviderImpl implements SessionProviderService { 2 3 //session存活時間, 單位是分鍾. 4 private Integer exp = 30; 5 public void setExp(Integer exp) { 6 this.exp = exp; 7 } 8 9 10 @Autowired 11 private Jedis jedis; 12 //保存用戶到redis中 注冊: 保存用戶到mysql的同時保存用戶名作為Key 用戶Id當做value 到redis中 13 //jessionId value==用戶名 14 public void setAttributerForUsername(String jessionId, String value){ 15 jedis.set(jessionId + ":USER_NAME", value); 16 jedis.expire(jessionId + ":USER_NAME", 60*exp); 17 } 18 19 20 //獲取 21 public String getAttributterForUsername(String jessionId){ 22 String value = jedis.get(jessionId + ":USER_NAME"); 23 if(null != value){ 24 //計算session過期時間是 用戶最后一次請求開始計時. 25 jedis.expire(jessionId + ":USER_NAME", 60*exp); 26 return value; 27 } 28 return null; 29 } 30 }
這里的getAttributterForUsername 是通過傳遞進來的CSessionId 去Redis服務器中查找 相應的結果, 如果已經保存了這個 CSessionId, 那么就返回username.
如果已經登陸, 那么就返回1, 在ajax請求的success中再進行相應的處理.
關於登陸的再來梳理一下:
已經登陸, 校驗是否登陸
登陸成功: 會將一個CSessionId保存到Redis中, Redis中設置的這個CSessionId的過期時間為60分鍾.
CSessionId是保存在Cookies中的, 如果Cookies中沒有這個CSessionId則創建一個返回.Cookies中的CSessionId的過期時間也是60分鍾.
校驗是否登錄:通過ajax發送跨域請求, 此時因為已經登陸成功, 所以Cookies中存在這個CSessionId. 然后通過這個CSessionId我們可以在Redis服務器中查出對應的username. 然后Controller將設置一個flag為1, 在ajax中接收到這個flag , 就可以根據判斷來做出相應的處理.
關於Login就這么多, 當然這里的權限驗證遠遠不夠, 而且這里也省略的注冊的內容, 大致需要注意的就是這么多, 其中最 關鍵的就是CSession的使用, 這個可以解決多服務器直接session的共享.