我們先來(lái)看下用戶登錄流程圖,如下圖所示。用戶登錄涉及到三個(gè)部分,第一個(gè)部分是淘淘商城前臺(tái)工程,第二個(gè)部分是單點(diǎn)登錄服務(wù),第三個(gè)部分是Redis服務(wù)。具體流程下圖已經(jīng)說(shuō)的很明白了,我就不再啰嗦一遍了,相比于傳統(tǒng)的登錄,我們并沒(méi)有把用戶登錄信息存在Session當(dāng)中,而是存放到了Redis數(shù)據(jù)庫(kù)當(dāng)中。
上圖中要生成的key就是token,中文翻譯過(guò)來(lái)是令牌的意思,也可以叫做ticket(票據(jù)、憑證)。
對(duì)于上面的用戶登錄流程圖,我仍粗略作一下解釋,用戶登錄的處理流程大致可分為以下幾個(gè)步驟:
下面我們就來(lái)實(shí)現(xiàn)服務(wù)端的登錄業(yè)務(wù)。
首先我們來(lái)編寫(xiě)dao層的代碼。由于用戶登錄只涉及到單表操作,因此我們使用逆向工程生成的dao層代碼即可。
然后我們來(lái)編寫(xiě)service層的代碼。在taotao-sso-interface工程的com.taotao.sso.service包下新建一個(gè)用戶登錄的接口,例如UserLoginService,如下圖所示。
對(duì)應(yīng)地,我們要在taotao-sso-service工程的com.taotao.sso.service.impl包中編寫(xiě)以上接口的一個(gè)實(shí)現(xiàn)類,例如UserLoginServiceImpl。
package com
.taotao
.sso
.service
.impl
;import java
.util
.List
;
import java
.util
.UUID
;import org
.apache
.commons
.lang3
.StringUtils
;
import org
.springframework
.beans
.factory
.annotation
.Autowired
;
import org
.springframework
.beans
.factory
.annotation
.Value
;
import org
.springframework
.stereotype
.Service
;
import org
.springframework
.util
.DigestUtils
;import com
.taotao
.common
.pojo
.TaotaoResult
;
import com
.taotao
.common
.utils
.JsonUtils
;
import com
.taotao
.mapper
.TbUserMapper
;
import com
.taotao
.pojo
.TbUser
;
import com
.taotao
.pojo
.TbUserExample
;
import com
.taotao
.pojo
.TbUserExample
.Criteria
;
import com
.taotao
.sso
.jedis
.JedisClient
;
import com
.taotao
.sso
.service
.UserLoginService
;@Service
public class UserLoginServiceImpl implements UserLoginService {@Autowiredprivate TbUserMapper userMapper
;@Autowiredprivate JedisClient client
;@Value("${USER_INFO}")private String USER_INFO
;@Value("${EXPIRE_TIME}")private Integer EXPIRE_TIME
;@Overridepublic TaotaoResult
login(String username
, String password
) {if (StringUtils
.isEmpty(username
) || StringUtils
.isEmpty(password
)) {return TaotaoResult
.build(400, "用戶名或密碼錯(cuò)誤");}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, "用戶名或密碼錯(cuò)誤");}TbUser user
= list
.get(0);String md5DigestAsHex
= DigestUtils
.md5DigestAsHex(password
.getBytes());if (!md5DigestAsHex
.equals(user
.getPassword())) { return TaotaoResult
.build(400, "用戶名或密碼錯(cuò)誤");}String token
= UUID
.randomUUID().toString();user
.setPassword(null
); client
.set(USER_INFO
+ ":" + token
, JsonUtils
.objectToJson(user
));client
.expire(USER_INFO
+ ":" + token
, EXPIRE_TIME
);return TaotaoResult
.ok(token
);}}
以上UserLoginServiceImpl類的代碼中USER_INFO和EXPIRE_TIME這兩個(gè)字段的值是配置在了resource.properties這個(gè)配置文件中,如下圖所示。
寫(xiě)完service層中的代碼之后,我們還得發(fā)布一下服務(wù),即在applicationContext-service.xml配置文件中添加如下一行配置:
<dubbo:service interface="com.taotao.sso.service.UserLoginService" ref="userLoginServiceImpl" timeout="300000" />
截圖如下:
這樣我們的service層代碼就算是寫(xiě)完了。
下面我們接著來(lái)編寫(xiě)controller層的代碼。首先我們添加對(duì)Dubbo服務(wù)的引用,即在taotao-sso-web工程下的springmvc.xml配置文件中添加如下一行配置:
<dubbo:reference interface="com.taotao.sso.service.UserLoginService" id="userLoginService" />
截圖如下:
然后咱們來(lái)看一下用戶登錄的接口文檔,如下圖所示。
由于我們需要把token保存到Cookie當(dāng)中,為了更加方便地操作Cookie,所以我們特意封裝了一個(gè)工具類,即CookieUtils。考慮到該工具類有可能被多個(gè)服務(wù)調(diào)用,因此最好把該工具類放到taotao-common工程中,如下圖所示。
可以看到當(dāng)前導(dǎo)包是有錯(cuò)誤的,要解決掉這個(gè)錯(cuò)誤就需要在taotao-common工程中添加對(duì)servlet-api的依賴,添加完依賴之后CookieUtils這個(gè)工具類就不會(huì)報(bào)錯(cuò)了,如下圖所示。
為了方便大家復(fù)制,現(xiàn)將CookieUtils工具類的代碼貼出,如下所示。
package com
.taotao
.common
.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
;
public final class CookieUtils {public static String
getCookieValue(HttpServletRequest request
, String cookieName
) {return getCookieValue(request
, cookieName
, false);}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
;}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
;}public static void setCookie(HttpServletRequest request
, HttpServletResponse response
, String cookieName
,String cookieValue
) {setCookie(request
, response
, cookieName
, cookieValue
, -1);}public static void setCookie(HttpServletRequest request
, HttpServletResponse response
, String cookieName
,String cookieValue
, int cookieMaxage
) {setCookie(request
, response
, cookieName
, cookieValue
, cookieMaxage
, false);}public static void setCookie(HttpServletRequest request
, HttpServletResponse response
, String cookieName
,String cookieValue
, boolean isEncode
) {setCookie(request
, response
, cookieName
, cookieValue
, -1, isEncode
);}public static void setCookie(HttpServletRequest request
, HttpServletResponse response
, String cookieName
,String cookieValue
, int cookieMaxage
, boolean isEncode
) {doSetCookie(request
, response
, cookieName
, cookieValue
, cookieMaxage
, isEncode
);}public static void setCookie(HttpServletRequest request
, HttpServletResponse response
, String cookieName
,String cookieValue
, int cookieMaxage
, String encodeString
) {doSetCookie(request
, response
, cookieName
, cookieValue
, cookieMaxage
, encodeString
);}public static void deleteCookie(HttpServletRequest request
, HttpServletResponse response
,String cookieName
) {doSetCookie(request
, response
, cookieName
, "", -1, false);}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
) {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();}}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
) {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();}}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) {domainName
= "." + domains
[len
- 3] + "." + domains
[len
- 2] + "." + domains
[len
- 1];} else if (len
<= 3 && len
> 1) {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
;}}
接著我們?cè)趖aotao-sso-web工程的src/main/java目錄下的com.taotao.sso.controller包中新建一個(gè)Controller,例如UserLoginController。
為方便大家復(fù)制,現(xiàn)將以上Controller類的代碼貼出,如下所示。
package com
.taotao
.sso
.controller
;import javax
.servlet
.http
.HttpServletRequest
;
import javax
.servlet
.http
.HttpServletResponse
;import org
.springframework
.beans
.factory
.annotation
.Autowired
;
import org
.springframework
.beans
.factory
.annotation
.Value
;
import org
.springframework
.stereotype
.Controller
;
import org
.springframework
.web
.bind
.annotation
.RequestMapping
;
import org
.springframework
.web
.bind
.annotation
.RequestMethod
;
import org
.springframework
.web
.bind
.annotation
.ResponseBody
;import com
.taotao
.common
.pojo
.TaotaoResult
;
import com
.taotao
.common
.utils
.CookieUtils
;
import com
.taotao
.sso
.service
.UserLoginService
;@Controller
public class UserLoginController {@Autowiredprivate UserLoginService loginService
;@Value("${TT_TOKEN_KEY}")private String TT_TOKEN_KEY
;@RequestMapping(value
="/user/login", method
=RequestMethod
.POST
)@ResponseBodypublic TaotaoResult
login(HttpServletRequest request
, HttpServletResponse response
, String username
, String password
) {TaotaoResult result
= loginService
.login(username
, password
);if (result
.getStatus() == 200) {CookieUtils
.setCookie(request
, response
, TT_TOKEN_KEY
, result
.getData().toString());}return result
;}}
以上Controller類的代碼中TT_TOKEN_KEY這個(gè)字段的值是配置在resource.properties這個(gè)配置文件中了,如下圖所示。
controller層的代碼編寫(xiě)完之后,接下來(lái)我們就要進(jìn)行測(cè)試了。由于在taotao-common工程中添加了一個(gè)工具類,因此需要重新打包taotao-common工程到本地maven倉(cāng)庫(kù)中,又由于在taotao-sso-interface工程中新添加了一個(gè)接口,因此我們還需要把taotao-sso工程重新打包到本地maven倉(cāng)庫(kù)。注意:在啟動(dòng)工程前要保證Zookeeper注冊(cè)中心和Redis服務(wù)器處于啟動(dòng)狀態(tài)!!!
以上準(zhǔn)備工作做好之后,我們啟動(dòng)taotao-sso工程和taotao-sso-web工程,啟動(dòng)成功之后,我們還是使用postman這款接口測(cè)試工具來(lái)測(cè)試,登錄時(shí)我們只用用戶名和密碼,不用電話和郵箱,添加完用戶名和密碼這兩個(gè)參數(shù)之后,點(diǎn)擊Send按鈕,如下圖所示。
可以看到返回的結(jié)果狀態(tài)碼是200,data中存放的是token的值,說(shuō)明用戶登錄成功了,同時(shí)也說(shuō)明我們開(kāi)發(fā)的用戶登錄接口是正確的。
登錄成功之后,我們可以利用Redis Desktop Manager可視化工具來(lái)查看一下token是否已經(jīng)被保存起來(lái)了,如下圖所示,發(fā)現(xiàn)已經(jīng)被保存起來(lái)了。
總結(jié)
以上是生活随笔為你收集整理的淘淘商城第96讲——单点登录之用户登录的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。