完成权限校验
Zuul的權限校驗,第一個是買家訪問,第二個是/order/finish,這個接口目前還沒有,你可以去補充一下,/order/finish是將訂單置為完成狀態,我們來補一下這個接口,買家下單賣家來接單,接單就會把它置成完結狀態,只能賣家來操作,第一步先查詢訂單,根據orderId先查詢訂單,查詢完了之后要判斷一下訂單狀態,并不是所有的訂單都可以置為完結,如果訂單狀態沒問題,就可以修改訂單狀態了,狀態為完結,修改完之后就返回,邏輯也不算復雜,因為orderId是主鍵,如果訂單不存在就拋一個異常,如果訂單存在就繼續往下走,telnet 10.40.8.152 6379要判斷訂單的狀態,一定是先下的訂單,只有新訂單才能變成完結的狀態,這個邏輯都是我們自己來定的,如果這個訂單不是新訂單的話,那么就報異常,如果訂單狀態也沒有問題的話,就可以修改訂單狀態為完結了,但是由于我們返回的是OrderDTO,所以你還要構造這么一個對象,orderDTO里面還需要訂單詳情,所以我們還要把訂單詳情給他查詢出來,才能填充到這個對象里面,查詢訂單詳情,通過orderId來查訂單詳情,返回的肯定是一個list,一條orderId可以對應多條orderDetail,這里也最好判斷一下訂單詳情是否存在,如果不存在也拋一個異常,叫做訂單詳情不存在,只能買家或者賣家訪問,那么你肯定要知道,買家有什么特征,賣家有什么特征,依靠這些特征把它們區分開來,這里我們可以簡單地使用Cookie,買家是Cookie里有openId,賣家是Cookie里有Token,并且對應的Redis里面,有值,那接下來問題來了,之后買家和賣家登陸的時候,他應該訪問那些服務呢,有人說那不是user服務嗎,我是說第一次請求到哪個服務呢,沒錯,他首先訪問的是api-gateway,再由gateway轉發到user服務去,而我們之前登陸的時候,使用的是user服務,所以我們先要確定一點,他通過訪問api-gateway,是否可以成功登陸,成功登陸了他才會往Cookie里面設置值,不然登陸都沒有成功,那你把cookie里面來判斷,壓根就獲取不到,另外把user服務也給啟動了,服務已經啟動,user這個是8900,我們先訪問user,看一下登陸行不行,8900,這個是賣家登陸,登陸成功localhost:8900/login/seller?openid=abcd我們主要看一下cookie,cookie也寫進去了,沒問題,那我先把cookie給他刪掉,我們再通過api-gateway,gateway是8040,以后肯定是訪問api-gateway的,訪問這個接口,所以我們來訪問一下localhost:8040/login/seller?openid=abcd404,這是由于我們少了一個前綴,應該是userlocalhost:8040/user/login/seller?openid=abcd返回的是成功,但是Cookie里面沒有值,這就是重點,你看我再刷新,也沒有,可以刷新的,這是什么原因呢,之前有跟大家提過zuul,我們看敏感頭設置zuul.routes.myProduct.sensitiveHeaders=當時我們是對product服務來設置的,那現在再設置一下也很簡單,無非就是user的也寫一份,如果我對所有的服務都設置敏感頭呢,都讓他傳遞cookie,該怎么做呢,你可以這么來寫,因為在這上面寫是沒有提示的,如果要排除所有的敏感頭的話,這里有一個全局的配置zuul.sensitiveHeaders=你只要設置這個,注意是在zuul的目錄后面,全部服務忽略敏感頭,全部服務都可以傳遞cookie,這樣子就很清楚了,我們看到這個時候cookie就已經被寫進去了,這是賣家登陸,我們再來試試買家登陸localhost:8040/user/login/buyer?openid=abc看openid也被寫進去了,現在已經確保我們可以通過api-gateway,是可以正常登陸的,我們接下去就繼續編寫這塊的邏輯,來判斷一下,從哪里獲取呢,從request里面獲取if("/order/finish".equals(request.getRequestURI()))然后你要判斷cookie里面是否有cookieId,從這里面獲取的是cookieId,要判斷一下要獲取的值,是否為空,如果為null,或者cookie里面為空的話,就說明不存在openid的cookie,這個時候你可以返回他,沒有權限,同樣的如果是這個/order/finishurl,如果能取到token,那還要干一件事情,對應的redis里面也應該有值,我們可以繼續判斷,或者這個時候我們取redis的,先來訪問創建登訂單這個接口localhost:8040/order/order/create還有order服務會調用商品服務,所以把商品服務也給啟動一下,我們把order和product都啟動了,創建成功其實是不符合我們的預期的,目前我們還沒有登陸,所以你看cookie里面沒有東西,現在返回的就是401了,權限不足,那么我們先來登陸一次呢,http://localhost:8900/login/buyer?openid=abc買家登陸用的應該是這個,先登陸一次,成功了,你看cookie里面已經有了,現在再到這個地方創建訂單,你會發現還是401使用POSTMAN它是隔離開來的,特別要注意這一點,所以我們自己要給他寫一個Cookie,同樣的訂單完結,/order/finish,我們繼續對這個進行測試,他不僅僅判斷cookie,他還判斷了redis,所以我們從瀏覽器里面拿一下,localhost:8040/user/login/seller?openid=abcdhttp://localhost:8040/order/order/finishtoken_UUID0617af7a-c4a5-4c34-bf18-7e4c8c13846f不同的人訪問不同的URL,功能我們是寫完了,我們再來看一下代碼方面,寫到filter里面,這里加if語句來判斷,其實這種寫法以后是相當不好維護的,其實判斷的權限比較多的話呢,你看你現在是判斷一個URL,那如果很多呢,再加上這些權限是耦合進來判斷,那有一天產品經理跟你說,我們對買家放開,不限制了,那你是不是要把這一段給刪掉呢,有人會說一開始產品定下來就是這樣子,那以后就不會改了,你信誰都不要信產品的話,產品跟你說以后需求不改呢,這就好比你作為一個女生,男朋友教你去酒店,跟你說我們只是單純的看看電視,都是一類的問題,關于這塊代碼如何寫的更優雅一些呢,我來寫話肯定這樣來寫,對買家和賣家分別建一個Filter,比如我就建一個買家的Filter,不是新建,直接拷貝了一份,是否應該攔截,是否應該攔截我們都是通過URL判斷的,如果相等的話就攔截,否則就不攔截@Override
public boolean shouldFilter() {RequestContext requestContext = RequestContext.getCurrentContext();HttpServletRequest request = requestContext.getRequest();//對于買家權限這里是一個地址,如果是多個地址就向這里面去加就行了.// /order/create 只能買家訪問(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;
}那攔截之后要處理的邏輯呢,上面就根據條件是否要來攔截,現在是一個地址https://blog.csdn.net/q610376681/article/details/94132703那你如果要做多個地址的話,只需要往數據庫里面配一些,下面是攔截之后具體的處理邏輯,這是買家端,同理賣家端也來寫一個,賣家,賣家端是/order/finish這個接口,其實邏輯都沒有變化,只不過代碼上會改動一些@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 只能賣家訪問(cookie里有token,并且對應的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;
}改成這樣結構上更加清晰一些,不同的角色你就可以寫一個Filter,絕對是不會影響另外一個角色的,有一個地方想跟大家多提兩句,你看這里我只會區分買家和賣家,也就是把身份給他鑒別出來,他到底是誰,到底屬于什么樣的角色,大部分會把這些信息儲存到數據庫里面去,那是不是要去連數據庫進行判斷呢,希望大家在做的時候,不要這么去做,那是不是會直接去連數據庫,拿到信息去判斷呢,這里又回到我們之前說的問題了,邊界的問題,你看api-gateway他做什么事情,你如果讓他直接去連user,連用戶信息的數據庫的話,其實是不合適的,有朋友可能要說,那我能不能調用user的服務,沒錯你調用user的服務,是可以的,我覺得也應該去調用user的服務,但是你每次鑒權的時候,都去調user的服務,user服務又去讀取數據庫的話,這樣子還是對數據庫壓力挺大的,我建議還是利用Redis,api-gateway還是去讀Redis里面的信息,就可以直接判斷用戶的權限,當然Redis里面的信息怎么過來呢,可以像我們之前講的異步擴庫存的方式,用戶信息一變動之后,你可以發一個消息過來,網關這邊監聽這個消息,然后把它記錄到Redis里面,然后就行了,前面幾節我們添加了用戶服務,通過zuul完成對不同角色,URL的控制,微服務架構下,多個微服務都需要對訪問進行鑒權,每個微服務都需要明確當前訪問的用戶,及其權限,在Zuul的前置過濾器里,實現相關邏輯,是一個值得考慮的方案,同時在微服務框架中,多個服務的無狀態化,一般會考慮兩種技術方案,一種是分布式Session,另外一種是Auth,我們都是采用第一種方案,就是將用戶認證的信息,儲存在共享儲存中,且通常由用戶會話作為key,來實現簡單的分布式,哈希映射,當用戶訪問微服務時,用戶數據可以從共享儲存中取,用戶登錄狀態是不透明的,同時也是一個高可用且擴展的解決方案,第二種常用的方法,是OAuth2.0與Spring Security結合,這里我要強調一個細節,我們添加用戶服務的過程中,Utils之類的代碼一直在Copy,相同的我們就拷貝過來,我當時說少了一個基礎服務,如果公司是比較大型的項目進行改造的話,基礎服務會比較容易,一目了然的被拆出來,因為這部分代碼已經有了,所以比較好分辨,但是如果是一個從頭開始的項目,不是很有把握,首先將公用組件放到公用模塊里面去,就比如我們現在的user,product服務,里面都有common模塊,有了一定的積累之后,很自然的你就可以把這塊代碼給剝離出來,作為公共組件,成為一個公共的一個服務,另外一點,由于在Spring Cloud里面的所有微服務,都是通過Zuul來對外提供統一的入口,這個時候如果公司有兩套系統,一套是傳統的項目,另外一套是微服務的項目,只要這兩套項目同時運行,Zuul會非常關鍵
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和數據庫的匹配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);}// 設置cookie (name value 過期時間單位是s)CookieUtil.set(response,"openid",openid,7200);log.info("設置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);//這樣就會防止不停的往redis里面set數據return ResultVOUtil.success();}//1.openid和數據庫的匹配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);}//設置redis key =uuid value =xzy expire 過期時間
// 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("設置redis成功");//設置cookie (token=UUID 過期時間單位是s)CookieUtil.set(response,"token_UUID",token,7200);log.info("設置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和數據庫的匹配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);}// 設置cookie (name value 過期時間單位是s)CookieUtil.set(response,"openid",openid,7200);log.info("設置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);//這樣就會防止不停的往redis里面set數據return ResultVOUtil.success();}//1.openid和數據庫的匹配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);}//設置redis key =uuid value =xzy expire 過期時間
// 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("設置redis成功");//設置cookie (token=UUID 過期時間單位是s)CookieUtil.set(response,"token_UUID",token,7200);log.info("設置cookie成功");return ResultVOUtil.success();}}
?
總結
- 上一篇: 解读Redis报错:“MISCONF R
- 下一篇: Zuul:跨域