完成权限校验
Zuul的權(quán)限校驗(yàn),第一個(gè)是買家訪問(wèn),第二個(gè)是/order/finish,這個(gè)接口目前還沒(méi)有,你可以去補(bǔ)充一下,/order/finish是將訂單置為完成狀態(tài),我們來(lái)補(bǔ)一下這個(gè)接口,買家下單賣家來(lái)接單,接單就會(huì)把它置成完結(jié)狀態(tài),只能賣家來(lái)操作,第一步先查詢訂單,根據(jù)orderId先查詢訂單,查詢完了之后要判斷一下訂單狀態(tài),并不是所有的訂單都可以置為完結(jié),如果訂單狀態(tài)沒(méi)問(wèn)題,就可以修改訂單狀態(tài)了,狀態(tài)為完結(jié),修改完之后就返回,邏輯也不算復(fù)雜,因?yàn)閛rderId是主鍵,如果訂單不存在就拋一個(gè)異常,如果訂單存在就繼續(xù)往下走,telnet 10.40.8.152 6379要判斷訂單的狀態(tài),一定是先下的訂單,只有新訂單才能變成完結(jié)的狀態(tài),這個(gè)邏輯都是我們自己來(lái)定的,如果這個(gè)訂單不是新訂單的話,那么就報(bào)異常,如果訂單狀態(tài)也沒(méi)有問(wèn)題的話,就可以修改訂單狀態(tài)為完結(jié)了,但是由于我們返回的是OrderDTO,所以你還要構(gòu)造這么一個(gè)對(duì)象,orderDTO里面還需要訂單詳情,所以我們還要把訂單詳情給他查詢出來(lái),才能填充到這個(gè)對(duì)象里面,查詢訂單詳情,通過(guò)orderId來(lái)查訂單詳情,返回的肯定是一個(gè)list,一條orderId可以對(duì)應(yīng)多條orderDetail,這里也最好判斷一下訂單詳情是否存在,如果不存在也拋一個(gè)異常,叫做訂單詳情不存在,只能買家或者賣家訪問(wèn),那么你肯定要知道,買家有什么特征,賣家有什么特征,依靠這些特征把它們區(qū)分開來(lái),這里我們可以簡(jiǎn)單地使用Cookie,買家是Cookie里有openId,賣家是Cookie里有Token,并且對(duì)應(yīng)的Redis里面,有值,那接下來(lái)問(wèn)題來(lái)了,之后買家和賣家登陸的時(shí)候,他應(yīng)該訪問(wèn)那些服務(wù)呢,有人說(shuō)那不是user服務(wù)嗎,我是說(shuō)第一次請(qǐng)求到哪個(gè)服務(wù)呢,沒(méi)錯(cuò),他首先訪問(wèn)的是api-gateway,再由gateway轉(zhuǎn)發(fā)到user服務(wù)去,而我們之前登陸的時(shí)候,使用的是user服務(wù),所以我們先要確定一點(diǎn),他通過(guò)訪問(wèn)api-gateway,是否可以成功登陸,成功登陸了他才會(huì)往Cookie里面設(shè)置值,不然登陸都沒(méi)有成功,那你把cookie里面來(lái)判斷,壓根就獲取不到,另外把user服務(wù)也給啟動(dòng)了,服務(wù)已經(jīng)啟動(dòng),user這個(gè)是8900,我們先訪問(wèn)user,看一下登陸行不行,8900,這個(gè)是賣家登陸,登陸成功localhost:8900/login/seller?openid=abcd我們主要看一下cookie,cookie也寫進(jìn)去了,沒(méi)問(wèn)題,那我先把cookie給他刪掉,我們?cè)偻ㄟ^(guò)api-gateway,gateway是8040,以后肯定是訪問(wèn)api-gateway的,訪問(wèn)這個(gè)接口,所以我們來(lái)訪問(wèn)一下localhost:8040/login/seller?openid=abcd404,這是由于我們少了一個(gè)前綴,應(yīng)該是userlocalhost:8040/user/login/seller?openid=abcd返回的是成功,但是Cookie里面沒(méi)有值,這就是重點(diǎn),你看我再刷新,也沒(méi)有,可以刷新的,這是什么原因呢,之前有跟大家提過(guò)zuul,我們看敏感頭設(shè)置zuul.routes.myProduct.sensitiveHeaders=當(dāng)時(shí)我們是對(duì)product服務(wù)來(lái)設(shè)置的,那現(xiàn)在再設(shè)置一下也很簡(jiǎn)單,無(wú)非就是user的也寫一份,如果我對(duì)所有的服務(wù)都設(shè)置敏感頭呢,都讓他傳遞cookie,該怎么做呢,你可以這么來(lái)寫,因?yàn)樵谶@上面寫是沒(méi)有提示的,如果要排除所有的敏感頭的話,這里有一個(gè)全局的配置zuul.sensitiveHeaders=你只要設(shè)置這個(gè),注意是在zuul的目錄后面,全部服務(wù)忽略敏感頭,全部服務(wù)都可以傳遞cookie,這樣子就很清楚了,我們看到這個(gè)時(shí)候cookie就已經(jīng)被寫進(jìn)去了,這是賣家登陸,我們?cè)賮?lái)試試買家登陸localhost:8040/user/login/buyer?openid=abc看openid也被寫進(jìn)去了,現(xiàn)在已經(jīng)確保我們可以通過(guò)api-gateway,是可以正常登陸的,我們接下去就繼續(xù)編寫這塊的邏輯,來(lái)判斷一下,從哪里獲取呢,從request里面獲取if("/order/finish".equals(request.getRequestURI()))然后你要判斷cookie里面是否有cookieId,從這里面獲取的是cookieId,要判斷一下要獲取的值,是否為空,如果為null,或者cookie里面為空的話,就說(shuō)明不存在openid的cookie,這個(gè)時(shí)候你可以返回他,沒(méi)有權(quán)限,同樣的如果是這個(gè)/order/finishurl,如果能取到token,那還要干一件事情,對(duì)應(yīng)的redis里面也應(yīng)該有值,我們可以繼續(xù)判斷,或者這個(gè)時(shí)候我們?nèi)edis的,先來(lái)訪問(wèn)創(chuàng)建登訂單這個(gè)接口localhost:8040/order/order/create還有order服務(wù)會(huì)調(diào)用商品服務(wù),所以把商品服務(wù)也給啟動(dòng)一下,我們把order和product都啟動(dòng)了,創(chuàng)建成功其實(shí)是不符合我們的預(yù)期的,目前我們還沒(méi)有登陸,所以你看cookie里面沒(méi)有東西,現(xiàn)在返回的就是401了,權(quán)限不足,那么我們先來(lái)登陸一次呢,http://localhost:8900/login/buyer?openid=abc買家登陸用的應(yīng)該是這個(gè),先登陸一次,成功了,你看cookie里面已經(jīng)有了,現(xiàn)在再到這個(gè)地方創(chuàng)建訂單,你會(huì)發(fā)現(xiàn)還是401使用POSTMAN它是隔離開來(lái)的,特別要注意這一點(diǎn),所以我們自己要給他寫一個(gè)Cookie,同樣的訂單完結(jié),/order/finish,我們繼續(xù)對(duì)這個(gè)進(jìn)行測(cè)試,他不僅僅判斷cookie,他還判斷了redis,所以我們從瀏覽器里面拿一下,localhost:8040/user/login/seller?openid=abcdhttp://localhost:8040/order/order/finishtoken_UUID0617af7a-c4a5-4c34-bf18-7e4c8c13846f不同的人訪問(wèn)不同的URL,功能我們是寫完了,我們?cè)賮?lái)看一下代碼方面,寫到filter里面,這里加if語(yǔ)句來(lái)判斷,其實(shí)這種寫法以后是相當(dāng)不好維護(hù)的,其實(shí)判斷的權(quán)限比較多的話呢,你看你現(xiàn)在是判斷一個(gè)URL,那如果很多呢,再加上這些權(quán)限是耦合進(jìn)來(lái)判斷,那有一天產(chǎn)品經(jīng)理跟你說(shuō),我們對(duì)買家放開,不限制了,那你是不是要把這一段給刪掉呢,有人會(huì)說(shuō)一開始產(chǎn)品定下來(lái)就是這樣子,那以后就不會(huì)改了,你信誰(shuí)都不要信產(chǎn)品的話,產(chǎn)品跟你說(shuō)以后需求不改呢,這就好比你作為一個(gè)女生,男朋友教你去酒店,跟你說(shuō)我們只是單純的看看電視,都是一類的問(wèn)題,關(guān)于這塊代碼如何寫的更優(yōu)雅一些呢,我來(lái)寫話肯定這樣來(lái)寫,對(duì)買家和賣家分別建一個(gè)Filter,比如我就建一個(gè)買家的Filter,不是新建,直接拷貝了一份,是否應(yīng)該攔截,是否應(yīng)該攔截我們都是通過(guò)URL判斷的,如果相等的話就攔截,否則就不攔截@Override
public boolean shouldFilter() {RequestContext requestContext = RequestContext.getCurrentContext();HttpServletRequest request = requestContext.getRequest();//對(duì)于買家權(quán)限這里是一個(gè)地址,如果是多個(gè)地址就向這里面去加就行了.// /order/create 只能買家訪問(wèn)(cookie里有openid)if("/order/order/create".equals(request.getRequestURI())) return true;else return false;}@Override
public Object run() throws ZuulException {RequestContext requestContext = RequestContext.getCurrentContext();HttpServletRequest request = requestContext.getRequest();Cookie cookie = CookieUtil.get(request, "openid");if(cookie ==null||StringUtils.isEmpty(cookie.getValue())){requestContext.setSendZuulResponse(false);requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());}return null;
}那攔截之后要處理的邏輯呢,上面就根據(jù)條件是否要來(lái)攔截,現(xiàn)在是一個(gè)地址https://blog.csdn.net/q610376681/article/details/94132703那你如果要做多個(gè)地址的話,只需要往數(shù)據(jù)庫(kù)里面配一些,下面是攔截之后具體的處理邏輯,這是買家端,同理賣家端也來(lái)寫一個(gè),賣家,賣家端是/order/finish這個(gè)接口,其實(shí)邏輯都沒(méi)有變化,只不過(guò)代碼上會(huì)改動(dòng)一些@Override
public boolean shouldFilter() {RequestContext requestContext = RequestContext.getCurrentContext();HttpServletRequest request = requestContext.getRequest();if("/order/order/finish".equals(request.getRequestURI())){return true;}return false;
}@Override
public Object run() throws ZuulException {RequestContext requestContext = RequestContext.getCurrentContext();HttpServletRequest request = requestContext.getRequest();// /order/finish 只能賣家訪問(wèn)(cookie里有token,并且對(duì)應(yīng)的redis中有值)Cookie cookie = CookieUtil.get(request, "token");if(cookie == null|| StringUtils.isEmpty(cookie.getValue())|| StringUtils.isEmpty(stringRedisTemplate.opsForValue().get(String.format(RedisConstant.Token_TEMPLATE,cookie.getValue())))){requestContext.setSendZuulResponse(false);requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());}return null;
}改成這樣結(jié)構(gòu)上更加清晰一些,不同的角色你就可以寫一個(gè)Filter,絕對(duì)是不會(huì)影響另外一個(gè)角色的,有一個(gè)地方想跟大家多提兩句,你看這里我只會(huì)區(qū)分買家和賣家,也就是把身份給他鑒別出來(lái),他到底是誰(shuí),到底屬于什么樣的角色,大部分會(huì)把這些信息儲(chǔ)存到數(shù)據(jù)庫(kù)里面去,那是不是要去連數(shù)據(jù)庫(kù)進(jìn)行判斷呢,希望大家在做的時(shí)候,不要這么去做,那是不是會(huì)直接去連數(shù)據(jù)庫(kù),拿到信息去判斷呢,這里又回到我們之前說(shuō)的問(wèn)題了,邊界的問(wèn)題,你看api-gateway他做什么事情,你如果讓他直接去連user,連用戶信息的數(shù)據(jù)庫(kù)的話,其實(shí)是不合適的,有朋友可能要說(shuō),那我能不能調(diào)用user的服務(wù),沒(méi)錯(cuò)你調(diào)用user的服務(wù),是可以的,我覺得也應(yīng)該去調(diào)用user的服務(wù),但是你每次鑒權(quán)的時(shí)候,都去調(diào)user的服務(wù),user服務(wù)又去讀取數(shù)據(jù)庫(kù)的話,這樣子還是對(duì)數(shù)據(jù)庫(kù)壓力挺大的,我建議還是利用Redis,api-gateway還是去讀Redis里面的信息,就可以直接判斷用戶的權(quán)限,當(dāng)然Redis里面的信息怎么過(guò)來(lái)呢,可以像我們之前講的異步擴(kuò)庫(kù)存的方式,用戶信息一變動(dòng)之后,你可以發(fā)一個(gè)消息過(guò)來(lái),網(wǎng)關(guān)這邊監(jiān)聽這個(gè)消息,然后把它記錄到Redis里面,然后就行了,前面幾節(jié)我們添加了用戶服務(wù),通過(guò)zuul完成對(duì)不同角色,URL的控制,微服務(wù)架構(gòu)下,多個(gè)微服務(wù)都需要對(duì)訪問(wèn)進(jìn)行鑒權(quán),每個(gè)微服務(wù)都需要明確當(dāng)前訪問(wèn)的用戶,及其權(quán)限,在Zuul的前置過(guò)濾器里,實(shí)現(xiàn)相關(guān)邏輯,是一個(gè)值得考慮的方案,同時(shí)在微服務(wù)框架中,多個(gè)服務(wù)的無(wú)狀態(tài)化,一般會(huì)考慮兩種技術(shù)方案,一種是分布式Session,另外一種是Auth,我們都是采用第一種方案,就是將用戶認(rèn)證的信息,儲(chǔ)存在共享儲(chǔ)存中,且通常由用戶會(huì)話作為key,來(lái)實(shí)現(xiàn)簡(jiǎn)單的分布式,哈希映射,當(dāng)用戶訪問(wèn)微服務(wù)時(shí),用戶數(shù)據(jù)可以從共享儲(chǔ)存中取,用戶登錄狀態(tài)是不透明的,同時(shí)也是一個(gè)高可用且擴(kuò)展的解決方案,第二種常用的方法,是OAuth2.0與Spring Security結(jié)合,這里我要強(qiáng)調(diào)一個(gè)細(xì)節(jié),我們添加用戶服務(wù)的過(guò)程中,Utils之類的代碼一直在Copy,相同的我們就拷貝過(guò)來(lái),我當(dāng)時(shí)說(shuō)少了一個(gè)基礎(chǔ)服務(wù),如果公司是比較大型的項(xiàng)目進(jìn)行改造的話,基礎(chǔ)服務(wù)會(huì)比較容易,一目了然的被拆出來(lái),因?yàn)檫@部分代碼已經(jīng)有了,所以比較好分辨,但是如果是一個(gè)從頭開始的項(xiàng)目,不是很有把握,首先將公用組件放到公用模塊里面去,就比如我們現(xiàn)在的user,product服務(wù),里面都有common模塊,有了一定的積累之后,很自然的你就可以把這塊代碼給剝離出來(lái),作為公共組件,成為一個(gè)公共的一個(gè)服務(wù),另外一點(diǎn),由于在Spring Cloud里面的所有微服務(wù),都是通過(guò)Zuul來(lái)對(duì)外提供統(tǒng)一的入口,這個(gè)時(shí)候如果公司有兩套系統(tǒng),一套是傳統(tǒng)的項(xiàng)目,另外一套是微服務(wù)的項(xiàng)目,只要這兩套項(xiàng)目同時(shí)運(yùn)行,Zuul會(huì)非常關(guān)鍵
package com.learn.cloud.controller;import java.util.UUID;
import java.util.concurrent.TimeUnit;import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import com.learn.cloud.entity.ResultEnum;
import com.learn.cloud.entity.ResultVO;
import com.learn.cloud.entity.UserInfo;
import com.learn.cloud.service.impl.UserServiceImpl;
import com.learn.cloud.utils.CookieUtil;
import com.learn.cloud.utils.ResultVOUtil;import lombok.extern.slf4j.Slf4j;/*** 前端控制器* @author Leon.Sun*/
@Slf4j
@RestController
@RequestMapping("/login")
public class UserInfoController {private final Logger log = LoggerFactory.getLogger(UserInfoController.class);@Autowiredprivate UserServiceImpl UserService;// 操作redis@Autowiredprivate StringRedisTemplate stringRedisTemplate;// @Autowired
// private RedisTemplate<String, Object> redisTemplate; /*** 買家登陸* @param openid* @param response* @return*/@GetMapping("/buyer")public ResultVO LoginByBuyer(@RequestParam("openid") String openid, HttpServletResponse response){ log.info("buyer openid"+openid);// 1.openid和數(shù)據(jù)庫(kù)的匹配UserInfo userInfo= UserService.selectByOpenId(openid);System.out.println("1:"+userInfo);if (userInfo==null){ return ResultVOUtil.error(ResultEnum.OPENID_IS_NOT_EXISTS);}// 判斷角色 1是買家 2是賣家if(userInfo.getRole()!=1){ return ResultVOUtil.error(ResultEnum.ROLE_ERROR);}// 設(shè)置cookie (name value 過(guò)期時(shí)間單位是s)CookieUtil.set(response,"openid",openid,7200);log.info("設(shè)置cookie成功");return ResultVOUtil.success();}/*** 賣家登陸* @param openid* @param response* @return*/@GetMapping("/seller")public ResultVO LoginBySeller(@RequestParam("openid") String openid,HttpServletRequest request, HttpServletResponse response){ log.info("seller openid"+openid);//生成UUIDString token = UUID.randomUUID().toString();//判斷是否登陸 cookie不為null redis不為nullCookie cookie= CookieUtil.get(request,"token_UUID");if (cookie!=null && !StringUtils.isEmpty(stringRedisTemplate.opsForValue().get("token_UUID"+cookie.getValue()))){String tokenValue = stringRedisTemplate.opsForValue().get("token_UUID"+cookie.getValue());System.out.println(tokenValue);//這樣就會(huì)防止不停的往redis里面set數(shù)據(jù)return ResultVOUtil.success();}//1.openid和數(shù)據(jù)庫(kù)的匹配UserInfo userInfo= UserService.selectByOpenId(openid);System.out.println("2:"+userInfo);if (userInfo==null){ return ResultVOUtil.error(ResultEnum.OPENID_IS_NOT_EXISTS);}//2判斷角色 1是買家 2是賣家if(userInfo.getRole()!=2){ return ResultVOUtil.error(ResultEnum.ROLE_ERROR);}//設(shè)置redis key =uuid value =xzy expire 過(guò)期時(shí)間
// stringRedisTemplate.opsForValue().set(String.format("token_UUID",token),openid,7200, TimeUnit.SECONDS);stringRedisTemplate.opsForValue().set("token_UUID"+token, openid,60*60*2,TimeUnit.SECONDS);
// stringRedisTemplate.opsForValue().set("token_UUID"+token,openid+"1122",7200, TimeUnit.SECONDS);
// redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
// redisTemplate.opsForValue().set("token_UUID"+token,openid,7200, TimeUnit.SECONDS);log.info("設(shè)置redis成功");//設(shè)置cookie (token=UUID 過(guò)期時(shí)間單位是s)CookieUtil.set(response,"token_UUID",token,7200);log.info("設(shè)置cookie成功");return ResultVOUtil.success();}}
package com.learn.cloud.controller;import java.util.UUID;
import java.util.concurrent.TimeUnit;import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import com.learn.cloud.entity.ResultEnum;
import com.learn.cloud.entity.ResultVO;
import com.learn.cloud.entity.UserInfo;
import com.learn.cloud.service.impl.UserServiceImpl;
import com.learn.cloud.utils.CookieUtil;
import com.learn.cloud.utils.ResultVOUtil;import lombok.extern.slf4j.Slf4j;/*** 前端控制器* @author Leon.Sun*/
@Slf4j
@RestController
@RequestMapping("/login")
public class UserInfoController {private final Logger log = LoggerFactory.getLogger(UserInfoController.class);@Autowiredprivate UserServiceImpl UserService;// 操作redis@Autowiredprivate StringRedisTemplate stringRedisTemplate;// @Autowired
// private RedisTemplate<String, Object> redisTemplate; /*** 買家登陸* @param openid* @param response* @return*/@GetMapping("/buyer")public ResultVO LoginByBuyer(@RequestParam("openid") String openid, HttpServletResponse response){ log.info("buyer openid"+openid);// 1.openid和數(shù)據(jù)庫(kù)的匹配UserInfo userInfo= UserService.selectByOpenId(openid);System.out.println("1:"+userInfo);if (userInfo==null){ return ResultVOUtil.error(ResultEnum.OPENID_IS_NOT_EXISTS);}// 判斷角色 1是買家 2是賣家if(userInfo.getRole()!=1){ return ResultVOUtil.error(ResultEnum.ROLE_ERROR);}// 設(shè)置cookie (name value 過(guò)期時(shí)間單位是s)CookieUtil.set(response,"openid",openid,7200);log.info("設(shè)置cookie成功");return ResultVOUtil.success();}/*** 賣家登陸* @param openid* @param response* @return*/@GetMapping("/seller")public ResultVO LoginBySeller(@RequestParam("openid") String openid,HttpServletRequest request, HttpServletResponse response){ log.info("seller openid"+openid);//生成UUIDString token = UUID.randomUUID().toString();//判斷是否登陸 cookie不為null redis不為nullCookie cookie= CookieUtil.get(request,"token_UUID");if (cookie!=null && !StringUtils.isEmpty(stringRedisTemplate.opsForValue().get("token_UUID"+cookie.getValue()))){String tokenValue = stringRedisTemplate.opsForValue().get("token_UUID"+cookie.getValue());System.out.println(tokenValue);//這樣就會(huì)防止不停的往redis里面set數(shù)據(jù)return ResultVOUtil.success();}//1.openid和數(shù)據(jù)庫(kù)的匹配UserInfo userInfo= UserService.selectByOpenId(openid);System.out.println("2:"+userInfo);if (userInfo==null){ return ResultVOUtil.error(ResultEnum.OPENID_IS_NOT_EXISTS);}//2判斷角色 1是買家 2是賣家if(userInfo.getRole()!=2){ return ResultVOUtil.error(ResultEnum.ROLE_ERROR);}//設(shè)置redis key =uuid value =xzy expire 過(guò)期時(shí)間
// stringRedisTemplate.opsForValue().set(String.format("token_UUID",token),openid,7200, TimeUnit.SECONDS);stringRedisTemplate.opsForValue().set("token_UUID"+token, openid,60*60*2,TimeUnit.SECONDS);
// stringRedisTemplate.opsForValue().set("token_UUID"+token,openid+"1122",7200, TimeUnit.SECONDS);
// redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
// redisTemplate.opsForValue().set("token_UUID"+token,openid,7200, TimeUnit.SECONDS);log.info("設(shè)置redis成功");//設(shè)置cookie (token=UUID 過(guò)期時(shí)間單位是s)CookieUtil.set(response,"token_UUID",token,7200);log.info("設(shè)置cookie成功");return ResultVOUtil.success();}}
?
總結(jié)
- 上一篇: 解读Redis报错:“MISCONF R
- 下一篇: Zuul:跨域