日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

gateway-统一权限-认证

發布時間:2023/12/10 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 gateway-统一权限-认证 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

int## 基礎名詞概念
**權限:**屬于系統的安全范疇,權限管理實現對用戶訪問系統的控制,按照安全規則或者安全控制策略用戶可以訪問而且只能訪問自己被授權的資源,主要包括用戶身份認證和請求鑒權兩部分,簡稱認證鑒權
認證判斷一個用戶是否為合法用戶的處理過程,最常用的簡單身份認證是系統通過核對用戶輸入的用戶名和口令,看其是否與系統中存儲的該用戶的用戶名和口令一致,來判斷用戶身份是否正確

鑒權:即訪問控制,控制誰能訪問那些資源;進行身份認證后需要分配權限可訪問的系統資源,對于某些資源沒有權限是無法訪問的,如下圖所示


權限控制:用戶是某個角色、或擁有某個資源時,才可訪問系統資源我們稱之為權限控制,權限控制分為下列2類型:
基于角色
RBAC基于角色的訪問控制是以角色為中心進行訪問控制,比如:主體的角色為總經理可以查詢企業運營報表,查詢員工薪資信息等,訪問控制流程如下:

基于資源
RBAC基于資源的訪問控制,是以資源中心進行訪問控制,企業中常用的權限管理方法,實現思路是:將系統操作的每個URL配置在資源表中,將資源對應到角色,將角色分配給用戶,用戶訪問系統功能通過Filter進行過濾,過濾器獲取到用戶的url,只要訪問的url是用戶分配角色中的URL是用戶分配角色的url則進行訪問,其具體流程如下:

匿名資源:無需認證鑒權就可以訪問的資源
公共資源:只需登錄既可以訪問的資源

多平臺權限控制

xxxx作為一個SaaS平臺,商家提供運營主體信息后,運營平臺會為商家開通系統,各個商家平臺都需要在運營平臺的管理下去工作:
1、運營平臺可以管理所有商家平臺的企業信息
2、運營平臺可以管理所有商家平臺的資源信息
3、運營平臺可以管理所有商家平臺的角色信息
4、運營平臺可以管理商家平臺的用戶信息

第二章 基礎信息簡介

在開始做權限開發之前我們需要看下權限設計的數據庫結構:

通過上圖,我們可以得到如下的信息:
一個企業可以有多個用戶
一個用戶可以有多個角色
一個角色可以有多個資源
這個是經典的權限設計,也就是:企業,用戶,角色,資源通過它們可以來完成整個權限的控制。

企業信息

商家想申請入駐平臺,首先在申請頁面【也可以后端錄入】進行信息填寫,填寫完成【運營平臺】對商家資質進行審核,審核通過后商家即可入職使用,如圖所示:


數據庫結構設計

