1. 程式人生 > >淘淘商城——單點登入之使用者登入

淘淘商城——單點登入之使用者登入

我們先來看下使用者登入流程圖,如下圖所示。使用者登入涉及到三個部分,第一部分是淘淘商城前臺工程,第二部分是單點登入服務,第三部分是Redis服務。具體流程下圖已經說的很明白了,我就不再囉嗦一遍了,相比於傳統的登入,我們沒有把使用者登入資訊存在session當中,而是存放到了Redis資料庫當中。
這裡寫圖片描述
對於上面的使用者登入流程圖,我仍粗略作一下解釋,使用者登入的處理流程大致可分為以下幾個步驟:

  1. 使用者在登入頁面提交使用者名稱和密碼。
  2. 使用者登入成功後生成token,token就相當於原來的jsessionid,它是一個字串,可以使用uuid演算法來生成。
  3. 把使用者資訊儲存到Redis資料庫裡面,key就是token,value就是TbUser物件轉換成的json,Redis儲存資訊時建議使用String型別來儲存,並且可以使用“字首:token”作為key。
  4. 設定key的過期時間,相當於模擬Session的過期時間,一般置為半個小時。
  5. 把token寫入cookie中。
  6. cookie需要跨域,並且是跨多個二級域名,例如www.taotao.com、sso.taotao.com、order.taotao.com。
  7. 設定cookie的有效期,建議設定為關閉瀏覽器cookie即失效。
  8. 登入成功。

下面我們就來實現服務端的登入業務。
由於使用者登入只涉及到單表操作,因此我們使用逆向工程生成的dao層程式碼即可。
下面我們來編寫Service層的程式碼。首先在taotao-sso-interface工程的com.taotao.sso.service包下新建一個使用者登入的介面——UserLoginService.java,如下圖所示。
這裡寫圖片描述


緊接著在taotao-sso-service工程的com.taotao.sso.service.impl包中編寫UserLoginService介面的實現類——UserLoginServiceImpl.java,實現完之後,UserLoginServiceImpl類的所有程式碼如下:

/**
 * 使用者登入處理Service
 * <p>Title: UserLoginServiceImpl</p>
 * <p>Description: </p>
 * <p>Company: www.itcast.cn</p> 
 * @version
1.0 */
@Service public class UserLoginServiceImpl implements UserLoginService { @Autowired private TbUserMapper userMapper; @Autowired private JedisClient jedisClient; @Value("${SESSION_PRE}") private String SESSION_PRE; @Value("${SESSION_EXPIRE}") private Integer SESSION_EXPIRE; @Override public TaotaoResult login(String username, String password) { // 判斷使用者名稱和密碼是否正確 TbUserExample example = new TbUserExample(); Criteria criteria = example.createCriteria(); criteria.andUsernameEqualTo(username); List<TbUser> list = userMapper.selectByExample(example); if (list == null || list.size() == 0) { return TaotaoResult.build(400, "使用者名稱或密碼錯誤"); } // 校驗密碼,密碼要進行md5加密後再校驗 TbUser user = list.get(0); if (!DigestUtils.md5DigestAsHex(password.getBytes()).equals(user.getPassword())) { return TaotaoResult.build(400, "使用者名稱或密碼錯誤"); } // 生成一個token String token = UUID.randomUUID().toString(); // 把使用者資訊儲存到Redis資料庫裡面去 // key就是token,value就是使用者物件轉換成json user.setPassword(null); // 為了安全,就不要把密碼儲存到Redis資料庫裡面去,因為這樣太危險了,因此我們先把密碼置空 jedisClient.set(SESSION_PRE + ":" + token, JsonUtils.objectToJson(user)); // 設定key的過期時間 jedisClient.expire(SESSION_PRE + ":" + token, SESSION_EXPIRE); // 返回結果 return TaotaoResult.ok(token); } }

UserLoginServiceImpl類的程式碼中SESSION_PRE和SESSION_EXPIRE這兩個欄位的值配置在配置檔案中,如下圖所示。
這裡寫圖片描述
下面我們便釋出一下服務,如下圖所示。即在applicationContext-service.xml配置檔案中新增如下配置:

<dubbo:service interface="com.taotao.sso.service.UserLoginService" ref="userLoginServiceImpl" timeout="300000" />

這裡寫圖片描述
這樣我們的Service層程式碼便寫完了。
下面我們接著來編寫Controller層的程式碼。首先我們新增對dubbo服務的引用,即在taotao-sso-web工程下的springmvc.xml配置檔案中新增如下配置:

<dubbo:reference interface="com.taotao.sso.service.UserLoginService" id="userLoginService" />

這裡寫圖片描述
接著來看一下使用者登入的介面文件,如下圖所示。
這裡寫圖片描述
我們需要把token儲存到cookie當中,為了方便的操作cookie,我們特意封裝了一個工具類,由於該工具類有可能被多個服務使用,因此我們最好把該工具類放到taotao-common工程中,如下圖所示。
這裡寫圖片描述
可以看到當前導包是有錯誤的,要解決這個問題就要在taotao-common工程中新增對servlet-api的依賴,新增完依賴之後CookieUtils類就不報錯了,如下圖所示。
這裡寫圖片描述
為了方便大家複製,現將CookieUtils工具類的程式碼給出。

/**
 * 
 * Cookie 工具類
 *
 */
public final class CookieUtils {

