前言:
前面我們已經(jīng)實(shí)現(xiàn)了數(shù)據(jù)的校驗和注冊功能烟逊,下面是實(shí)現(xiàn)登錄功能;用戶的登錄涉及到三部分
1.前臺工程
2.單點(diǎn)登錄服務(wù)
3.redis服務(wù)眷唉;相比于傳統(tǒng)服務(wù)冬阳,我們沒有把用戶信息存在session中,而是放在redis中
登錄的流程:
登錄的處理流程:
1氯窍、登錄頁面提交用戶名密碼狼讨。
2、登錄成功后生成token布隔。Token相當(dāng)于原來的jsessionid衅檀,字符串计济,可以使用uuid学密。
3腻暮、把用戶信息保存到redis具垫。Key就是token筝蚕,value就是TbUser對象轉(zhuǎn)換成json起宽。
4坯沪、使用String類型保存Session信息≡逄牵可以使用“前綴:token”為key
5、設(shè)置key的過期時間潘拱。模擬Session的過期時間。一般半個小時禽最。
6呛占、把token寫入cookie中。
7帜篇、Cookie需要跨域。例如www.taotao.com\sso.taotao.com\order.taotao.com诫咱,可以使用工具類笙隙。
8、Cookie的有效期坎缭。關(guān)閉瀏覽器失效竟痰。
9、登錄成功掏呼。
1.實(shí)現(xiàn)登錄功能
- 功能分析
請求的url:/user/login
請求的方法:POST
參數(shù):username凯亮、password,表單提交的數(shù)據(jù)。可以使用方法的形參接收。
返回值:json數(shù)據(jù)疏之,使用TaotaoResult包含一個token。 - Dao層
查詢tb_user表索抓。單表查詢。可以使用逆向工程另患。 - Service層
參數(shù):
1薯嗤、用戶名:String username
2玻褪、密碼:String password
返回值:TaotaoResult捻浦,包裝token。
業(yè)務(wù)邏輯:
1、判斷用戶名密碼是否正確凡怎。
2、登錄成功后生成token捆昏。Token相當(dāng)于原來的jsessionid举户,字符串拐云,可以使用uuid泳桦。
3演痒、把用戶信息保存到redis兆沙。Key就是token龙誊,value就是TbUser對象轉(zhuǎn)換成json。
4推溃、使用String類型保存Session信息助赞。可以使用“前綴:token”為key
5掠手、設(shè)置key的過期時間做祝。模擬Session的過期時間轩性。一般半個小時脯厨。
6壳澳、返回TaotaoResult包裝token铃彰。
@Override
public TaotaoResult login(String username, String password) {
// 1竹揍、判斷用戶名密碼是否正確。
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, "用戶名或密碼錯誤");
}
TbUser user = list.get(0);
//校驗密碼
if (!user.getPassword().equals(DigestUtils.md5DigestAsHex(password.getBytes()))) {
return TaotaoResult.build(400, "用戶名或密碼錯誤");
}
// 2邪铲、登錄成功后生成token鬼佣。Token相當(dāng)于原來的jsessionid,字符串霜浴,可以使用uuid晶衷。
String token = UUID.randomUUID().toString();
// 3、把用戶信息保存到redis阴孟。Key就是token晌纫,value就是TbUser對象轉(zhuǎn)換成json。
// 4永丝、使用String類型保存Session信息锹漱。可以使用“前綴:token”為key
user.setPassword(null);
jedisClient.set(USER_INFO + ":" + token, JsonUtils.objectToJson(user));
// 5慕嚷、設(shè)置key的過期時間哥牍。模擬Session的過期時間。一般半個小時喝检。
jedisClient.expire(USER_INFO + ":" + token, SESSION_EXPIRE);
// 6嗅辣、返回TaotaoResult包裝token。
return TaotaoResult.ok(token);
}
login接口中用到了配置文件挠说,如下圖所示澡谭。
#redis key的名稱
USER_SESSION=USER_SESSION
#session的過期時間
SESSION_EXPIRE=1800
- 發(fā)布服務(wù)和引用服務(wù)
不用做任何修改~ - Controller
請求的url:/user/login
請求的方法:POST
參數(shù):username、password损俭,表單提交的數(shù)據(jù)蛙奖。可以使用方法的形參接收杆兵。
HttpServletRequest雁仲、HttpServletResponse
返回值:json數(shù)據(jù),使用TaotaoResult包含一個token琐脏。
我們在登錄的時候需要把token保存到cookie中攒砖,為了方便我們操作,我們封住了了一個工具類
package com.taotao.utils;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* 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;
}
/**
* 設(shè)置Cookie的值 不設(shè)置生效時間默認(rèn)瀏覽器關(guān)閉即失效,也不編碼
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue) {
setCookie(request, response, cookieName, cookieValue, -1);
}
/**
* 設(shè)置Cookie的值 在指定時間內(nèi)生效,但不編碼
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, int cookieMaxage) {
setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);
}
/**
* 設(shè)置Cookie的值 不設(shè)置生效時間,但編碼
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, boolean isEncode) {
setCookie(request, response, cookieName, cookieValue, -1, isEncode);
}
/**
* 設(shè)置Cookie的值 在指定時間內(nèi)生效, 編碼參數(shù)
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, int cookieMaxage, boolean isEncode) {
doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);
}
/**
* 設(shè)置Cookie的值 在指定時間內(nèi)生效, 編碼參數(shù)(指定編碼)
*/
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);
}
/**
* 設(shè)置Cookie的值,并使其在指定時間內(nèi)生效
*
* @param cookieMaxage cookie生效的最大秒數(shù)
*/
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) {// 設(shè)置域名的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();
}
}
/**
* 設(shè)置Cookie的值祭衩,并使其在指定時間內(nèi)生效
*
* @param cookieMaxage cookie生效的最大秒數(shù)
*/
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) {// 設(shè)置域名的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;
}
}
下面利用這個工具類實(shí)現(xiàn)登錄
@RequestMapping(value="/user/login", method=RequestMethod.POST)
@ResponseBody
public TaotaoResult login(String username, String password,
HttpServletRequest request, HttpServletResponse response) {
// 1灶体、接收兩個參數(shù)。
// 2掐暮、調(diào)用Service進(jìn)行登錄蝎抽。
TaotaoResult result = userService.login(username, password);
// 3、從返回結(jié)果中取token路克,寫入cookie樟结。Cookie要跨域。
String token = result.getData().toString();
CookieUtils.setCookie(request, response, COOKIE_TOKEN_KEY, token);
// 4精算、響應(yīng)數(shù)據(jù)瓢宦。Json數(shù)據(jù)。TaotaoResult灰羽,其中包含Token驮履。
return result;
}
下面我們來測試,由于在taotao-common中添加了工具類廉嚼,因此需要重新打包taotao-common工程到本地maven倉庫玫镐,由于在taotao-sso-interface添加了接口,因此我們需要把taotao-sso工程重新打包到本地maven倉庫怠噪。
在啟動工程前要保證zookeeper和redis服務(wù)器處于啟動狀態(tài)?炙啤!傍念!
-
測試
打包完后矫夷,我們啟動taotao-sso工程和taotao-sso-web工程,啟動之后憋槐,我們還是使用RestClient3.5來測試双藕。我們修改下URL地址將/user/register修改為/user/login,然后點(diǎn)擊下面那個圈住的圖標(biāo)秦陋。
可以看到彈出一個小框蔓彩,我們點(diǎn)擊"是"
登錄我們只用用戶名和密碼治笨,不用電話和郵箱驳概,因此我們把電話和郵箱兩個參數(shù)去掉,選中email和phone兩行旷赖,右鍵點(diǎn)擊"Remove Selected"顺又。
去掉email和phone之后,雙擊value那一個框進(jìn)入編輯狀態(tài)等孵,修改username的值為"zhangsan6"稚照,如下圖,點(diǎn)擊"Generate"。
點(diǎn)擊執(zhí)行按鈕果录,可以看到返回的結(jié)果狀態(tài)碼是200上枕,data中存放的是token的值。說明登錄成功了弱恒,同時也說明我們的登錄接口正確
2.通過token獲取用戶的信息
- 功能分析
請求的url:/user/token/{token}
參數(shù):String token需要從url中取辨萍。
返回值:json數(shù)據(jù)。使用TaotaoResult包裝Tbuser對象返弹。
業(yè)務(wù)邏輯:
1锈玉、從url中取參數(shù)。
2义起、根據(jù)token查詢redis拉背。
3、如果查詢不到數(shù)據(jù)默终。返回用戶已經(jīng)過期椅棺。
4、如果查詢到數(shù)據(jù)齐蔽,說明用戶已經(jīng)登錄土陪。
5、需要重置key的過期時間肴熏。
6鬼雀、把json數(shù)據(jù)轉(zhuǎn)換成TbUser對象,然后使用TaotaoResult包裝并返回蛙吏。 - Dao層
使用JedisClient對象源哩。(因為我們的數(shù)據(jù)已經(jīng)保存在redis中了,這樣就不用再訪問數(shù)據(jù)庫) - Service層
參數(shù):String token
返回值:TaotaoResult
@Override
public TaotaoResult getUserByToken(String token) {
// 2鸦做、根據(jù)token查詢redis励烦。
String json = jedisClient.get(USER_INFO + ":" + token);
if (StringUtils.isBlank(json)) {
// 3、如果查詢不到數(shù)據(jù)泼诱。返回用戶已經(jīng)過期坛掠。
return TaotaoResult.build(400, "用戶登錄已經(jīng)過期,請重新登錄治筒。");
}
// 4屉栓、如果查詢到數(shù)據(jù),說明用戶已經(jīng)登錄耸袜。
// 5友多、需要重置key的過期時間。
jedisClient.expire(USER_INFO + ":" + token, SESSION_EXPIRE);
// 6堤框、把json數(shù)據(jù)轉(zhuǎn)換成TbUser對象域滥,然后使用TaotaoResult包裝并返回纵柿。
TbUser user = JsonUtils.jsonToPojo(json, TbUser.class);
return TaotaoResult.ok(user);
}
-Controller
請求的url:/user/token/{token}
參數(shù):String token需要從url中取。
返回值:json數(shù)據(jù)启绰。使用TaotaoResult包裝Tbuser對象昂儒。
@RequestMapping("/user/token/{token}")
@ResponseBody
public TaotaoResult getUserByToken(@PathVariable String token) {
TaotaoResult result = userService.getUserByToken(token);
return result;
}
- 測試
把token復(fù)制出來
我們使用新的token來測試,訪問地址http://localhost:8088/user/token/ba9bb30f-1a61-4b61-9931-6b94d1aeefdf委可,結(jié)果如下圖所示荆忍,發(fā)現(xiàn)正常返回了用戶的信息
3.安全退出
所謂的安全退出就是將token從redis中刪除
- 功能分析
請求的url:/user/logout/{token}
參數(shù):String token需要從url中取。
返回值:json數(shù)據(jù)撤缴。使用TaotaoResult包裝Tbuser對象刹枉。
業(yè)務(wù)邏輯:
1、從url中取參數(shù)屈呕。
2微宝、根據(jù)key刪除redis中token中的數(shù)據(jù)(直接設(shè)置為過期)
4.使用TaotaoResult返回結(jié)果 - Service層
public TaotaoResult logout(String token) {
String key = USER_SESSION + ":" + token;
//根據(jù)key刪除redis中token中的數(shù)據(jù)(直接設(shè)置為過期)
jedisClient.expire(key,0);
return TaotaoResult.ok();
}
- Controller層
//安全退出
@RequestMapping(value = "/user/logout/{token}",method = RequestMethod.GET)
@ResponseBody
public TaotaoResult logout(@PathVariable String token){
TaotaoResult result = userService.logout(token);
return result;
}
- 測試
我們又增加了一個新的接口,記得要install一下虎眨,把工程打包到本地
為了測試蟋软,我們還需要重新發(fā)送下登錄請求,獲取一個新的token(原來登錄的token已經(jīng)過期了嗽桩,設(shè)置的過期時間是30分鐘)岳守,如下圖所示。
我們使用新的token來測試安全退出碌冶,在地址欄訪問http://localhost:8088/user/logout/ba9bb30f-1a61-4b61-9931-6b94d1aeefdf湿痢,結(jié)果如下圖所示
這時我們再嘗試通過該token來獲取用戶信息,在地址欄訪問http://localhost:8088/user/token/ba9bb30f-1a61-4b61-9931-6b94d1aeefdf扑庞,結(jié)果如下圖所示譬重,顯示token已過期(從登錄到現(xiàn)在還不到30分鐘),這說明我們的安全退出沒問題了罐氨。