CREATE TABLE `tab_enterprise` (`id` bigint(18) NOT NULL,`enterprise_id` bigint(18) NOT NULL COMMENT '商戶ID【系統內部識別使用】',`enterprise_name` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '企業名稱',`enterprise_no` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '工商號',`province` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '地址(省)',`area` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '地址(區)',`city` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '地址(市)',`address` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '詳細地址',`status` varchar(8) COLLATE utf8_bin NOT NULL COMMENT '狀態(試用:trial,停用:stop,正式:official)',`proposer_Id` bigint(18) DEFAULT NULL COMMENT '申請人Id',`enable_flag` varchar(18) CHARACTER SET utf8 NOT NULL COMMENT '是否有效',`created_time` datetime NOT NULL COMMENT '創建時間',`updated_time` datetime NOT NULL COMMENT '創建時間',`expire_time` datetime NOT NULL COMMENT '到期時間 (試用下是默認七天后到期,狀態改成停用)',`web_site` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '商戶門店web站點',`sharding_id` bigint(18) NOT NULL COMMENT '分庫id',`app_web_site` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '商戶h5web站點',PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='企業賬號管理';

實現細節
對于這個功能的CRUD這里就不做贅述,這里主要思考2個問題:
為什么我們要為商家綁定域名?
通過一個圖來分析下整個的工作流程

員工在瀏覽器中發起ppsk.shop.eehp.cn的訪問請求
阿里云域名解析會把ppsk.shop.eehp網址解析到阿里云ECS服務器101.101.108.2
阿里云ECS服務器【101.101.108.2】宿主機會把信息轉發到docker-nginx服務器
docker-nginx服務器配置的serverName【*.shop.eehp.cn】轉發到gateway服務
gateway服務根據ppsk.shop.eehp.cn兌換企業號100001
根據企業號100001訪問目標的商家A
域名和企業號如何建立關聯
在security模塊的initEnterpriseWeb方法,這里主要有四個方法:
init:初始化企業站點信息到redis,此方法上有==@PostConstruct==注解,表示項目啟動時即加載信息
addWebSiteforRedis:添加緩存中的站點,當我們【新增】企業主體信息時調用此方法
deleteWebSiteForRedis:移除緩存中的站點,當我們【刪除,僅用】企業主體信息時調用此方法
updateWebSiteforRedis:更新緩存中的站點,當我們修改禁用企業主體信息時調用此方法

/*** @ClassName initEnterpriseWebSIteInfo.java* @Description 初始化企業站點信息到redis*/ @Component public class InitEnterpriseSite {@AutowiredIEnterpriseService enterpriseService;@AutowiredRedissonClient redissonClient;/***獲得兩時間的秒間隔*/public Long secondInterval(Date date1, Date date2) {long secondInterval = (date2.getTime() - date1.getTime()) / 1000;return secondInterval;}/**** @description 初始化企業站點信息到redis*/@PostConstructpublic void init(){QueryWrapper<Enterprise> queryWrapper = new QueryWrapper<>();queryWrapper.lambda().eq(Enterprise::getEnableFlag, SuperConstant.YES).and(wrapper->wrapper.eq(Enterprise::getStatus,SuperConstant.TRIAL).or().eq(Enterprise::getStatus,SuperConstant.OFFICIAL));List<Enterprise> list = enterpriseService.list(queryWrapper);List<EnterpriseVo> enterpriseVos = BeanConv.toBeanList(list, EnterpriseVo.class);for (EnterpriseVo enterpriseVo : enterpriseVos) {String webSiteKey = SecurityCacheConstant.WEBSITE+enterpriseVo.getWebSite();RBucket<EnterpriseVo> webSiteBucket = redissonClient.getBucket(webSiteKey);String appWebSiteKey = SecurityCacheConstant.APP_WEBSITE+enterpriseVo.getAppWebSite();RBucket<EnterpriseVo> appWebSiteBucket = redissonClient.getBucket(appWebSiteKey);Long secondInterval = this.secondInterval(new Date(), enterpriseVo.getExpireTime());if (secondInterval.longValue()>0){webSiteBucket.set(enterpriseVo,secondInterval, TimeUnit.SECONDS);appWebSiteBucket.set(enterpriseVo,secondInterval, TimeUnit.SECONDS);}}}/**** @description 添加緩存中的站點* @param enterpriseVo 企業號* @return:*/public void addWebSiteforRedis(EnterpriseVo enterpriseVo){String webSiteKey = SecurityCacheConstant.WEBSITE+enterpriseVo.getWebSite();RBucket<EnterpriseVo> webSiteBucket = redissonClient.getBucket(webSiteKey);String appWebSiteKey = SecurityCacheConstant.APP_WEBSITE+enterpriseVo.getAppWebSite();RBucket<EnterpriseVo> appWebSiteBucket = redissonClient.getBucket(appWebSiteKey);Long secondInterval = this.secondInterval(new Date(), enterpriseVo.getExpireTime());if (secondInterval.longValue()>0){webSiteBucket.trySet(enterpriseVo,secondInterval, TimeUnit.SECONDS);appWebSiteBucket.trySet(enterpriseVo,secondInterval, TimeUnit.SECONDS);}}/**** @description 移除緩存中的站點* @param enterpriseVo 企業號* @return:*/public void deleteWebSiteforRedis( EnterpriseVo enterpriseVo){String webSiteKey = SecurityCacheConstant.WEBSITE+enterpriseVo.getWebSite();RBucket<EnterpriseVo> webSiteBucket = redissonClient.getBucket(webSiteKey);String appWebSiteKey = SecurityCacheConstant.APP_WEBSITE+enterpriseVo.getAppWebSite();RBucket<EnterpriseVo> appWebSiteBucket = redissonClient.getBucket(appWebSiteKey);webSiteBucket.delete();appWebSiteBucket.delete();}/**** @description 更新緩存中的站點* @param enterpriseVo 企業號* @return:*/public void updataWebSiteforRedis(EnterpriseVo enterpriseVo){String webSiteKey = SecurityCacheConstant.WEBSITE+enterpriseVo.getWebSite();RBucket<EnterpriseVo> webSiteBucket = redissonClient.getBucket(webSiteKey);String appWebSiteKey = SecurityCacheConstant.APP_WEBSITE+enterpriseVo.getAppWebSite();RBucket<EnterpriseVo> appWebSiteBucket = redissonClient.getBucket(appWebSiteKey);Long secondInterval = this.secondInterval(new Date(), enterpriseVo.getExpireTime());if (secondInterval.longValue()>0){webSiteBucket.set(enterpriseVo,secondInterval, TimeUnit.SECONDS);appWebSiteBucket.set(enterpriseVo,secondInterval, TimeUnit.SECONDS);}}

考慮到企業表【Enterprise】做CRUD的時候會影響緩存的更新,需要在EnterpriseFaceImpl中做同步的處理

@Override public EnterpriseVo createEnterprise(EnterpriseVo eterperiseVo) {Enterprise enterpriseResult = EnterpriseService.createEnterprise(eterperiseVo);//同步緩存if (!EmptyUtil.isNullOrEmpty(enterpriseResult)){initEnterpriseWebSiteInfo.addWebSiteforRedis(eterperiseVo.getWebSite(),eterperiseVo);}return BeanConv.toBean(enterpriseResult,EnterpriseVo.class); }@Override public Boolean updateEnterprise(EnterpriseVo enterpriseVo) {Boolean flag = EnterpriseService.updateEnterprise(enterpriseVo);//同步緩存if (flag){if (enterpriseVo.getEnableFlag().equals(SuperConstant.YES)){initEnterpriseWebSiteInfo.updataWebSiteforRedis(enterpriseVo.getWebSite(),enterpriseVo);}else {initEnterpriseWebSiteInfo.deleteWebSiteforRedis(enterpriseVo.getWebSite(),enterpriseVo);}}return flag; }@Override public Boolean deleteEnterprise(String[] checkedIds) {//同步緩存for (String checkedId : checkedIds) {Enterprise enterprise = EnterpriseService.getById(checkedId);EnterpriseVo enterpriseVo = BeanConv.toBean(enterprise, EnterpriseVo.class);initEnterpriseWebSiteInfo.deleteWebSiteforRedis(enterprise.getWebSite(),enterpriseVo);}Boolean flag = EnterpriseService.deleteEnterprise(checkedIds);return flag; } CREATE TABLE `tab_resource` (`id` bigint(18) NOT NULL COMMENT '主鍵',`parent_id` bigint(18) DEFAULT NULL COMMENT '父Id',`resource_name` varchar(36) DEFAULT NULL COMMENT '資源名稱',`request_path` varchar(200) DEFAULT NULL COMMENT '資源路徑',`icon` varchar(20) DEFAULT NULL COMMENT '圖標',`is_leaf` varchar(18) DEFAULT NULL COMMENT '是否葉子節點',`resource_type` varchar(36) DEFAULT NULL COMMENT '資源類型',`sort_no` int(11) DEFAULT NULL COMMENT '排序',`description` varchar(200) DEFAULT NULL COMMENT '描述',`system_code` varchar(36) DEFAULT NULL COMMENT '系統歸屬',`is_system_root` varchar(18) DEFAULT NULL COMMENT '是否根節點',`enable_flag` varchar(18) DEFAULT NULL COMMENT '是否有效',`created_time` datetime DEFAULT NULL COMMENT '創建時間',`updated_time` datetime DEFAULT NULL COMMENT '創建時間',`sharding_id` bigint(18) DEFAULT NULL,`label` varchar(200) DEFAULT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='資源表';

為了解決信息系統中的訪問控制管理的問題,適當簡化授權工作量,提高權限管理效率,需要建立基于角色的多系統授權管理模型,其業務管理模式如下:
由運營平臺系統管理員負責角色的權限及用戶權限及用戶分配。
由運營平臺系統管理員負責角色的權限匹配,同時賦予商家管理員對角色分配用戶的權限,定義標準角色,實現權限管理的部分下放
在此模式下,系統管理員不再兼任單位管理員工作,需要實現權限的多級下放,其架構設計如圖所示

數據庫結構:
角色表:

CREATE TABLE `tab_role` (`id` bigint(18) NOT NULL COMMENT '主鍵',`role_name` varchar(36) DEFAULT NULL COMMENT '角色名稱',`label` varchar(36) DEFAULT NULL COMMENT '角色標識',`description` varchar(200) DEFAULT NULL COMMENT '角色描述',`sort_no` int(36) DEFAULT NULL COMMENT '排序',`enable_flag` varchar(18) DEFAULT NULL COMMENT '是否有效',`created_time` datetime DEFAULT NULL COMMENT '創建時間',`updated_time` datetime DEFAULT NULL COMMENT '創建時間',`sharding_id` bigint(18) DEFAULT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='角色表';

角色資源表:

CREATE TABLE `tab_role_resource` (`id` bigint(18) NOT NULL,`enable_flag` varchar(18) DEFAULT NULL,`role_id` bigint(18) DEFAULT NULL,`resource_id` bigint(18) DEFAULT NULL,`created_time` datetime DEFAULT NULL COMMENT '創建時間',`updated_time` datetime DEFAULT NULL COMMENT '創建時間',`sharding_id` bigint(18) DEFAULT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='角色資源表';

用戶信息

xxx系統中用戶分為:運營商員工、商家平臺員工,其信息的維護規則如下:
由運營平臺系統管理員:可以定義,管理所有的用戶,并且從角色中選擇權限
由運營平臺系統管理員:負責定義角色,同時賦予商家管理員對角色,商家管理員分配用戶的權限(定義標準角色,實現權限管理的部分下放)。
多個運營商之間的員工信息是相互隔絕的

數據庫結構
用戶表:

CREATE TABLE `tab_user` (`id` bigint(18) NOT NULL COMMENT '主鍵',`store_id` bigint(32) DEFAULT NULL COMMENT '門店Id',`enterprise_id` bigint(18) NOT NULL COMMENT '商戶號',`username` varchar(36) DEFAULT NULL COMMENT '登錄名稱',`real_name` varchar(36) DEFAULT NULL COMMENT '真實姓名',`password` varchar(150) DEFAULT NULL COMMENT '密碼',`sex` varchar(11) DEFAULT NULL COMMENT '性別',`mobil` varchar(36) DEFAULT NULL COMMENT '電話',`email` varchar(36) DEFAULT NULL COMMENT '郵箱',`discount_limit` decimal(10,2) DEFAULT NULL COMMENT '折扣上線',`reduce_limit` decimal(10,2) DEFAULT NULL COMMENT '減免金額上線',`duties` varchar(36) DEFAULT NULL COMMENT '職務',`sort_no` int(11) DEFAULT NULL COMMENT '排序',`enable_flag` varchar(18) DEFAULT NULL COMMENT '是否有效',`created_time` datetime DEFAULT NULL COMMENT '創建時間',`updated_time` datetime DEFAULT NULL COMMENT '創建時間',`sharding_id` bigint(18) DEFAULT NULL COMMENT '分庫id',PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用戶表';

用戶角色表

CREATE TABLE `tab_user_role` (`id` bigint(36) NOT NULL,`enable_flag` varchar(18) DEFAULT NULL,`user_id` bigint(18) DEFAULT NULL,`role_id` bigint(18) DEFAULT NULL,`created_time` datetime DEFAULT NULL COMMENT '創建時間',`updated_time` datetime DEFAULT NULL COMMENT '創建時間',`sharding_id` bigint(18) DEFAULT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用戶角色表';

統一權限認證

認證:判斷一個用戶是否為合法用戶的處理過程,最常用的簡單身份認證方式是系統通過核對用戶輸入的用戶名和口令,看其是否與系統中存儲的該用戶和口令一致,來判斷用戶身份是否正確,如下圖所示:

集成的方式:本權限是基于spring-cloud-gateway網關來做權限的控制,因為gateway是基于響應式webflux【響應式編程】的機制進行處理的,所以這里的用法和原本httpservlet是有所區別的,首先我們來看一下整體的模塊依賴處理:

商家發起請求到gateway-shop網關
gateway網關調用model-security-client,進行認證或鑒權過濾器
model-security-client作為服務消費者通過model-security-interface接口進行用戶認證、鑒權接口調用
model-security-producer作為服務生產者進行當前用戶登錄、角色、權限的查詢
model-security-client:模塊是本權限系統核心,他提供了具體的認證、鑒權的邏輯,如果一個gateway想要實現權限的控制只需要依賴此客戶端

認證流程總述


認證總體流程如下:
用戶在登錄頁選擇登錄方式
判斷登錄方式是短信登錄、賬號密碼登錄,進行域名校驗,兌換企業ID【enterpriseid】
通過服務鑒權轉換器ServerAuthenticationConverter構建權限對象Authentication
Authentication對象交于認證管理器【ReactiveAuthenticationManager】進行認證

服務鑒權轉換器

ServerAuthenticationConverter:主要是負責表單的自動轉換,在spring-security中的默認的登錄頁面是long頁面,我們需要從表單中獲取用戶名和密碼或者用戶短信驗證碼

package com.xxxx.restkeeper.converter;import com.itheima.restkeeper.converter.LoginConverter; import com.itheima.restkeeper.utils.EmptyUtil; import com.itheima.restkeeper.utils.RegisterBeanHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono;/*** @ClassName ReactiveFormLoginAuthenticationConverter.java* @Description 自定義表單轉換*/ @Component public class ReactiveServerAuthenticationConverter implements ServerAuthenticationConverter {//登錄方式private String loginTypeParameter = "loginType";//站點類型private String siteTypeParameter = "siteType";@AutowiredRegisterBeanHandler registerBeanHandler;@Overridepublic Mono<Authentication> convert(ServerWebExchange exchange) {String loginType = exchange.getRequest().getHeaders().getFirst("loginType");String siteType = exchange.getRequest().getHeaders().getFirst("siteType");if (EmptyUtil.isNullOrEmpty(loginType)){throw new BadCredentialsException("客戶登陸異常");}LoginConverter loginConverter = registerBeanHandler.getBean(loginType, LoginConverter.class);return loginConverter.convert(exchange,loginType,siteType);} }

**LoginConverter:**登錄轉換接口定義

import org.springframework.security.core.Authentication; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono;/*** @ClassName LoginTypeConverterHandler.java* @Description 登錄類型轉換接口*/ public interface LoginConverter {/**** @description 登錄轉換* @param exchange* @param loginType* @param siteType* @return*/public Mono<Authentication> convert(ServerWebExchange exchange,String loginType,String siteType); }

**MobilLoginConverter:**手機驗證碼登錄轉換器

import org.redisson.api.RBucket; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono;/*** @ClassName SystemMobilLoginConverter.java* @Description 手機登錄*/ @Component("mobilLogin") public class MobilLoginConverter implements LoginConverter {//手機private String mobileParameter = "mobile";//驗證碼private String authCodeParameter = "authCode";@AutowiredRedissonClient redissonClient;@Overridepublic Mono<Authentication> convert(ServerWebExchange exchange,String loginType,String siteType) {String hostName = exchange.getRequest().getURI().getHost();String key = null;if (siteType.equals(SuperConstant.WEBSITE)){key = SecurityCacheConstant.WEBSITE+hostName;}else if (siteType.equals(SuperConstant.APP_WEBSITE)){key = SecurityCacheConstant.APP_WEBSITE+hostName;}else {return Mono.error(new BadCredentialsException("站點類型未定義"));}//域名校驗RBucket<EnterpriseVo> bucket = redissonClient.getBucket(key);EnterpriseVo enterpriseVo = bucket.get();if (EmptyUtil.isNullOrEmpty(enterpriseVo)){return Mono.error(new BadCredentialsException("Invalid hostName"));}//獲得enterpriseIdString enterpriseId = String.valueOf(enterpriseVo.getEnterpriseId());return exchange.getFormData().map( data -> {String mobile = data.getFirst(this.mobileParameter);String authCode = data.getFirst(this.authCodeParameter);if (EmptyUtil.isNullOrEmpty(mobile)||EmptyUtil.isNullOrEmpty(authCode)){throw new BadCredentialsException("客戶登陸異常");}String principal = mobile+":"+enterpriseId+":"+loginType+":"+siteType;return new UsernamePasswordAuthenticationToken(principal, authCode);});} }

**UsernameLoginConverter:**用戶名密碼登錄

import org.redisson.api.RBucket; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono;/*** @ClassName SysUsernameLoginConverterHandler.java* @Description 系統賬號密碼轉換*/ @Component("usernameLogin") public class UsernameLoginConverter implements LoginConverter {//賬號private String usernameParameter = "username";//密碼private String passwordParameter = "password";@AutowiredRedissonClient redissonClient;@Overridepublic Mono<Authentication> convert(ServerWebExchange exchange,String loginType,String siteType) {String hostName = exchange.getRequest().getURI().getHost();String key = null;if (siteType.equals(SuperConstant.WEBSITE)){key = SecurityCacheConstant.WEBSITE+hostName;}else if (siteType.equals(SuperConstant.APP_WEBSITE)){key = SecurityCacheConstant.APP_WEBSITE+hostName;}else {return Mono.error(new BadCredentialsException("站點類型未定義"));}//域名校驗RBucket<EnterpriseVo> bucket = redissonClient.getBucket(key);EnterpriseVo enterpriseVo = bucket.get();if (EmptyUtil.isNullOrEmpty(enterpriseVo)){return Mono.error(new BadCredentialsException("Invalid hostName"));}//獲得enterpriseIdString enterpriseId = String.valueOf(enterpriseVo.getEnterpriseId());return exchange.getFormData().map( data -> {String username = data.getFirst(this.usernameParameter);String password = data.getFirst(this.passwordParameter);if (EmptyUtil.isNullOrEmpty(username)||EmptyUtil.isNullOrEmpty(password)){throw new BadCredentialsException("用戶登陸異常");}String principal = username+":"+enterpriseId+":"+loginType+":"+siteType;return new UsernamePasswordAuthenticationToken(principal, password);});} }

用戶信息明細


ReactiveUserDetailsServiceImpl:主要負責認證過程,對于用戶信息的獲得,這里分為四種獲得方式
user賬戶登錄
user手機登錄
customer賬戶登錄
customer手機登錄
統一調用UserAdapterFace或者CustomerAdapterFace進行用戶登錄消息的獲得方式

import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboReference; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono;import java.util.HashSet;/*** @ClassName ReactiveUserDetailsServiceImpl.java* @Description 支持flum的身份類實現ReactiveUserDetailsService接口*/ @Component("reactiveUserDetailsService") @Slf4j public class ReactiveUserDetailsServiceImpl implements ReactiveUserDetailsService{//調用RPC原創服務@DubboReference(version = "${dubbo.application.version}", check = false)UserAdapterFace userAdapterFace;//調用RPC原創服務@DubboReference(version = "${dubbo.application.version}", check = false)CustomerAdapterFace customerAdapterFace;//驗證身份@Overridepublic Mono<UserDetails> findByUsername(String principal) {String[] principals = principal.split(":");if (principals.length!=4){log.warn("用戶:{}登錄信息不完整",principal);return Mono.empty();}String mobile =principals[0];String username =principals[0];Long enterpriseId =Long.valueOf(principals[1]);String loginType =principals[2];String siteType =principals[3];UserVo userVo = null;//user賬戶登錄if (loginType.equals(SuperConstant.USERNAME_LOGIN)&&siteType.equals(SuperConstant.WEBSITE)){userVo = userAdapterFace.findUserByUsernameAndEnterpriseId(username, enterpriseId);}//user手機登錄if (loginType.equals(SuperConstant.MOBIL_LOGIN)&&siteType.equals(SuperConstant.WEBSITE)){userVo = userAdapterFace.findUserByMobilAndEnterpriseId(mobile, enterpriseId);}//customer賬戶登錄if (loginType.equals(SuperConstant.USERNAME_LOGIN)&&siteType.equals(SuperConstant.APP_WEBSITE)){userVo = customerAdapterFace.findCustomerByUsernameAndEnterpriseId(username, enterpriseId);}//customer手機登錄if (loginType.equals(SuperConstant.MOBIL_LOGIN)&&siteType.equals(SuperConstant.APP_WEBSITE)){userVo = customerAdapterFace.findCustomerByMobilAndEnterpriseId(mobile, enterpriseId);}if (EmptyUtil.isNullOrEmpty(userVo)){log.warn("用戶:{}不存在",principal);return Mono.empty();}UserAuth userAuth = new UserAuth(userVo.getUsername(),userVo.getPassword(),new HashSet<>(),userVo.getId(),userVo.getShardingId(),userVo.getEnterpriseId(),userVo.getStoreId(),userVo.getJwtToken(),userVo.getRealName(),userVo.getSex(),userVo.getMobil(),userVo.getEmail(),userVo.getDiscountLimit(),userVo.getReduceLimit(),userVo.getDuties(),userVo.getCreatedTime(),userVo.getUpdatedTime());return Mono.just(userAuth);} }

認證管理器

**JwtReactiveAuthenticationManager:**查詢用戶明細信息之后,與請求傳遞過來的密碼或者短信驗證碼進行比對,如果比對成功則表示登錄成功

import org.redisson.api.RBucket; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers;/*** @ClassName JwtUserDetailsRepositoryReactiveAuthenticationManager.java* @Description 認證管理器*/ @Component public class JwtReactiveAuthenticationManager implements ReactiveAuthenticationManager {//密碼編輯者private PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();//調度程序private Scheduler scheduler = Schedulers.parallel();//用戶明細信息服務@Autowiredprivate ReactiveUserDetailsService reactiveUserDetailsService;@AutowiredRedissonClient redissonClient;@Overridepublic Mono<Authentication> authenticate(Authentication authentication) {final String principal = authentication.getName();final String password = (String) authentication.getCredentials();String[] principals = principal.split(":");//mobile+":"+enterpriseId+":"+loginType+":"+siteTypeString mobile =principals[0];String enterpriseId =principals[1];String loginType =principals[2];String siteType =principals[3];Mono<UserDetails> userDetailsMono = this.reactiveUserDetailsService.findByUsername(principal);//密碼校驗if (loginType.equals(SuperConstant.USERNAME_LOGIN)){return userDetailsMono.publishOn(this.scheduler)//密碼比較.filter(u -> this.passwordEncoder.matches(password, u.getPassword()))//失敗處理.switchIfEmpty(Mono.defer(()->Mono.error(new BadCredentialsException("Invalid Credentials"))))//成功處理.map(u ->new UsernamePasswordAuthenticationToken(u, u.getPassword(), u.getAuthorities()));}//短信校驗if (loginType.equals(SuperConstant.MOBIL_LOGIN)){//redis中獲得驗證碼String key = SmsCacheConstant.LOGIN_CODE+principals[0];RBucket<String> bucket = redissonClient.getBucket(key);String authCode = bucket.get();if (EmptyUtil.isNullOrEmpty(authCode)){Mono.error(new BadCredentialsException("Invalid Credentials"));}return userDetailsMono.publishOn(this.scheduler)//密碼比較.filter(u -> authCode.equals(password))//失敗處理.switchIfEmpty(Mono.defer(()->Mono.error(new BadCredentialsException("Invalid Credentials"))))//成功處理.map(u ->new UsernamePasswordAuthenticationToken(u, u.getPassword(), u.getAuthorities()));}throw new BadCredentialsException("Invalid Credentials");}public void setPasswordEncoder(PasswordEncoder passwordEncoder) {Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");this.passwordEncoder = passwordEncoder;}}

認證成功

import io.netty.util.CharsetUtil; import org.apache.dubbo.config.annotation.DubboReference; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.security.core.Authentication; import org.springframework.security.web.server.WebFilterExchange; import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono;import java.util.*;/*** @ClassName JsonServerAuthenticationSuccessHandler.java* @Description 登錄成功handler*/ @Component public class JsonServerAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler {@AutowiredJwtTokenManager jwtTokenManager;@DubboReference(version = "${dubbo.application.version}", check = false)UserAdapterFace userAdapterFace;@Overridepublic Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {ServerHttpResponse response = webFilterExchange.getExchange().getResponse();response.setStatusCode(HttpStatus.OK);response.getHeaders().set(HttpHeaders.CONTENT_TYPE, "application/json; charset=UTF-8");UserAuth authUser = (UserAuth) authentication.getPrincipal();//構建userVo返回對象UserVo userVo = UserVo.builder().id(authUser.getId()).username(authUser.getUsername()).reduceLimit(authUser.getReduceLimit()).discountLimit(authUser.getDiscountLimit()).enterpriseId(authUser.getEnterpriseId()).storeId(authUser.getStoreId()).build();//處理角色構建List<RoleVo> roleByUserId = userAdapterFace.findRoleByUserId(userVo.getId());Set<String> roles = new HashSet<>();for (RoleVo roleVo : roleByUserId) {roles.add(roleVo.getLabel());}//處理資源構建List<ResourceVo> resourceByUserId = userAdapterFace.findResourceByUserId(userVo.getId());Set<String> resources = new HashSet<>();for (ResourceVo resourceVo : resourceByUserId) {resources.add(resourceVo.getRequestPath());}//用戶指定角色、資源userVo.setRoles(roles);userVo.setResources(resources);//構建JWT令牌Map<String,Object> claims = new HashMap<>();String userVoJsonString= JSONObject.toJSONString(userVo);claims.put("currentUser",userVoJsonString);String jwtToken = jwtTokenManager.issuedToken("system",jwtTokenManager.getJwtProperties().getTtl(),authUser.getId().toString(),claims);userVo.setJwtToken(jwtToken);//返回信息給前端ResponseWrap<UserVo> responseWrap = ResponseWrapBuild.build(AuthEnum.SUCCEED, userVo);String result = JSONObject.toJSONString(responseWrap);DataBuffer buffer = response.bufferFactory().wrap(result.getBytes(CharsetUtil.UTF_8));return response.writeWith(Mono.just(buffer));} }

認證失敗
認證失敗:只需要返回錯誤信息即可

import io.netty.util.CharsetUtil; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.server.WebFilterExchange; import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono;/*** @ClassName JsonServerAuthenticationFailureHandler.java* @Description 登錄失敗*/ @Component public class JsonServerAuthenticationFailureHandler implements ServerAuthenticationFailureHandler {@Overridepublic Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) {//指定應答狀態ServerHttpResponse response = webFilterExchange.getExchange().getResponse();response.setStatusCode(HttpStatus.OK);response.getHeaders().set(HttpHeaders.CONTENT_TYPE, "application/json; charset=UTF-8");//返回信息給前段ResponseWrap<UserAuth> responseWrap = ResponseWrapBuild.build(AuthEnum.FAIL, null);String result = JSONObject.toJSONString(responseWrap);DataBuffer buffer = response.bufferFactory().wrap(result.getBytes(CharsetUtil.UTF_8));return response.writeWith(Mono.just(buffer));} }

總結

以上是生活随笔為你收集整理的gateway-统一权限-认证的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。