    /**
     * 得到Cookie的值, 不編碼
     * 
     * @param request
     * @param cookieName
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName) {
        return getCookieValue(request, cookieName, false);
    }

    /**
     * 得到Cookie的值,
     * 
     * @param request
     * @param cookieName
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {
        Cookie[] cookieList = request.getCookies();
        if (cookieList == null || cookieName == null) {
            return null;
        }
        String retValue = null;
        try {
            for (int i = 0; i < cookieList.length; i++) {
                if (cookieList[i].getName().equals(cookieName)) {
                    if (isDecoder) {
                        retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");
                    } else {
                        retValue = cookieList[i].getValue();
                    }
                    break;
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return retValue;
    }

    /**
     * 得到Cookie的值,
     * 
     * @param request
     * @param cookieName
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {
        Cookie[] cookieList = request.getCookies();
        if (cookieList == null || cookieName == null) {
            return null;
        }
        String retValue = null;
        try {
            for (int i = 0; i < cookieList.length; i++) {
                if (cookieList[i].getName().equals(cookieName)) {
                    retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);
                    break;
                }
            }
        } catch (UnsupportedEncodingException e) {
             e.printStackTrace();
        }
        return retValue;
    }

    /**
     * 設定Cookie的值 不設定生效時間預設瀏覽器關閉即失效,也不編碼
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
            String cookieValue) {
        setCookie(request, response, cookieName, cookieValue, -1);
    }

    /**
     * 設定Cookie的值 在指定時間內生效,但不編碼
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
            String cookieValue, int cookieMaxage) {
        setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);
    }

    /**
     * 設定Cookie的值 不設定生效時間,但編碼
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
            String cookieValue, boolean isEncode) {
        setCookie(request, response, cookieName, cookieValue, -1, isEncode);
    }

    /**
     * 設定Cookie的值 在指定時間內生效, 編碼引數
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
            String cookieValue, int cookieMaxage, boolean isEncode) {
        doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);
    }

    /**
     * 設定Cookie的值 在指定時間內生效, 編碼引數(指定編碼)
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
            String cookieValue, int cookieMaxage, String encodeString) {
        doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);
    }

    /**
     * 刪除Cookie帶cookie域名
     */
    public static void deleteCookie(HttpServletRequest request, HttpServletResponse response,
            String cookieName) {
        doSetCookie(request, response, cookieName, "", -1, false);
    }

    /**
     * 設定Cookie的值,並使其在指定時間內生效
     * 
     * @param cookieMaxage cookie生效的最大秒數
     */
    private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,
            String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) {
        try {
            if (cookieValue == null) {
                cookieValue = "";
            } else if (isEncode) {
                cookieValue = URLEncoder.encode(cookieValue, "utf-8");
            }
            Cookie cookie = new Cookie(cookieName, cookieValue);
            if (cookieMaxage > 0)
                cookie.setMaxAge(cookieMaxage);
            if (null != request) {// 設定域名的cookie
                String domainName = getDomainName(request);
                System.out.println(domainName);
                if (!"localhost".equals(domainName)) {
                    cookie.setDomain(domainName);
                }
            }
            cookie.setPath("/");
            response.addCookie(cookie);
        } catch (Exception e) {
             e.printStackTrace();
        }
    }

    /**
     * 設定Cookie的值,並使其在指定時間內生效
     * 
     * @param cookieMaxage cookie生效的最大秒數
     */
    private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,
            String cookieName, String cookieValue, int cookieMaxage, String encodeString) {
        try {
            if (cookieValue == null) {
                cookieValue = "";
            } else {
                cookieValue = URLEncoder.encode(cookieValue, encodeString);
            }
            Cookie cookie = new Cookie(cookieName, cookieValue);
            if (cookieMaxage > 0)
                cookie.setMaxAge(cookieMaxage);
            if (null != request) {// 設定域名的cookie
                String domainName = getDomainName(request);
                System.out.println(domainName);
                if (!"localhost".equals(domainName)) {
                    cookie.setDomain(domainName);
                }
            }
            cookie.setPath("/");
            response.addCookie(cookie);
        } catch (Exception e) {
             e.printStackTrace();
        }
    }

