這一篇部落格來講解下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的共享.