<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring Cloud Gateway + Jwt + Oauth2 實(shí)現(xiàn)網(wǎng)關(guān)的鑒權(quán)操作

          共 27183字,需瀏覽 55分鐘

           ·

          2021-08-30 09:39

          作者:huan1993
          來源:SegmentFault 思否社區(qū)

          一、背景

          隨著我們的微服務(wù)越來越多,如果每個微服務(wù)都要自己去實(shí)現(xiàn)一套鑒權(quán)操作,那么這么操作比較冗余,因此我們可以把鑒權(quán)操作統(tǒng)一放到網(wǎng)關(guān)去做,如果微服務(wù)自己有額外的鑒權(quán)處理,可以在自己的微服務(wù)中處理。

          二、需求

          1、在網(wǎng)關(guān)層完成url層面的鑒權(quán)操作。
          • 所有的OPTION請求都放行。

          • 所有不存在請求,直接都拒絕訪問。

          • user-provider服務(wù)的findAllUsers需要 user.userInfo權(quán)限才可以訪問。

          2、將解析后的jwt token當(dāng)做請求頭傳遞到下游服務(wù)中。
          3、整合Spring Security Oauth2 Resource Server

          三、前置條件

          1、搭建一個可用的認(rèn)證服務(wù)器,可以參考之前的文章.
          2、知道Spring Security Oauth2 Resource Server資源服務(wù)器如何使用,可以參考之前的文章.

          四、項(xiàng)目結(jié)構(gòu)


          五、網(wǎng)關(guān)層代碼的編寫

          1、引入jar包

          <dependency>
              <groupId>com.alibaba.cloud</groupId>
              <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
          </dependency>
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-gateway</artifactId>
          </dependency>
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
          </dependency>
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-security</artifactId>
          </dependency>
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-loadbalancer</artifactId>
          </dependency>

          2、自定義授權(quán)管理器

          自定義授權(quán)管理器,判斷用戶是否有權(quán)限訪問
          此處我們簡單判斷
          1、放行所有的 OPTION 請求。
          2、判斷某個請求(url)用戶是否有權(quán)限訪問。
          3、所有不存在的請求(url)直接無權(quán)限訪問。
          package com.huan.study.gateway.config;

          import com.google.common.collect.Maps;
          import lombok.extern.slf4j.Slf4j;
          import org.springframework.http.HttpMethod;
          import org.springframework.http.server.reactive.ServerHttpRequest;
          import org.springframework.security.authentication.AbstractAuthenticationToken;
          import org.springframework.security.authorization.AuthorizationDecision;
          import org.springframework.security.authorization.ReactiveAuthorizationManager;
          import org.springframework.security.core.Authentication;
          import org.springframework.security.core.GrantedAuthority;
          import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
          import org.springframework.security.web.server.authorization.AuthorizationContext;
          import org.springframework.stereotype.Component;
          import org.springframework.util.AntPathMatcher;
          import org.springframework.util.PathMatcher;
          import org.springframework.util.StringUtils;
          import org.springframework.web.server.ServerWebExchange;
          import reactor.core.publisher.Mono;

          import javax.annotation.PostConstruct;
          import java.util.Map;
          import java.util.Objects;

          /**
           * 自定義授權(quán)管理器,判斷用戶是否有權(quán)限訪問
           *
           * @author huan.fu 2021/8/24 - 上午9:57
           */
          @Component
          @Slf4j
          public class CustomReactiveAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {

              /**
               * 此處保存的是資源對應(yīng)的權(quán)限,可以從數(shù)據(jù)庫中獲取
               */
              private static final Map<String, String> AUTH_MAP = Maps.newConcurrentMap();

              @PostConstruct
              public void initAuthMap() {
                  AUTH_MAP.put("/user/findAllUsers""user.userInfo");
                  AUTH_MAP.put("/user/addUser""ROLE_ADMIN");
              }


              @Override
              public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext authorizationContext) {
                  ServerWebExchange exchange = authorizationContext.getExchange();
                  ServerHttpRequest request = exchange.getRequest();
                  String path = request.getURI().getPath();

                  // 帶通配符的可以使用這個進(jìn)行匹配
                  PathMatcher pathMatcher = new AntPathMatcher();
                  String authorities = AUTH_MAP.get(path);
                  log.info("訪問路徑:[{}],所需要的權(quán)限是:[{}]", path, authorities);

                  // option 請求,全部放行
                  if (request.getMethod() == HttpMethod.OPTIONS) {
                      return Mono.just(new AuthorizationDecision(true));
                  }

                  // 不在權(quán)限范圍內(nèi)的url,全部拒絕
                  if (!StringUtils.hasText(authorities)) {
                      return Mono.just(new AuthorizationDecision(false));
                  }

                  return authentication
                          .filter(Authentication::isAuthenticated)
                          .filter(a -> a instanceof JwtAuthenticationToken)
                          .cast(JwtAuthenticationToken.class)
                          .doOnNext(token -> {
                              System.out.println(token.getToken().getHeaders());
                              System.out.println(token.getTokenAttributes());
                          })
                          .flatMapIterable(AbstractAuthenticationToken::getAuthorities)
                          .map(GrantedAuthority::getAuthority)
                          .any(authority -> Objects.equals(authority, authorities))
                          .map(AuthorizationDecision::new)
                          .defaultIfEmpty(new AuthorizationDecision(false));
              }
          }

          3、token認(rèn)證失敗、或超時的處理

          package com.huan.study.gateway.config;

          import org.springframework.core.io.buffer.DataBuffer;
          import org.springframework.core.io.buffer.DataBufferUtils;
          import org.springframework.http.HttpStatus;
          import org.springframework.security.core.AuthenticationException;
          import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
          import org.springframework.web.server.ServerWebExchange;
          import reactor.core.publisher.Mono;

          import java.nio.charset.StandardCharsets;

          /**
           * 認(rèn)證失敗異常處理
           *
           * @author huan.fu 2021/8/25 - 下午1:10
           */
          public class CustomServerAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {
              @Override
              public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException ex) {

                  return Mono.defer(() -> Mono.just(exchange.getResponse()))
                          .flatMap(response -> {
                              response.setStatusCode(HttpStatus.UNAUTHORIZED);
                              String body = "{\"code\":401,\"msg\":\"token不合法或過期\"}";
                              DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
                              return response.writeWith(Mono.just(buffer))
                                      .doOnError(error -> DataBufferUtils.release(buffer));
                          });
              }
          }

          4、用戶沒有權(quán)限的處理

          package com.huan.study.gateway.config;

          import lombok.extern.slf4j.Slf4j;
          import org.springframework.context.annotation.Bean;
          import org.springframework.core.io.buffer.DataBuffer;
          import org.springframework.core.io.buffer.DataBufferUtils;
          import org.springframework.http.HttpStatus;
          import org.springframework.http.server.reactive.ServerHttpRequest;
          import org.springframework.http.server.reactive.ServerHttpResponse;
          import org.springframework.security.access.AccessDeniedException;
          import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
          import org.springframework.web.server.ServerWebExchange;
          import reactor.core.publisher.Mono;

          import java.nio.charset.StandardCharsets;

          /**
           * 無權(quán)限訪問異常
           *
           * @author huan.fu 2021/8/25 - 下午12:18
           */
          @Slf4j
          public class CustomServerAccessDeniedHandler implements ServerAccessDeniedHandler {

              @Override
              public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denied) {

                  ServerHttpRequest request = exchange.getRequest();

                  return exchange.getPrincipal()
                          .doOnNext(principal -> log.info("用戶:[{}]沒有訪問:[{}]的權(quán)限.", principal.getName(), request.getURI()))
                          .flatMap(principal -> {
                              ServerHttpResponse response = exchange.getResponse();
                              response.setStatusCode(HttpStatus.FORBIDDEN);
                              String body = "{\"code\":403,\"msg\":\"您無權(quán)限訪問\"}";
                              DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
                              return response.writeWith(Mono.just(buffer))
                                      .doOnError(error -> DataBufferUtils.release(buffer));
                          });
              }
          }

          5、將token信息傳遞到下游服務(wù)器中

          package com.huan.study.gateway.config;

          import com.fasterxml.jackson.core.JsonProcessingException;
          import com.fasterxml.jackson.databind.ObjectMapper;
          import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
          import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
          import org.springframework.http.server.reactive.ServerHttpRequest;
          import org.springframework.security.core.context.ReactiveSecurityContextHolder;
          import org.springframework.security.core.context.SecurityContext;
          import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
          import org.springframework.web.server.ServerWebExchange;
          import org.springframework.web.server.WebFilter;
          import org.springframework.web.server.WebFilterChain;
          import reactor.core.publisher.Mono;

          /**
           * 將token信息傳遞到下游服務(wù)中
           *
           * @author huan.fu 2021/8/25 - 下午2:49
           */
          public class TokenTransferFilter implements WebFilter {

              private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

              static {
                  OBJECT_MAPPER.registerModule(new Jdk8Module());
                  OBJECT_MAPPER.registerModule(new JavaTimeModule());
              }

              @Override
              public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
                  return ReactiveSecurityContextHolder.getContext()
                          .map(SecurityContext::getAuthentication)
                          .cast(JwtAuthenticationToken.class)
                          .flatMap(authentication -> {
                              ServerHttpRequest request = exchange.getRequest();
                              request = request.mutate()
                                      .header("tokenInfo", toJson(authentication.getPrincipal()))
                                      .build();

                              ServerWebExchange newExchange = exchange.mutate().request(request).build();

                              return chain.filter(newExchange);
                          });
              }

              public String toJson(Object obj) {
                  try {
                      return OBJECT_MAPPER.writeValueAsString(obj);
                  } catch (JsonProcessingException e) {
                      return null;
                  }
              }
          }

          6、網(wǎng)關(guān)層面的配置

          package com.huan.study.gateway.config;

          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.context.annotation.Bean;
          import org.springframework.context.annotation.Configuration;
          import org.springframework.core.convert.converter.Converter;
          import org.springframework.core.io.FileSystemResource;
          import org.springframework.core.io.Resource;
          import org.springframework.security.authentication.AbstractAuthenticationToken;
          import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
          import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
          import org.springframework.security.config.web.server.ServerHttpSecurity;
          import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
          import org.springframework.security.oauth2.jwt.Jwt;
          import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
          import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
          import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
          import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
          import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
          import org.springframework.security.oauth2.server.resource.web.server.ServerBearerTokenAuthenticationConverter;
          import org.springframework.security.web.server.SecurityWebFilterChain;
          import reactor.core.publisher.Mono;

          import java.io.IOException;
          import java.nio.file.Files;
          import java.security.KeyFactory;
          import java.security.NoSuchAlgorithmException;
          import java.security.interfaces.RSAPublicKey;
          import java.security.spec.InvalidKeySpecException;
          import java.security.spec.X509EncodedKeySpec;
          import java.util.Base64;

          /**
           * 資源服務(wù)器配置
           *
           * @author huan.fu 2021/8/24 - 上午10:08
           */
          @Configuration
          @EnableWebFluxSecurity
          public class ResourceServerConfig {

              @Autowired
              private CustomReactiveAuthorizationManager customReactiveAuthorizationManager;

              @Bean
              public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
                  http.oauth2ResourceServer()
                          .jwt()
                              .jwtAuthenticationConverter(jwtAuthenticationConverter())
                              .jwtDecoder(jwtDecoder())
                              .and()
                          // 認(rèn)證成功后沒有權(quán)限操作
                          .accessDeniedHandler(new CustomServerAccessDeniedHandler())
                          // 還沒有認(rèn)證時發(fā)生認(rèn)證異常,比如token過期,token不合法
                          .authenticationEntryPoint(new CustomServerAuthenticationEntryPoint())
                          // 將一個字符串token轉(zhuǎn)換成一個認(rèn)證對象
                          .bearerTokenConverter(new ServerBearerTokenAuthenticationConverter())
                              .and()
                  .authorizeExchange()
                          // 所有以 /auth/** 開頭的請求全部放行
                          .pathMatchers("/auth/**""/favicon.ico").permitAll()
                          // 所有的請求都交由此處進(jìn)行權(quán)限判斷處理
                          .anyExchange()
                              .access(customReactiveAuthorizationManager)
                              .and()
                          .exceptionHandling()
                              .accessDeniedHandler(new CustomServerAccessDeniedHandler())
                              .authenticationEntryPoint(new CustomServerAuthenticationEntryPoint())
                              .and()
                          .csrf()
                              .disable()
                  .addFilterAfter(new TokenTransferFilter(), SecurityWebFiltersOrder.AUTHENTICATION);

                  return http.build();
              }

              /**
               * 從jwt令牌中獲取認(rèn)證對象
               */
              public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {

                  // 從jwt 中獲取該令牌可以訪問的權(quán)限
                  JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter();
                  // 取消權(quán)限的前綴,默認(rèn)會加上SCOPE_
                  authoritiesConverter.setAuthorityPrefix("");
                  // 從那個字段中獲取權(quán)限
                  authoritiesConverter.setAuthoritiesClaimName("scope");

                  JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
                  // 獲取 principal name
                  jwtAuthenticationConverter.setPrincipalClaimName("sub");
                  jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);

                  return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
              }

              /**
               * 解碼jwt
               */
              public ReactiveJwtDecoder jwtDecoder() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
                  Resource resource = new FileSystemResource("/Users/huan/code/study/idea/spring-cloud-alibaba-parent/gateway-oauth2/new-authoriza-server-public-key.pem");
                  String publicKeyStr = String.join("", Files.readAllLines(resource.getFile().toPath()));
                  byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyStr);
                  X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
                  KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                  RSAPublicKey rsaPublicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);

                  return NimbusReactiveJwtDecoder.withPublicKey(rsaPublicKey)
                          .signatureAlgorithm(SignatureAlgorithm.RS256)
                          .build();
              }
          }

          7、網(wǎng)關(guān)yaml配置文件

          spring:
            application:
              name: gateway-auth
            cloud:
              nacos:
                discovery:
                  server-addr: localhost:8847
              gateway:
                routes:
                  - id: user-provider
                    uri: lb://user-provider
                    predicates:
                      - Path=/user/**
                    filters:
                      - RewritePath=/user(?<segment>/?.*), $\{segment}
              compatibility-verifier:
                # 取消SpringCloud SpringCloudAlibaba SpringBoot 等的版本檢查
                enabled: false
          server:
            port: 9203
          debug: true

          六、演示

          1、客戶端 gateway 在認(rèn)證服務(wù)器擁有的權(quán)限為 user.userInfo

          2、user-provider服務(wù)提供了一個api findAllUsers,它會返回 系統(tǒng)中存在的用戶(假的數(shù)據(jù)) 和 解碼后的token信息。

          3、在網(wǎng)關(guān)層面,findAllUsers 需要的權(quán)限為 user.userInfo,正好 gateway這個客戶端有這個權(quán)限,所以可以訪問。

          七、代碼路徑

          Spring Cloud Alibaba: Spring Cloud Alibaba 學(xué)習(xí) - Gitee.com


          點(diǎn)擊左下角閱讀原文,到 SegmentFault 思否社區(qū) 和文章作者展開更多互動和交流,掃描下方”二維碼“或在“公眾號后臺回復(fù)“ 入群 ”即可加入我們的技術(shù)交流群,收獲更多的技術(shù)文章~

          - END -


          瀏覽 47
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91精品国产91久久久久久吃药 | 成人AV一区二区三区在线观看 | 亚州无码高清视频 | 操操操操操操操操操操操操操操操操操逼 | 亚洲涩图二 |