淘淘商城——單點登入之使用者登入
我們先來看下使用者登入流程圖,如下圖所示。使用者登入涉及到三個部分,第一部分是淘淘商城前臺工程,第二部分是單點登入服務,第三部分是Redis服務。具體流程下圖已經說的很明白了,我就不再囉嗦一遍了,相比於傳統的登入,我們沒有把使用者登入資訊存在session當中,而是存放到了Redis資料庫當中。
對於上面的使用者登入流程圖,我仍粗略作一下解釋,使用者登入的處理流程大致可分為以下幾個步驟:
- 使用者在登入頁面提交使用者名稱和密碼。
- 使用者登入成功後生成token,token就相當於原來的jsessionid,它是一個字串,可以使用uuid演算法來生成。
- 把使用者資訊儲存到Redis資料庫裡面,key就是token,value就是TbUser物件轉換成的json,Redis儲存資訊時建議使用String型別來儲存,並且可以使用“字首:token”作為key。
- 設定key的過期時間,相當於模擬Session的過期時間,一般置為半個小時。
- 把token寫入cookie中。
- cookie需要跨域,並且是跨多個二級域名,例如www.taotao.com、sso.taotao.com、order.taotao.com。
- 設定cookie的有效期,建議設定為關閉瀏覽器cookie即失效。
- 登入成功。
下面我們就來實現服務端的登入業務。
由於使用者登入只涉及到單表操作,因此我們使用逆向工程生成的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是否已經被儲存起來了,如下圖所示,發現已經被儲存起來了。