1. 程式人生 > >Spring Cloud實戰 | 第六篇:Spring Cloud Gateway+Spring Security OAuth2+JWT實現微服務統一認證授權

Spring Cloud實戰 | 第六篇:Spring Cloud Gateway+Spring Security OAuth2+JWT實現微服務統一認證授權

## **一. 前言** 本篇實戰案例基於 [youlai-mall](https://github.com/hxrui/youlai-mall) 專案。專案使用的是當前主流和最新版本的技術和解決方案,自己不會太多華麗的言辭去描述,只希望能勾起大家對程式設計的一點喜歡。所以有興趣的朋友可以進入 [github](https://github.com/hxrui/youlai-mall) | [碼雲](https://gitee.com/haoxr/youlai-mall)瞭解下專案明細 ,有興趣也可以一起研發。 微服務通過整合 Spirng Cloud Gateway、Spring Security OAuth2、JWT 實現微服務的統一認證授權。其中Spring Cloud Gateway作為OAuth2客戶端,其他微服務提供資源服務給閘道器,交由閘道器來做統一鑑權,所以這裡網關同樣也作為資源伺服器。 **溫馨提示**:微服務認證授權在整個系列算是比較有難度的,本篇同時從理論和實戰兩個角度出發,所以篇幅有些長,還需要往期文章搭建的環境基礎,希望大家可以耐心的研究下。 **往期系列文章** 1. [Spring Cloud實戰 | 第一篇:Windows搭建Nacos服務 ](https://www.cnblogs.com/haoxianrui/p/13581881.html) 2. [Spring Cloud實戰 | 第二篇:Spring Cloud整合Nacos實現註冊中心](https://www.cnblogs.com/haoxianrui/p/13584204.html) 3. [Spring Cloud實戰 | 第三篇:Spring Cloud整合Nacos實現配置中心](https://www.cnblogs.com/haoxianrui/p/13585125.html) 4. [Spring Cloud實戰 | 第四篇:Spring Cloud整合Gateway實現API閘道器](https://www.cnblogs.com/haoxianrui/p/13608650.html) 5. [Spring Cloud實戰 | 第五篇:Spring Cloud整合OpenFeign實現微服務之間的呼叫](https://www.cnblogs.com/haoxianrui/p/13615592.html) ## **二. OAuth2和JWT概念及關係?** ### **1. 什麼是OAuth2?** > OAuth 2.0 是目前最流行的授權機制,用來授權第三方應用,獲取使用者資料。 -- [【阮一峰】OAuth 2.0 的一個簡單解釋](http://www.ruanyifeng.com/blog/2019/04/oauth_design.html) > QQ登入OAuth2.0:對於使用者相關的OpenAPI(例如獲取使用者資訊,動態同步,照片,日誌,分享等),為了保護使用者資料的安全和隱私,第三方網站訪問使用者資料前都需要顯式的向用戶徵求授權。 -- [【QQ登入】OAuth2.0開發文件](https://wiki.open.qq.com/wiki/%E3%80%90QQ%E7%99%BB%E5%BD%95%E3%80%91OAuth2.0%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3) 從上面定義可以理解OAuth2是一個授權協議,並且廣泛流行的應用。 下面通過“有道雲筆記”通過“QQ授權登入”的案例來分析QQ的OAuth2平臺的具體實現。
**流程分析:** ` 有道雲筆記客戶端 -> 選擇QQ授權登入 -> QQ認證授權成功返回access_token -> 有道雲筆記客戶端接收到access_token後進入有道雲筆記應用 ` 流程關聯OAuth2的角色關聯如下: ``` (1)第三方應用程式(Third-party Application):案例中的"有道雲筆記"客戶端。 (2)HTTP服務提供商(HTTP Service):QQ (3)資源所有者(Resource Owner):使用者 (4)使用者代理(User Agent): 比如瀏覽器,代替使用者去訪問這些資源。 (5)認證伺服器(Authorization Server):服務提供商專門用來處理認證的伺服器。案例中QQ提供的認證授權。 (6)資源伺服器(Resource server):即服務提供商存放使用者生成的資源的伺服器。它與認證伺服器,可以是同一臺伺服器,也可以是不同的伺服器。 這裡指客戶端拿到access_token要去訪問資源物件的伺服器,比如我們在有道雲裡的筆記。 ``` ### **2. 什麼是JWT?** JWT(JSON Web Token)是令牌token的一個子集,首先在伺服器端身份認證通過後生成一個字串憑證並返回給客戶端,客戶端請求伺服器端時攜帶該token字串進行鑑權認證。 JWT是無狀態的。 除了包含簽名演算法、憑據過期時間之外,還可擴充套件新增額外資訊,比如使用者資訊等,所以無需將JWT儲存在伺服器端。相較於cookie/session機制中需要將使用者資訊儲存在伺服器端的session裡節省了記憶體開銷,使用者量越多越明顯。 JWT的結構如下: ![](https://i.loli.net/2020/09/19/3nfIPYcDQzm41lu.jpg) 看不明白沒關係,我先把[youlai-mall](https://github.com/hxrui/youlai-mall)認證通過後生成的access token(標準的JWT格式)放到[JWT官網](https://jwt.io/)進行解析成能看的定的格式。 ![](https://i.loli.net/2020/09/19/8SuirOcdvGt3ACm.png) JWT字串由Header(頭部)、Payload(負載)、Signature(簽名)三部分組成。 Header: JSON物件,用來描述JWT的元資料,alg屬性表示簽名的演算法,typ標識token的型別 Payload: JSON物件,用來存放實際需要傳遞的資料, 除了預設欄位,還可以在此自定義私有欄位 Signature: 對Header、Payload這兩部分進行簽名,簽名需要私鑰,為了防止資料被篡改 ### **3. OAuth2和JWT關係?** - OAuth2是一種認證授權的協議規範。 - JWT是基於token的安全認證協議的實現。 至於一定要給這二者沾點親帶點故的話。可以說OAuth2在認證成功生成的令牌access_token可以由JWT實現。 ## **三. 認證伺服器** 認證伺服器落地 [youlai-mall](https://github.com/hxrui/youlai-mall) 的youlai-auth認證中心模組,完整程式碼地址: [github](https://github.com/hxrui/youlai-mall) | [碼雲](https://gitee.com/haoxr/youlai-mall) ### **1. pom依賴** ``` org.springframework.cloud
spring-cloud-starter-oauth2
org.springframework.security spring-security-oauth2-jose ``` ### **2. 認證服務配置(AuthorizationServerConfig)** ``` java /** * 認證服務配置 */ @Configuration @EnableAuthorizationServer @AllArgsConstructor public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { private DataSource dataSource; private AuthenticationManager authenticationManager; private UserDetailsServiceImpl userDetailsService; /** * 客戶端資訊配置 */ @Override @SneakyThrows public void configure(ClientDetailsServiceConfigurer clients) { JdbcClientDetailsServiceImpl jdbcClientDetailsService = new JdbcClientDetailsServiceImpl(dataSource); jdbcClientDetailsService.setFindClientDetailsSql(AuthConstants.FIND_CLIENT_DETAILS_SQL); jdbcClientDetailsService.setSelectClientDetailsSql(AuthConstants.SELECT_CLIENT_DETAILS_SQL); clients.withClientDetails(jdbcClientDetailsService); } /** * 配置授權(authorization)以及令牌(token)的訪問端點和令牌服務(token services) */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); List tokenEnhancers = new ArrayList<>(); tokenEnhancers.add(tokenEnhancer()); tokenEnhancers.add(jwtAccessTokenConverter()); tokenEnhancerChain.setTokenEnhancers(tokenEnhancers); endpoints.authenticationManager(authenticationManager) .accessTokenConverter(jwtAccessTokenConverter()) .tokenEnhancer(tokenEnhancerChain) .userDetailsService(userDetailsService) // refresh_token有兩種使用方式:重複使用(true)、非重複使用(false),預設為true // 1.重複使用:access_token過期重新整理時, refresh token過期時間未改變,仍以初次生成的時間為準 // 2.非重複使用:access_token過期重新整理時, refresh_token過期時間延續,在refresh_token有效期內重新整理而無需失效再次登入 .reuseRefreshTokens(false); } /** * 允許表單認證 */ @Override public void configure(AuthorizationServerSecurityConfigurer security) { security.allowFormAuthenticationForClients(); } /** * 使用非對稱加密演算法對token簽名 */ @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setKeyPair(keyPair()); return converter; } /** * 從classpath下的金鑰庫中獲取金鑰對(公鑰+私鑰) */ @Bean public KeyPair keyPair() { KeyStoreKeyFactory factory = new KeyStoreKeyFactory( new ClassPathResource("youlai.jks"), "123456".toCharArray()); KeyPair keyPair = factory.getKeyPair( "youlai", "123456".toCharArray()); return keyPair; } /** * JWT內容增強 */ @Bean public TokenEnhancer tokenEnhancer() { return (accessToken, authentication) -> { Map map = new HashMap<>(2); User user = (User) authentication.getUserAuthentication().getPrincipal(); map.put(AuthConstants.JWT_USER_ID_KEY, user.getId()); map.put(AuthConstants.JWT_CLIENT_ID_KEY, user.getClientId()); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(map); return accessToken; }; } } ``` AuthorizationServerConfig這個配置類是整個認證服務實現的核心。總結下來就是兩個關鍵點,客戶端資訊配置和access_token生成配置。 #### **2.1 客戶端資訊配置** 配置OAuth2認證允許接入的客戶端的資訊,因為接入OAuth2認證伺服器首先人家得認可你這個客戶端吧,就比如上面案例中的QQ的OAuth2認證伺服器認可“有道雲筆記”客戶端。 同理,我們需要把客戶端資訊配置在認證伺服器上來表示認證伺服器所認可的客戶端。一般可配置在認證伺服器的記憶體中,但是這樣很不方便管理擴充套件。所以實際最好配置在資料庫中的,提供視覺化介面對其進行管理,方便以後像PC端、APP端、小程式端等多端靈活接入。 Spring Security OAuth2官方提供的客戶端資訊表oauth_client_details ``` sql CREATE TABLE `oauth_client_details` ( `client_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `access_token_validity` int(11) NULL DEFAULT NULL, `refresh_token_validity` int(11) NULL DEFAULT NULL, `additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`client_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; ``` 新增一條客戶端資訊 ``` sql INSERT INTO `oauth_client_details` VALUES ('client', NULL, '123456', 'all', 'password,refresh_token', '', NULL, NULL, NULL, NULL, NULL); ``` ![](https://i.loli.net/2020/09/20/8EisZPdSHLBWmYw.png) #### **2.2 token生成配置** 專案使用JWT實現access_token,關於access_token生成步驟的配置如下: **1. 生成金鑰庫** 使用JDK工具的keytool生成JKS金鑰庫(Java Key Store),並將youlai.jks放到resources目錄 ` keytool -genkey -alias youlai -keyalg RSA -keypass 123456 -keystore youlai.jks -storepass 123456 ` -genkey 生成金鑰 -alias 別名 -keyalg 金鑰演算法 -keypass 金鑰口令 -keystore 生成金鑰庫的儲存路徑和名稱 -storepass 金鑰庫口令 ![](https://i.loli.net/2020/09/17/mMJLyHh1ix82AdE.png) **2. JWT內容增強** JWT負載資訊預設是固定的,如果想自定義新增一些額外資訊,需要實現TokenEnhancer的enhance方法將附加資訊新增到access_token中。 **3. JWT簽名** JwtAccessTokenConverter是生成token的轉換器,可以實現指定token的生成方式(JWT)和對JWT進行簽名。 簽名實際上是生成一段標識(JWT的Signature部分)作為接收方驗證資訊是否被篡改的依據。原理部分請參考這篇的文章:[RSA加密、解密、簽名、驗籤的原理及方法](https://www.cnblogs.com/pcheng/p/9629621.html) 其中對JWT簽名有對稱和非對稱兩種方式: > 對稱方式:認證伺服器和資源伺服器使用同一個金鑰進行加簽和驗籤 ,預設演算法HMAC > 非對稱方式:認證伺服器使用私鑰加簽,資源伺服器使用公鑰驗籤,預設演算法RSA 非對稱方式相較於對稱方式更為安全,因為私鑰只有認證伺服器知道。 專案中使用RSA非對稱簽名方式,具體實現步驟如下: (1). 從金鑰庫獲取金鑰對(金鑰+私鑰) (2). 認證伺服器私鑰對token簽名 (3). 提供公鑰獲取介面供資源伺服器驗籤使用 **公鑰獲取介面** ``` /** * RSA公鑰開放介面 */ @RestController @AllArgsConstructor public class PublicKeyController { private KeyPair keyPair; @GetMapping("/rsa/publicKey") public Map getKey() { RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAKey key = new RSAKey.Builder(publicKey).build(); return new JWKSet(key).toJSONObject(); } } ``` ### **3. 安全配置(WebSecurityConfig)** ``` @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll() .and() .authorizeRequests().antMatchers("/rsa/publicKey").permitAll().anyRequest().authenticated() .and() .csrf().disable(); } /** * 如果不配置SpringBoot會自動配置一個AuthenticationManager,覆蓋掉記憶體中的使用者 */ @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } } ``` 安全配置主要是配置請求訪問許可權、定義認證管理器、密碼加密配置。 ## **四. 資源伺服器** 資源伺服器落地 [youlai-mall](https://github.com/hxrui/youlai-mall) 的youlai-gateway微服務閘道器模組,完整程式碼地址: [github](https://github.com/hxrui/youlai-mall) | [碼雲](https://gitee.com/haoxr/youlai-mall) 上文有提到過閘道器這裡是擔任資源伺服器的角色,因為閘道器是微服務資源訪問的統一入口,所以在這裡做資源訪問的統一鑑權是再合適不過。 ### **1. pom依賴** ``` xml org.springframework.security
spring-security-oauth2-resource-server
org.springframework.security spring-security-oauth2-jose ``` ### **2. 配置檔案(youlai-gateway.yaml)** ``` yaml spring: security: oauth2: resourceserver: jwt: # 獲取JWT驗籤公鑰請求路徑 jwk-set-uri: 'http://localhost:9999/youlai-auth/rsa/publicKey' redis: database: 0 host: localhost port: 6379 password: cloud: gateway: discovery: locator: enabled: true # 啟用服務發現 lower-case-service-id: true routes: - id: youlai-auth uri: lb://youlai-auth predicates: - Path=/youlai-auth/** filters: - StripPrefix=1 - id: youlai-admin uri: lb://youlai-admin predicates: - Path=/youlai-admin/** filters: - StripPrefix=1 # 配置白名單路徑 white-list: urls: - "/youlai-auth/oauth/token" - "/youlai-auth/rsa/publicKey" ``` ### **3. 鑑權管理器** 鑑權管理器是作為資源伺服器驗證是否有權訪問資源的裁決者,核心部分的功能先已通過註釋形式進行說明,後面再對具體形式補充。 ``` java /** * 鑑權管理器 */ @Component @AllArgsConstructor @Slf4j public class AuthorizationManager implements ReactiveAuthorizationManager { private RedisTemplate redisTemplate; private WhiteListConfig whiteListConfig; @Override public Mono check(Mono mono, AuthorizationContext authorizationContext) { ServerHttpRequest request = authorizationContext.getExchange().getRequest(); String path = request.getURI().getPath(); PathMatcher pathMatcher = new AntPathMatcher(); // 白名單路徑直接放行 List whiteList = whiteListConfig.getUrls(); for (String ignoreUrl : whiteList) { if (pathMatcher.match(ignoreUrl, path)) { return Mono.just(new AuthorizationDecision(true)); } } // 對應跨域的預檢請求直接放行 if (request.getMethod() == HttpMethod.OPTIONS) { return Mono.just(new AuthorizationDecision(true)); } // token為空拒絕訪問 String token = request.getHeaders().getFirst(AuthConstants.JWT_TOKEN_HEADER); if (StrUtil.isBlank(token)) { return Mono.just(new AuthorizationDecision(false)); } // 快取取資源許可權角色關係列表 Map resourceRolesMap = redisTemplate.opsForHash().entries(AuthConstants.RESOURCE_ROLES_KEY); Iterator iterator = resourceRolesMap.keySet().iterator(); // 請求路徑匹配到的資源需要的角色許可權集合authorities統計 List authorities = new ArrayList<>(); while (iterator.hasNext()) { String pattern = (String) iterator.next(); if (pathMatcher.match(pattern, path)) { authorities.addAll(Convert.toList(String.class, resourceRolesMap.get(pattern))); } } Mono authorizationDecisionMono = mono .filter(Authentication::isAuthenticated) .flatMapIterable(Authentication::getAuthorities) .map(GrantedAuthority::getAuthority) .any(roleId -> { // roleId是請求使用者的角色(格式:ROLE_{roleId}),authorities是請求資源所需要角色的集合 log.info("訪問路徑:{}", path); log.info("使用者角色roleId:{}", roleId); log.info("資源需要許可權authorities:{}", authorities); return authorities.contains(roleId); }) .map(AuthorizationDecision::new) .defaultIfEmpty(new AuthorizationDecision(false)); return authorizationDecisionMono; } } ``` 第1、2、3處只是做些基礎訪問判斷,不做過多的說明 第4處從Redis快取獲取資源許可權資料。首先我們會關注兩個問題: (1). 資源許可權資料是什麼樣格式資料? (2). 資料什麼時候初始化到快取中? 以下就帶著這兩個問題來分析要完成第4步從快取獲取資源許可權資料需要提前做哪些工作吧。 **(1). 資源許可權資料格式** ![](https://i.loli.net/2020/09/23/Ipi7Kk5YzULaWT6.png) 需要把url和role_ids的對映關係快取到redis,大致意思的意思可以理解擁有url訪問許可權的角色ID有哪些。 **(2). 初始化快取時機** SpringBoot提供兩個介面CommandLineRunner和ApplicationRunner用於容器啟動後執行一些業務邏輯,比如資料初始化和預載入、MQ監聽啟動等。兩個介面執行時機無差,唯一區別在於介面的引數不同。有興趣的朋友可以瞭解一下這兩位朋友,以後會經常再見的哈~ 那麼這裡的業務邏輯是在容器初始化完成之後將從MySQL讀取到資源許可權資料載入到Redis快取中,正中下懷,來看下具體實現吧。 ![](https://i.loli.net/2020/09/23/l6rUYjQb3qgsmxn.png) Redis快取中的資源許可權資料 ![](https://i.loli.net/2020/09/23/KDYRu3bsWkIJfoX.png) 至此從快取資料可以看到擁有資源url訪問許可權的角色資訊,從快取獲取賦值給resourceRolesMap。 第5處根據請求路徑去匹配resourceRolesMap的資url(Ant Path匹配規則),得到對應資源所需角色資訊新增到authorities。 第6處就是判斷使用者是否有權訪問資源的最終一步了,只要使用者的角色中匹配到authorities中的任何一個,就說明該使用者擁有訪問許可權,允許通過。 ### **4. 資源伺服器配置** 這裡做的工作是將鑑權管理器AuthorizationManager配置到資源伺服器、請求白名單放行、無權訪問和無效token的自定義異常響應。配置類基本上都是約定俗成那一套,核心功能和注意的細節點通過註釋說明。 ``` /** * 資源伺服器配置 */ @AllArgsConstructor @Configuration // 註解需要使用@EnableWebFluxSecurity而非@EnableWebSecurity,因為SpringCloud Gateway基於WebFlux @EnableWebFluxSecurity public class ResourceServerConfig { private AuthorizationManager authorizationManager; private CustomServerAccessDeniedHandler customServerAccessDeniedHandler; private CustomServerAuthenticationEntryPoint customServerAuthenticationEntryPoint; private WhiteListConfig whiteListConfig; @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { http.oauth2ResourceServer().jwt() .jwtAuthenticationConverter(jwtAuthenticationConverter()); // 自定義處理JWT請求頭過期或簽名錯誤的結果 http.oauth2ResourceServer().authenticationEntryPoint(customServerAuthenticationEntryPoint); http.authorizeExchange() .pathMatchers(ArrayUtil.toArray(whiteListConfig.getUrls(),String.class)).permitAll() .anyExchange().access(authorizationManager) .and() .exceptionHandling() .accessDeniedHandler(customServerAccessDeniedHandler) // 處理未授權 .authenticationEntryPoint(customServerAuthenticationEntryPoint) //處理未認證 .and().csrf().disable(); return http.build(); } /** * @linkhttps://blog.csdn.net/qq_24230139/article/details/105091273 * ServerHttpSecurity沒有將jwt中authorities的負載部分當做Authentication * 需要把jwt的Claim中的authorities加入 * 方案:重新定義ReactiveAuthenticationManager許可權管理器,預設轉換器JwtGrantedAuthoritiesConverter */ @Bean public Converter> jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConstants.AUTHORITY_PREFIX); jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstants.AUTHORITY_CLAIM_NAME); JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter); return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter); } } ``` ``` java /** * 無權訪問自定義響應 */ @Component public class CustomServerAccessDeniedHandler implements ServerAccessDeniedHandler { @Override public Mono handle(ServerWebExchange exchange, AccessDeniedException e) { ServerHttpResponse response=exchange.getResponse(); response.setStatusCode(HttpStatus.OK); response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); response.getHeaders().set("Access-Control-Allow-Origin","*"); response.getHeaders().set("Cache-Control","no-cache"); String body= JSONUtil.toJsonStr(Result.custom(ResultCodeEnum.USER_ACCESS_UNAUTHORIZED)); DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8"))); return response.writeWith(Mono.just(buffer)); } } ``` ``` java /** * 無效token/token過期 自定義響應 */ @Component public class CustomServerAuthenticationEntryPoint implements ServerAuthenticationEntryPoint { @Override public Mono commence(ServerWebExchange exchange, AuthenticationException e) { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.OK); response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); response.getHeaders().set("Access-Control-Allow-Origin", "*"); response.getHeaders().set("Cache-Control", "no-cache"); String body = JSONUtil.toJsonStr(Result.custom(ResultCodeEnum.USER_ACCOUNT_UNAUTHENTICATED)); DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8"))); return response.writeWith(Mono.just(buffer)); } } ``` ### **5. 閘道器鑑權測試** 使用者 | 角色ID| 角色名稱 ---|---|---| admin | 2|系統管理員| 資源名稱|資源路徑 | 要求角色許可權| ---|---|---| 系統管理|/youlai-admin/**| [1] 選單管理|/youlai-admin/menus/**| [1,2] 使用者管理|/youlai-admin/users/**| [1,2] 部門管理|/youlai-admin/depts/**| [1,2] 字典管理|/youlai-admin/dictionaries/**|[1] 角色管理|/youlai-admin/roles/**| [1] 資源管理|/youlai-admin/resources/**|[1] 從模擬的資料可以看到admin擁有系統管理員的角色,而系統管理員只有選單管理、使用者管理、部門管理三個請求資源的訪問許可權,無其他資源的訪問許可權。 ![](https://i.loli.net/2020/09/23/gimxYSN8AEskPwn.png) 啟動管理平臺前端工程 youlai-mall-admin-web 完整程式碼地址: [github](https://github.com/hxrui/youlai-mall-admin-web) | [碼雲](https://gitee.com/haoxr/youlai-mall-admin-web) 訪問除了選單管理、使用者管理、部門管理這三個系統管理員擁有訪問許可權的資源之外,頁面都會提示“訪問未授權”,直接的說明了閘道器伺服器實現了請求鑑權的目的。 ![](https://i.loli.net/2020/09/23/ZEHm3MyrBQ9vcSW.gif) ## **五. 結語** 至此,Spring Cloud的統一認證授權就實現了。其實還有很多可以擴充套件的點,文章中把客戶端資訊儲存在資料庫中,那麼可以新增一個管理介面來維護這些客戶端資訊,這樣便可靈活配置客戶端接入認證平臺、認證有效期等等。同時也還有未完成的事項,我們知道JWT是無狀態的,那使用者在登出、修改密碼、登出的時候怎麼能把JWT置為無效呢?因為不可能像cookie/session機制把使用者資訊從伺服器刪除。所以這些都是值得思考的東西,我會在下篇文章提供對應的解決方案。 今天部落格園的園齡達到6年了,6年的時間自己依然沒有折騰啥出來,工作還有生活的壓力都挺大的,但也不想就這樣放棄了,所以。。。加油吧!!! **完整原始碼地址** ==**youlai-mall**== [github](https://github.com/hxrui/youlai-mall) | [碼雲](https://gitee.com/haoxr/youlai-mall) ==**youlai-mall-admin-web**== [github](https://github.com/hxrui/youlai-mall-admin-web) | [碼雲](https://gitee.com/haoxr/youlai-mall-admin-web)