單點登入系統知識點總結
單點登入系統
單點登入要解決的核心問題是:一站點登入,多站點可以同時訪問。
單點登入業務流程
1. 登入頁面,使用者登入
2. 判斷使用者名稱和密碼是否正確
3. 登入成功後通過uuid生成token,token相當於原來的jsessionid。
4. 把使用者資訊儲存到redis中,key就是token,value就是使用者物件轉成的json。
5. 設定key的過期時間,模擬session的過期時間,一般為半個小時。
6. 將token寫入Cookie。Cookie要繫結二級域名,實現跨域。
7. 將token及登入成功的結果響應給瀏覽器。
8. 瀏覽器彈出登入成功提示框,點選確定,頁面跳轉到首頁
9. 在首頁渲染的過程中,傳送ajax請求給單點登入系統,服務端通過token獲取到使用者資訊返回瀏覽器,瀏覽器端從返回的使用者資訊中獲取到當前登入使用者的使用者名稱,顯示到首頁上。(這裡會有js跨域問題,使用jsonp解決。Ajax跨域訪問的時候,請求正常發過去了,服務端也把結果響應給瀏覽器,但是當Ajax要拿這個響應回來的資料的時候,瀏覽器不讓拿。跨域的根在這。)
Service層
@Override public e3Result login(String username, String password) { // 1、判斷使用者名稱密碼是否正確。 TbUserExample example = new Criteria criteria = example.createCriteria(); criteria.andUsernameEqualTo(username); //查詢使用者資訊 List<TbUser> list = userMapper.selectByExample(example); if (list == null || list.size() == 0) { return e3Result.build(400, "使用者名稱或密碼錯誤"); } TbUser user = list.get(0); //校驗密碼 if (!user.getPassword().equals(DigestUtils.md5DigestAsHex(password.getBytes()))) { return e3Result.build(400, "使用者名稱或密碼錯誤"); } // 2、登入成功後生成token。Token相當於原來的jsessionid,字串,可以使用uuid。 String token = UUID.randomUUID().toString(); // 3、把使用者資訊儲存到redis。Key就是token,value就是TbUser物件轉換成json。 // 4、使用String型別儲存Session資訊。可以使用“字首:token”為key user.setPassword(null); jedisClient.set(USER_INFO + ":" + token, JsonUtils.objectToJson(user)); // 5、設定key的過期時間。模擬Session的過期時間。一般半個小時。 jedisClient.expire(USER_INFO + ":" + token, SESSION_EXPIRE); // 6、返回e3Result包裝token。 return e3Result.ok(token); } |
表現層
@RequestMapping(value="/user/login", method=RequestMethod.POST) @ResponseBody public e3Result login(String username, String password, HttpServletRequest request, HttpServletResponse response) { // 1、接收兩個引數。 // 2、呼叫Service進行登入。 e3Result result = userService.login(username, password); // 3、從返回結果中取token,寫入cookie。Cookie要跨域。 String token = result.getData().toString(); CookieUtils.setCookie(request, response, COOKIE_TOKEN_KEY, token); // 4、響應資料。Json資料。e3Result,其中包含Token。 return result; } |
根據token查詢使用者資訊
Service層
@Override public e3Result getUserByToken(String token) { // 2、根據token查詢redis。 String json = jedisClient.get(USER_INFO + ":" + token); if (StringUtils.isBlank(json)) { // 3、如果查詢不到資料。返回使用者已經過期。 return e3Result.build(400, "使用者登入已經過期,請重新登入。"); } // 4、如果查詢到資料,說明使用者已經登入。 // 5、需要重置key的過期時間。 jedisClient.expire(USER_INFO + ":" + token, SESSION_EXPIRE); // 6、把json資料轉換成TbUser物件,然後使用e3Result包裝並返回。 TbUser user = JsonUtils.jsonToPojo(json, TbUser.class); return e3Result.ok(user); } |
表現層
@RequestMapping("/user/token/{token}") @ResponseBody public e3Result getUserByToken(@PathVariable String token) { e3Result result = userService.getUserByToken(token); return result; } |
我想訪問訂單系統,在展示訂單確認頁面之前,需要對使用者身份進行認證,要求使用者必須登入。使用springmvc的攔截器實現。需要實現一個介面HandlerInterceptor介面。
業務流程如下:
1. 從Cookie中取token
2. 如果token為空,說明使用者未登入,需要跳轉到登入頁面進行登入。在重定向到登入頁面時,要將當前系統的url作為引數傳過來,以便登入成功之後能再跳轉到當前系統。
3. 如果token不為空,要呼叫單點登入系統中的服務,根據token驗證使用者資訊是否存在
如果不存在,說明使用者登入已經過期,需要跳轉到登入頁面重新登入。在重定向到登入頁面時,要將當前系統的url作為引數傳過來,以便登入成功之後能再跳轉到當前系統。
如果存在,說明使用者已經登入,根據當前使用者的使用者id查詢訂單資訊,將結果資料返回給瀏覽器。
-------------------------------------------------------------------------------
在叢集環境下,如果用Session來存放使用者的登入資訊,那麼會出現要求使用者多次登入的情況.
為解決這個問題,我們可以通過使用Session複製來解決,但是Session複製的使用會限制叢集中伺服器節點的數量,一旦叢集中的伺服器數量過多,叢集的效能就會由高到低呈拋物線形式下滑。
基於此,我們想著使用一個專門的Session伺服器來統一管理Session,這樣一來,叢集中的節點數量就沒有限制了。這個專門的Session伺服器就是單點登入系統.
單點登入系統使用redis模擬Session,實現Session的統一管理。
單點登入的業務流程如下:
1. 使用者在登入頁面進行登入,如果登入驗證不成功,返回登入頁面重新登入。
2. 登入成功後,服務端通過uuid隨機生成一個名為token的字串(相當於來的jsessionid),作為key值,並且將封裝有當前登入使用者資訊的物件轉成json字串作為values值,以鍵值對的形式存入Redis快取資料庫。
3. 設定key的過期時間,一般設為半個小時。
4. 將token字串寫入Cookie中,返回給瀏覽器。在這個過程中,一般我們會同時響應一個重定向操作,讓頁面跳轉到首頁。
5. 在頁面跳轉到首頁以後,我們有一個要求:就是要在首頁上顯示出當前登入使用者的使用者名稱。這個時候,我們採取的辦法是在首頁載入完畢之後,向單點登入系統傳送一個ajax請求,請求的引數就是Cookie。
6. 單點登入系統接收到請求之後,取出Cookie裡面存放的token字串,去Redis中查詢響應的使用者資訊,若查不到,會重定向到登入頁面重新登入;若查到,會將使用者資訊由json字串轉成使用者物件,並返給瀏覽器端。同時重設key的過期時間。
7. 瀏覽器拿到返回過來的使用者物件,從中取出使用者名稱,嵌入到首頁的指定位置即可。注意:在這個過程中會產生一個跨域訪問的問題。
什麼是跨域問題?
答:域名不同或者域名相同埠號不同均可稱為跨域。首頁位置前臺系統中,首頁通過ajax請求訪問單點登入系統,兩個系統彼此獨立,部署在不同的Tomcat伺服器上,埠號不同,是跨域請求。跨域請求能發過去,並且瀏覽器能得到服務端返回的使用者資訊,但是ajax拿不到這個響應資訊,瀏覽器不讓使用。
如何解決跨域訪問問題?
答:使用jsonp技術。
至此,登入成功!