    /**
     * 得到cookie的域名
     */
    private static final String getDomainName(HttpServletRequest request) {
        String domainName = null;

        String serverName = request.getRequestURL().toString();
        if (serverName == null || serverName.equals("")) {
            domainName = "";
        } else {
            serverName = serverName.toLowerCase();
            serverName = serverName.substring(7);
            final int end = serverName.indexOf("/");
            serverName = serverName.substring(0, end);
            final String[] domains = serverName.split("\\.");
            int len = domains.length;
            if (len > 3) {
                // www.xxx.com.cn
                domainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];
            } else if (len <= 3 && len > 1) {
                // xxx.com or xxx.cn
                domainName = "." + domains[len - 2] + "." + domains[len - 1];
            } else {
                domainName = serverName;
            }
        }

        if (domainName != null && domainName.indexOf(":") > 0) {
            String[] ary = domainName.split("\\:");
            domainName = ary[0];
        }
        return domainName;
    }

}

接下來我們要在taotao-sso-web工程的UserController類中新增如下圖所示標註的程式碼。
這裡寫圖片描述
為方便大家複製,現將UserController類的程式碼貼出。

/**
 * 使用者管理Controller
 * <p>Title: UserController</p>
 * <p>Description: </p>
 * <p>Company: www.itcast.cn</p> 
 * @version 1.0
 */
@Controller
public class UserController {

    @Autowired
    private UserRegisterService userRegisterService;

    @Autowired
    private UserLoginService userLoginService;

    @Value("${COOKIE_TOKEN_KEY}")
    private String COOKIE_TOKEN_KEY;

    @RequestMapping(value="/user/check/{param}/{type}", method=RequestMethod.GET)
    @ResponseBody
    public TaotaoResult checkUserInfo(@PathVariable String param, @PathVariable Integer type) {
        TaotaoResult result = userRegisterService.checkUserInfo(param, type);
        return result;
    }

    @RequestMapping(value="/user/register", method=RequestMethod.POST)
    @ResponseBody
    public TaotaoResult registerUser(TbUser user) {
        TaotaoResult taotaoResult = userRegisterService.createUser(user);
        return taotaoResult;
    }

    @RequestMapping(value="/user/login", method=RequestMethod.POST)
    @ResponseBody
    public TaotaoResult userLogin(String username, String password,
            HttpServletRequest request, HttpServletResponse response) {
        TaotaoResult taotaoResult = userLoginService.login(username, password);
        // 取出token
        String token = taotaoResult.getData().toString();
        // 在返回結果之前,設定cookie(即將token寫入cookie)
        // 1.cookie怎麼跨域?
        // 2.如何設定cookie的有效期?
        CookieUtils.setCookie(request, response, COOKIE_TOKEN_KEY, token);
        // 返回結果
        return taotaoResult;
    }
}

UserController類的程式碼中COOKIE_TOKEN_KEY這個欄位的值配置在resource.properties配置檔案中,如下圖所示。
這裡寫圖片描述
下面我們來進行測試,由於在taotao-common中添加了一個工具類,因此需要重新打包taotao-common工程到本地maven倉庫,又由於在taotao-sso-interface工程中新添加了一個介面,因此我們還需要把taotao-sso工程重新打包到本地maven倉庫。注意:在啟動工程前要保證zookeeper和Redis伺服器處於啟動狀態!!! Redis伺服器的IP及埠要與applicationContext-redis.xml配置檔案一致,如下圖所示。
這裡寫圖片描述
以上準備工作做好之後,我們啟動taotao-sso工程和taotao-sso-web工程,啟動成功之後,我們還是使用RESTClient3.5來測試,最關鍵的兩步我已截圖。登入我們只用使用者名稱和密碼,不用電話和郵箱,所以新增完使用者名稱和密碼這兩個引數之後,點選”Generate”,如下圖所示。
這裡寫圖片描述
點選執行按鈕,可以看到返回的結果狀態碼是200,data中存放的是token的值。說明登入成功了,同時也說明我們的登入介面正確。
這裡寫圖片描述
登入成功了,我們利用redis-desktop-manager工具來檢視token是否已經被儲存起來了,如下圖所示,發現已經被儲存起來了。
這裡寫圖片描述