将Spring Cloud Gateway 与OAuth2模式一起使用

14 篇文章 12 订阅
订阅专栏

将Spring Cloud Gateway 与OAuth2模式一起使用

概述

Spring Cloud Gateway是一个构建在 Spring 生态之上的 API Gateway。 建立在 Spring Boot 2.x、 Spring WebFlux和 Project Reactor之上。

本节中您将使用Spring Cloud Gateway将请求路由到Servlet API服务。

本文您将学到

  • OpenID Connect 身份验证 - 用于用户身份验证
  • 令牌中继 - Spring Cloud Gateway API网关充当客户端将令牌转发到资源请求上

先决条件

  • Java 8+
  • MySQL
  • Redis

OpenID Connect身份验证

OpenID Connect 定义了一种基于 OAuth2 授权代码流的最终用户身份验证机制。下图是Spring Cloud Gateway与授权服务进行身份验证完整流程,为了清楚起见,其中一些参数已被省略。

在这里插入图片描述

创建授权服务

本节中我们将使用 Spring Authorization Server 构建授权服务,支持OAuth2协议与OpenID Connect协议。同时我们还将使用RBAC0基本权限模型控制访问权限。并且该授权服务同时作为OAuth2客户端支持Github第三方登录。


相关数据库表结构

我们创建了基本RBAC0权限模型用于本文示例讲解,并提供了OAuth2授权服务持久化存储所需表结构和OAuth2客户端持久化存储所需表结构。通过oauth2_client_role定义外部系统角色与本平台角色映射关系。涉及相关创建表及初始化数据的SQL语句可以 从这里获取。

在这里插入图片描述

角色说明

本节中授权服务默认提供两个角色,以下是角色属性及访问权限:

readwrite
ROLE_ADMIN
ROLE_OPERATION
Maven依赖
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
  <version>2.6.7</version>
</dependency>

<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-oauth2-authorization-server</artifactId>
  <version>0.3.1</version>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-client</artifactId>
  <version>2.6.7</version>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <version>2.6.7</version>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
  <version>2.6.7</version>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
  <version>2.6.7</version>
</dependency>

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.21</version>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid-spring-boot-starter</artifactId>
  <version>1.2.3</version>
</dependency>
配置

首先我们从application.yml配置开始,这里我们指定了端口号与MySQL连接配置:

server:
  port: 8080

spring:
  datasource:
    druid:
      db-type: mysql
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/oauth2server?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
      username: <<username>> # 修改用户名
      password: <<password>> # 修改密码

接下来我们将创建AuthorizationServerConfig,用于配置OAuth2及OIDC所需Bean,首先我们将新增OAuth2客户端信息,并持久化到数据库:

    @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
        RegisteredClient registeredClient = RegisteredClient.withId("relive-messaging-oidc")
                .clientId("relive-client")
                .clientSecret("{noop}relive-client")
                .clientAuthenticationMethods(s -> {
                    s.add(ClientAuthenticationMethod.CLIENT_SECRET_POST);
                    s.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
                })
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .redirectUri("http://127.0.0.1:8070/login/oauth2/code/messaging-gateway-oidc")
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                .scope(OidcScopes.EMAIL)
                .scope("read")
                .clientSettings(ClientSettings.builder()
                        .requireAuthorizationConsent(false) //不需要授权同意
                        .requireProofKey(false)
                        .build())
                .tokenSettings(TokenSettings.builder()
                        .accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED) // 生成JWT令牌
                        .idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
                        .accessTokenTimeToLive(Duration.ofSeconds(30 * 60))//accessTokenTimeToLive:access_token有效期
                        .refreshTokenTimeToLive(Duration.ofSeconds(60 * 60))//refreshTokenTimeToLive:refresh_token有效期
                        .reuseRefreshTokens(true)
                        .build())
                .build();

        JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
        registeredClientRepository.save(registeredClient);
        return registeredClientRepository;
    }


其次我们将创建授权过程中所需持久化容器类:

    @Bean
    public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
    }

 
    @Bean
    public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
    }

授权服务器需要其用于令牌的签名密钥,让我们生成一个 2048 字节的 RSA 密钥:

@Bean
public JWKSource<SecurityContext> jwkSource() {
  RSAKey rsaKey = Jwks.generateRsa();
  JWKSet jwkSet = new JWKSet(rsaKey);
  return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}

static class Jwks {

  private Jwks() {
  }

  public static RSAKey generateRsa() {
    KeyPair keyPair = KeyGeneratorUtils.generateRsaKey();
    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
    return new RSAKey.Builder(publicKey)
      .privateKey(privateKey)
      .keyID(UUID.randomUUID().toString())
      .build();
  }
}

static class KeyGeneratorUtils {

  private KeyGeneratorUtils() {
  }

  static KeyPair generateRsaKey() {
    KeyPair keyPair;
    try {
      KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
      keyPairGenerator.initialize(2048);
      keyPair = keyPairGenerator.generateKeyPair();
    } catch (Exception ex) {
      throw new IllegalStateException(ex);
    }
    return keyPair;
  }
}

接下来我们将创建用于OAuth2授权的SecurityFilterChain,SecurityFilterChain是Spring Security提供的过滤器链,Spring Security的认证授权功能都是通过滤器完成:

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
                new OAuth2AuthorizationServerConfigurer<>();
        //配置OIDC
        authorizationServerConfigurer.oidc(Customizer.withDefaults());

        RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();

        return http.requestMatcher(endpointsMatcher)
                .authorizeRequests((authorizeRequests) -> {
                    ((ExpressionUrlAuthorizationConfigurer.AuthorizedUrl) authorizeRequests.anyRequest()).authenticated();
                }).csrf((csrf) -> {
                    csrf.ignoringRequestMatchers(new RequestMatcher[]{endpointsMatcher});
                }).apply(authorizationServerConfigurer)
                .and()
                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
                .exceptionHandling(exceptions -> exceptions.
                        authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")))
                .apply(authorizationServerConfigurer)
                .and()
                .build();
    }

上述我们配置了OAuth2和OpenID Connect默认配置,并将为认证请求重定向到登录页,同时我们还启用了Spring Security提供的OAuth2资源服务配置,该配置用于保护OpenID Connect中/userinfo用户信息端点。


在启用Spring Security的OAuth2资源服务配置时我们指定了JWT验证,所以我们需要在application.yml中指定jwk-set-uri或声明式添加JwtDecoder,下面我们使用声明式配置:

    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }

接下来我们将自定义Access Token,在本示例中我们使用RBAC0权限模型,所以我们在Access Token中添加authorities为当前用户所属角色的权限(permissionCode):

@Configuration(proxyBeanMethods = false)
public class AccessTokenCustomizerConfig {

    @Autowired
    RoleRepository roleRepository;

    @Bean
    public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer() {
        return (context) -> {
            if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
                context.getClaims().claims(claim -> {
                    claim.put("authorities", roleRepository.findByRoleCode(context.getPrincipal().getAuthorities().stream()
                            .map(GrantedAuthority::getAuthority).findFirst().orElse("ROLE_OPERATION"))
                            .getPermissions().stream().map(Permission::getPermissionCode).collect(Collectors.toSet()));
                });
            }
        };
    }
}

RoleRepository属于role表持久层对象,在本示例中选用JPA框架,相关代码将不在文中展示,如果您并不了解JPA使用,可以使用Mybatis替代。


下面我们将配置授权服务Form表单认证方式:

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeRequests(authorizeRequests ->
                        authorizeRequests.anyRequest().authenticated()
                )
                .formLogin(withDefaults())
                
          ...
          
        return http.build();
    }

接下来我们将创建JdbcUserDetailsService 实现 UserDetailsService,用于在认证过程中查找登录用户的密码及权限信息,至于为什么需要实现UserDetailsService,感兴趣可以查看UsernamePasswordAuthenticationFilter -> ProviderManager -> DaoAuthenticationProvider 源码,在DaoAuthenticationProvider中通过调用UserDetailsService#loadUserByUsername(String username)获取用户信息。

@RequiredArgsConstructor
public class JdbcUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        com.relive.entity.User user = userRepository.findUserByUsername(username);
        if (ObjectUtils.isEmpty(user)) {
            throw new UsernameNotFoundException("user is not found");
        }
        if (CollectionUtils.isEmpty(user.getRoleList())) {
            throw new UsernameNotFoundException("role is not found");
        }
        Set<SimpleGrantedAuthority> authorities = user.getRoleList().stream().map(Role::getRoleCode)
                .map(SimpleGrantedAuthority::new).collect(Collectors.toSet());
        return new User(user.getUsername(), user.getPassword(), authorities);
    }
}

    @Bean
    UserDetailsService userDetailsService(UserRepository userRepository) {
        return new JdbcUserDetailsService(userRepository);
    }

在尝试请求未认证接口将会引导用户到登录页面并提示输入用户名密码,结果如下:

在这里插入图片描述



用户通常需要使用多个平台,这些平台由不同组织提供和托管。 这些用户可能需要使用每个平台的特定(和不同)的凭据。当用户拥有许多不同的凭据时,他们常常会忘记登录凭据。

联合身份验证是使用外部系统对用户进行身份验证。这可以与Google,Github或任何其他身份提供商一起使用。在这里,我将使用Github进行用户身份验证和数据同步管理。

Github身份认证

首先我们将配置Github客户端信息,你只需要更改其中clientIdclientSecret。其次我们将使用 Spring Security 持久化OAuth2客户端 文中介绍的JdbcClientRegistrationRepository持久层容器类将GitHub客户端信息存储在数据库中:

    @Bean
    ClientRegistrationRepository clientRegistrationRepository(JdbcTemplate jdbcTemplate) {
        JdbcClientRegistrationRepository jdbcClientRegistrationRepository = new JdbcClientRegistrationRepository(jdbcTemplate);
        ClientRegistration clientRegistration = ClientRegistration.withRegistrationId("github")
                .clientId("123456")
                .clientSecret("123456")
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}")
                .scope(new String[]{"read:user"})
                .authorizationUri("https://github.com/login/oauth/authorize")
                .tokenUri("https://github.com/login/oauth/access_token")
                .userInfoUri("https://api.github.com/user")
                .userNameAttributeName("login")
                .clientName("GitHub").build();

        jdbcClientRegistrationRepository.save(clientRegistration);
        return jdbcClientRegistrationRepository;
    }

接下来我们将实例化OAuth2AuthorizedClientServiceOAuth2AuthorizedClientRepository

  • OAuth2AuthorizedClientService:负责OAuth2AuthorizedClient在 Web 请求之间进行持久化。
  • OAuth2AuthorizedClientRepository:用于在请求之间保存和持久化授权客户端。

    @Bean
    OAuth2AuthorizedClientService authorizedClientService(
            JdbcTemplate jdbcTemplate,
            ClientRegistrationRepository clientRegistrationRepository) {
        return new JdbcOAuth2AuthorizedClientService(jdbcTemplate, clientRegistrationRepository);
    }

    @Bean
    OAuth2AuthorizedClientRepository authorizedClientRepository(
            OAuth2AuthorizedClientService authorizedClientService) {
        return new AuthenticatedPrincipalOAuth2AuthorizedClientRepository(authorizedClientService);
    }

对于每个使用Github登录的用户,我们都要分配平台的角色以控制他们可以访问哪些资源,在此我们将新建AuthorityMappingOAuth2UserService类授予用户角色:

@RequiredArgsConstructor
public class AuthorityMappingOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
    private DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
    private final OAuth2ClientRoleRepository oAuth2ClientRoleRepository;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        DefaultOAuth2User oAuth2User = (DefaultOAuth2User) delegate.loadUser(userRequest);

        Map<String, Object> additionalParameters = userRequest.getAdditionalParameters();
        Set<String> role = new HashSet<>();
        if (additionalParameters.containsKey("authority")) {
            role.addAll((Collection<? extends String>) additionalParameters.get("authority"));
        }
        if (additionalParameters.containsKey("role")) {
            role.addAll((Collection<? extends String>) additionalParameters.get("role"));
        }
        Set<SimpleGrantedAuthority> mappedAuthorities = role.stream()
                .map(r -> oAuth2ClientRoleRepository.findByClientRegistrationIdAndRoleCode(userRequest.getClientRegistration().getRegistrationId(), r))
                .map(OAuth2ClientRole::getRole).map(Role::getRoleCode).map(SimpleGrantedAuthority::new)
                .collect(Collectors.toSet());
        //当没有指定客户端角色,则默认赋予最小权限ROLE_OPERATION
        if (CollectionUtils.isEmpty(mappedAuthorities)) {
            mappedAuthorities = new HashSet<>(
                    Collections.singletonList(new SimpleGrantedAuthority("ROLE_OPERATION")));
        }
        String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
        return new DefaultOAuth2User(mappedAuthorities, oAuth2User.getAttributes(), userNameAttributeName);
    }
}

我们可以看到从authorityrole属性中获取权限信息,在通过OAuth2ClientRoleRepository查找映射到本平台的角色属性。

注意:authorityrole是由平台自定义属性,与OAuth2协议与Open ID Connect 协议无关,在生产环境中你可以与外部系统协商约定一个属性来传递权限信息。

OAuth2ClientRoleRepository为oauth2_client_role表持久层容器类,由JPA实现。

对于未获取到预先定义的映射角色信息,我们将赋予默认ROLE_OPERATION最小权限角色。而在本示例中GitHub登录的用户来说,也将被赋予ROLE_OPERATION角色。


针对GitHub认证成功并且首次登录的用户我们将获取用户信息并持久化到user表中,这里我们实现AuthenticationSuccessHandler并增加持久化用户逻辑:

public final class SavedUserAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private final AuthenticationSuccessHandler delegate = new SavedRequestAwareAuthenticationSuccessHandler();


    private Consumer<OAuth2User> oauth2UserHandler = (user) -> {
    };

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        if (authentication instanceof OAuth2AuthenticationToken) {
            if (authentication.getPrincipal() instanceof OAuth2User) {
                this.oauth2UserHandler.accept((OAuth2User) authentication.getPrincipal());
            }
        }

        this.delegate.onAuthenticationSuccess(request, response, authentication);
    }

    public void setOauth2UserHandler(Consumer<OAuth2User> oauth2UserHandler) {
        this.oauth2UserHandler = oauth2UserHandler;
    }
}

我们将通过setOauth2UserHandler(Consumer oauth2UserHandler)方法将UserRepositoryOAuth2UserHandler注入到SavedUserAuthenticationSuccessHandler中,UserRepositoryOAuth2UserHandler定义了具体持久层操作:

@Component
@RequiredArgsConstructor
public final class UserRepositoryOAuth2UserHandler implements Consumer<OAuth2User> {

    private final UserRepository userRepository;

    private final RoleRepository roleRepository;

    @Override
    public void accept(OAuth2User oAuth2User) {
        DefaultOAuth2User defaultOAuth2User = (DefaultOAuth2User) oAuth2User;
        if (this.userRepository.findUserByUsername(oAuth2User.getName()) == null) {
            User user = new User();
            user.setUsername(defaultOAuth2User.getName());
            Role role = roleRepository.findByRoleCode(defaultOAuth2User.getAuthorities()
                    .stream().map(GrantedAuthority::getAuthority).findFirst().orElse("ROLE_OPERATION"));
            user.setRoleList(Arrays.asList(role));
            userRepository.save(user);
        }
    }
}

我们通过defaultOAuth2User.getAuthorities()获取到映射后的角色信息,并将其与用户信息存储到数据库中。

UserRepository和RoleRepository为持久化容器类。


最后我们向SecurityFilterChain加入OAuth2 Login配置:

    @Autowired
    UserRepositoryOAuth2UserHandler userHandler;

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeRequests(authorizeRequests ->
                        authorizeRequests.anyRequest().authenticated()
                )
                .oauth2Login(oauth2login -> {
                    SavedUserAuthenticationSuccessHandler successHandler = new SavedUserAuthenticationSuccessHandler();
                    successHandler.setOauth2UserHandler(userHandler);
                    oauth2login.successHandler(successHandler);
                });
      
      	...
        
        return http.build();
    }

创建Spring Cloud Gateway应用程序

本节中我们将在Spring Cloud Gateway中通过 Spring Security OAuth2 Login 启用OpenID Connect身份验证,并将Access Token中继到下游服务。

Maven依赖
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-gateway</artifactId>
  <version>3.1.2</version>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-client</artifactId>
  <version>2.6.7</version>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
  <version>2.6.7</version>
</dependency>

<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-data-redis</artifactId>
  <version>2.6.3</version>
</dependency>

<dependency>
  <groupId>io.netty</groupId>
  <artifactId>netty-all</artifactId>
  <version>4.1.76.Final</version>
</dependency>

配置

首先我们在application.yml添加以下属性:

server:
  port: 8070
  servlet:
    session:
      cookie:
        name: GATEWAY-CLIENT

这里指定了cookie name为GATEWAY-CLIENT,避免与授权服务JSESSIONID冲突。


通过Spring Cloud Gateway路由到资源服务器:

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id: resource-server
          uri: http://127.0.0.1:8090
          predicates:
            Path=/resource/**
          filters:
            - TokenRelay

TokenRelay 过滤器将提取存储在用户会话中的访问令牌,并将其作为Authorization标头添加到传出请求中。这允许下游服务对请求进行身份验证。


我们将在application.yml中添加OAuth2客户端信息:

spring:
  security:
    oauth2:
      client:
        registration:
          messaging-gateway-oidc:
            provider: gateway-client-provider
            client-id: relive-client
            client-secret: relive-client
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
            scope:
              - openid
              - profile
            client-name: messaging-gateway-oidc
        provider:
          gateway-client-provider:
            authorization-uri: http://127.0.0.1:8080/oauth2/authorize
            token-uri: http://127.0.0.1:8080/oauth2/token
            jwk-set-uri: http://127.0.0.1:8080/oauth2/jwks
            user-info-uri: http://127.0.0.1:8080/userinfo
            user-name-attribute: sub

OpenID Connect 使用一个特殊的权限范围值 openid 来控制对 UserInfo 端点的访问,其他信息与上节中授权服务注册客户端信息参数保持一致。


我们通过Spring Security拦截未认证请求到授权服务器进行认证。为了简单起见, CSRF被禁用。

@Configuration(proxyBeanMethods = false)
@EnableWebFluxSecurity
public class OAuth2LoginSecurityConfig {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        http
                .authorizeExchange(authorize -> authorize
                        .anyExchange().authenticated()
                )
                .oauth2Login(withDefaults())
                .cors().disable();
        return http.build();
    }
}

Spring Cloud Gateway在完成OpenID Connect身份验证后,将用户信息和令牌存储在session会话中,所以添加spring-session-data-redis提供由 Redis 支持的分布式会话功能,在application.yml中添加以下配置:

spring:
  session:
    store-type: redis # 会话存储类型
    redis:
      flush-mode: on_save # 会话刷新模式
      namespace: gateway:session # 用于存储会话的键的命名空间
  redis:
    host: localhost
    port: 6379
    password: 123456

基于上述示例我们使用 Spring Cloud Gateway驱动身份验证,知道如何对用户进行身份验证,可以为用户获取令牌(在用户同意后),但不对通过Gateway的请求进行身份验证/授权(Spring Gateway Cloud并不是Access Token的受众目标)。这种方法背后的原因是一些服务是受保护的,而一些是公共的。即使在单个服务中,有时也只能保护几个端点而不是每个端点。这就是我将请求的身份验证/授权留给特定服务的原因。

当然从实现角度并不妨碍我们在Spring Cloud Gateway进行身份验证/授权,这只是一个选择问题。

搭建资源服务

本节中我们使用Spring Boot搭建一个简单的资源服务,示例中资源服务提供两个API接口,并通过Spring Security OAuth2资源服务配置保护。

Maven依赖
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <version>2.6.7</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
  <version>2.6.7</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
  <version>2.6.7</version>
</dependency>
配置

application.yml中添加jwk-set-uri属性:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://127.0.0.1:8080
          jwk-set-uri: http://127.0.0.1:8080/oauth2/jwks 

server:
  port: 8090

创建ResourceServerConfig类来配置Spring Security安全模块,@EnableMethodSecurity注解来启用基于注解的安全性:

@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@EnableMethodSecurity
public class ResourceServerConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests((authorize) -> authorize
                .anyRequest().authenticated()
                 )
                .oauth2ResourceServer()
                .jwt();
        return http.build();
    }
}

Spring Security资源服务在验证token提取权限默认使用claim中scopescp属性。

Spring Security JwtAuthenticationProvider通过JwtAuthenticationConverter辅助转换器提取权限等信息。

但是在本示例中内部化权限使用authorities属性,所以我们使用JwtAuthenticationConverter 手动提取权限:

    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        grantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");
        grantedAuthoritiesConverter.setAuthorityPrefix("");

        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }

在这里我们将权限属性指定为authorities,并完全删除权限前缀。


最后我们将创建用于示例中测试的API接口,使用@PreAuthorize保护接口必须由相应权限才能访问:

@RestController
public class ArticleController {

    List<String> article = new ArrayList<String>() {{
        add("article1");
        add("article2");
    }};

    @PreAuthorize("hasAuthority('read')")
    @GetMapping("/resource/article/read")
    public Map<String, Object> read(@AuthenticationPrincipal Jwt jwt) {
        Map<String, Object> result = new HashMap<>(2);
        result.put("principal", jwt.getClaims());
        result.put("article", article);
        return result;
    }

    @PreAuthorize("hasAuthority('write')")
    @GetMapping("/resource/article/write")
    public String write(@RequestParam String name) {
        article.add(name);
        return "success";
    }
}

测试我们的应用程序

在我们启动完成服务后,我们在浏览器中访问 http://127.0.0.1:8070/resource/article/read ,我们将重定向到授权服务登录页,如图所示:

在这里插入图片描述


在我们输入用户名密码(admin/password)后,将获取到请求响应信息:

在这里插入图片描述


admin用户所属角色是ROLE_ADMIN,所以我们尝试请求 http://127.0.0.1:8070/resource/article/write?name=article3

在这里插入图片描述


注销登录后,我们同样访问 http://127.0.0.1:8070/resource/article/read ,不过这次使用Github登录,响应信息如图所示:

在这里插入图片描述

可以看到响应信息中用户已经切换为你的Github用户名。


Github登录的用户默认赋予角色为ROLE_OPERATION,而ROLE_OPERATION是没有 http://127.0.0.1:8070/resource/article/write?name=article3 访问权限,我们来尝试测试下:

在这里插入图片描述

结果我们请求被拒绝,403状态码提示我们没有访问权限。

结论

本文中您了解到如何使用Spring Cloud Gateway结合OAuth2保护微服务。在示例中浏览器cookie仅存储sessionId,JWT访问令牌并没有暴露给浏览器,而是在内部服务中流转。这样我们体验到了JWT带来的优势,也同样利用cookie-session弥补了JWT的不足,例如当我们需要实现强制用户登出功能。

与往常一样,本文中使用的源代码可 在 GitHub 上获得。

spring boot学习(三)
weixin_42102703的博客
02-07 595
spring cloud gateway网关,集成认证中心,使用bearer token进行认证,生成网关swagger
全网最全的微服务+Outh2套餐,Gateway整合Oauth2!(入门到精通,附源码)满足你的味蕾需要(三)
white_mvlog的博客
06-06 1121
上篇文章主要讲解Oauth2模块、user-service模块、feign模块,那么作为重中之重的gateway,我们将其做成资源服务器来进行开发。针对oauth的推出登录如何解决以及如何整合第三方应用进行登录(如:gitee平台)
springboot整合Oauth2,GateWay实现网关登录授权验证
12-14
springboot整合Oauth2,GateWay实现网关登录授权验证
【实战】Spring Cloud Gateway 整合 OAuth2
最新发布
2401_84103818的博客
05-01 584
这样,等真的沉下心来学习,不至于被找资料分散了心神。另外,给大家安排了一波学习面试资料:以上就是本文的全部内容,希望对大家的面试有所帮助,祝大家早日升职加薪迎娶白富美走上人生巅峰!这样,等真的沉下心来学习,不至于被找资料分散了心神。另外,给大家安排了一波学习面试资料:[外链图片转存中…(img-88mXgLUW-1714525635696)][外链图片转存中…(img-3JlbLTDH-1714525635697)]
spring.security.oauth2.resourceserver.jwt.jwk-set-url 负载载均衡配置
qq_19746193的博客
10-27 2060
1、在网关@EnableWebFluxSecurity类中配解码负载均衡。2、在@EnableWebSecurity定义的类下定义。
Spring Cloud Gateway+Oauth2 实现统一认证和鉴权
const_
03-25 8469
前言 应用架构: 认证服务负责认证,网关负责校验认证和鉴权,其他API服务负责处理自己的业务逻辑。 安全相关的逻辑只存在于认证服务和网关服务中,其他服务只是单纯地提供服务而没有任何安全相关逻辑。 JWT认证流程: 1、用户使用账号和密码发出post请求; 2、服务器使用私钥创建一个jwt; 3、服务器返回这个jwt给浏览器; 4、浏览器将该jwt串在请求头中像服务器发送请求; 5、服务器验证该jwt; 6、返回响应的资源给浏览器。 JWT解析 JWT使用场景: 授权:这是JWT使用最多的场景,一旦用户登
SpringCloud Gateway 集成 oauth2 实现统一认证授权_03
Gblfy_Blog
08-18 5238
文章目录一、网关搭建1. 引入依赖2. 配置文件3. 增加权限管理器4. 自定义认证接口管理类5. 增加网关层的安全配置6. 搭建授权认证中心二、搭建产品服务2.1. 创建boot项目2.2. 引入依赖2.3. controller2.4. 启动类2.5. 配置四、测试验证4.1. 启动nacos4.2. 启动认证中心4.3. 启动产品服务4.3. 请求认证授权中心4.4. 网关请求产品模块4.5. 获取token4.6. 携带token请求产品服务4.7. 直接请求产品服务4.8. 请求结果比对五、总结
Spring Cloud Gateway 结合OAuth2提供UAA服务
duan18888的博客
05-19 479
微服务做用户认证和授权一直都是一个难点,随着OAuth2.0的密码模式被作废,更是难上加难了。今天胖哥群里的一个群友搭建用户认证授权体系的时候遇到了一些棘手的问题,这让胖哥觉得是时候分享一些思路出来了。 两种思路 通常微服务的认证和授权思路有两种: 所有的认证授权都由一个独立的用户认证授权服务器负责,它只负责发放Token,然后网关只负责转发请求到各个微服务模块,由微服务各个模块自行对Token进行校验处理。 另一种是网关不但承担了流量转发作用,还承担认证授权流程,当前请求的认证信息由网关中继给下游
springcloud-gateway-oauth2:这是一个SpringCloud gateway +oauht2.0+jwt 实现微服务的认证和授权
05-11
1.springcloud-gateway-oauth2 这是一个SpringCloud gateway +oauht2.0+jwt 实现微服务的认证和授权 (记得修改 redis连接 和jdbc连接) 2.安装naocs 3.测试 1.访问认证服务: 2. 访问资源服务: 3. 用户的信息
Spring Cloud Gateway使用Token验证详解
08-26
主要介绍了Spring Cloud Gateway使用Token验证详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
sample-gateway-oauth2login:结合了Spring Cloud GatewaySpring Security OAuth2的示例应用程序
01-30
sample-gateway-oauth2login:结合了Spring Cloud GatewaySpring Security OAuth2的示例应用程序
springcloud整合oauth2和jwt
04-09
springcloud整合oauth2和jwt实现权限认证,整合mybaits
详解SpringCloudGateway内存泄漏问题
08-18
主要介绍了详解SpringCloudGateway内存泄漏问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
这套Spring Cloud Gateway+Oauth2终极权限解决方案升级了!
weixin_62421895的博客
07-14 2309
首先还是来聊聊这套解决方案的实现思路,我们理想的解决方案应该是这样的,认证服务负责统一认证,网关服务负责校验认证和鉴权,其他API服务则负责处理自己的业务逻辑。安全相关的逻辑只存在于认证服务和网关服务中,其他服务只是单纯地提供服务而没有任何安全相关逻辑。这套解决方案中相关服务的划分如下:micro-oauth2-gateway :网关服务,负责请求转发和鉴权功能,整合Spring Security+Oauth2; micro-oauth2-auth :认证服务,负责对登录用户进行认证,整合Spring Se
springcloud整合Gateway+Oauth2 超级详解
weixin_45592424的博客
09-09 1222
对于springcloud 如何整合gateway网关和oauth2 实现鉴权和授权两大功能
Spring Cloud Gateway + Oauth2 实现统一认证和鉴权!
weixin_47600724的博客
12-29 2560
接下来我们就可以搭建网关服务了,它将作为Oauth2的资源服务、客户端服务使用,对访问微服务的请求进行统一的校验认证和鉴权操作。我们首先来搭建认证服务,它将作为Oauth2的认证服务使用,并且网关服务的鉴权功能也需要依赖它。最后我们搭建一个API服务,它不会集成和实现任何安全相关逻辑,全靠网关来保护它。接下来我们来演示下微服务系统中的统一认证鉴权功能,所有请求均通过网关访问。
这套Spring Cloud Gateway+Oauth2终极权限解决方案升级了
Cr1556648487的博客
08-18 1268
首先还是来聊聊这套解决方案的实现思路,我们理想的解决方案应该是这样的,认证服务负责统一认证,网关服务负责校验认证和鉴权,其他API服务则负责处理自己的业务逻辑。在微服务系统中实现权限功能时,我们不应该把重复的权限校验功能集成到每个独立的API服务中去,而应该在网关做统一处理,然后通过认证中心去统一认证,这样才是优雅微服务权限解决方案!当JWT令牌过期时,使用接口返回的refreshToken获取新的JWT令牌,访问地址:http://localhost:9201/auth/oauth/token。...
OAuth2系列】Spring Cloud Gateway 作为OAuth2 Client接入第三方单点登录代码实践
c18213590220的博客
12-06 2005
Spring Cloud GatewaySpring Cloud生态系统中的一个组件,主要用于构建微服务架构中的网关服务。它提供了一种灵活而强大的方式来路由请求、过滤请求以及添加各种功能,如负载均衡、熔断、安全性等。通过将Spring Cloud Gateway作为OAuth2 Client,可以实现用户在系统中的统一认证体验。用户只需要一次登录,即可访问多个微服务,避免了在每个服务中都进行独立的认证,下游微服务只需要专注自己的业务代码即可。
springcloudgateway整合oauth2
06-28
### 回答1: Spring Cloud Gateway可以通过整合OAuth2来实现安全认证和授权功能。具体步骤如下: 1. 引入Spring Security和Spring Security OAuth2依赖。 2. 配置Spring Security和OAuth2的相关配置,包括认证服务器地址、客户端ID和密钥等。 3. 在Spring Cloud Gateway中配置路由规则,并添加安全过滤器,将请求转发到认证服务器进行认证和授权。 4. 在路由规则中添加需要进行安全认证和授权的路径和方法。 5. 在授权成功后,将令牌添加到请求头中,以便后续服务可以获取到令牌信息。 通过以上步骤,就可以实现Spring Cloud Gateway的安全认证和授权功能。 ### 回答2: Spring Cloud Gateway是一个轻量级的API网关,而OAuth2是一种基于授权的安全协议,使用Spring Cloud Gateway整合OAuth2可以为API提供更安全的保护。 首先,我们需要在应用程序中引入OAuth2依赖,如下所示: ``` <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>2.3.5.RELEASE</version> </dependency> ``` 接下来,我们需要配置OAuth2具体的认证授权过程。在application.yml文件中进行配置,如下所示: ``` spring: security: oauth2: client: registration: client1: client-id: client1 client-secret: secret1 scope: read,write client-authentication-method: post authorization-grant-type: authorization_code redirect-uri: http://localhost:8081/oauth2/callback client-name: Client 1 provider: provider1 ``` 上述配置中,我们设置了客户端ID、秘钥、范围、客户端认证方式、授权类型等必要参数。其中,“authorization_code”是OAuth2的一种授权类型,通过授权码来获取令牌,这是最常用的一种授权类型。 此外,我们还需要在Gateway中进行相关的配置,包括路由规则、认证过滤器、访问权限控制等。具体实现可以参考springcloudgateway的官方文档。 总的来说,使用Spring Cloud Gateway整合OAuth2可以为API提供更安全的保护,同时也为开发人员提供了更多的灵活性和高效性。 ### 回答3: Spring Cloud GatewaySpring Cloud的一个新项目,它基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术,提供了一种构建API网关的方式。OAuth2是一种基于标准的授权协议,允许客户端(包括第三方应用程序和用户代理)访问受保护的资源。那么,如何在Spring Cloud Gateway中集成OAuth2呢? 首先,我们需要在Spring Cloud Gateway中引入spring-cloud-starter-oauth2依赖。然后,在application.yml配置文件中添加以下配置: ``` spring: security: oauth2: client: registration: custom-client: provider: custom-provider clientId: custom-client-id clientSecret: custom-client-secret authorizationGrantType: authorization_code redirectUriTemplate: "{baseUrl}/login/oauth2/code/{registrationId}" scope: - read - write provider: custom-provider: authorizationUri: https://custom-provider.com/oauth2/authorize tokenUri: https://custom-provider.com/oauth2/token userInfoUri: https://custom-provider.com/oauth2/userinfo userNameAttribute: sub ``` 上面的配置中,我们定义了一个名为custom-client的客户端,该客户端使用了我们自定义的OAuth2提供程序custom-provider。在这里,我们还定义了客户端ID和客户端密码,以及授权类型和重定向URI。此外,我们还定义了客户端的作用域和OAuth2提供程序的授权URI、令牌URI和用户信息URI。 在配置完成后,我们需要为Spring Cloud Gateway定义一个OAuth2路由过滤器。可以使用spring-security-oauth2-autoconfigure自动配置这个过滤器。在application.yml中添加以下配置: ``` spring: cloud: gateway: default-filters: - OAuth2GatewayFilterFactory ``` 注意,OAuth2GatewayFilterFactory是spring-security-oauth2-autoconfigure自动配置的名称。 现在,我们可以在Spring Cloud Gateway的路由定义中使用OAuth2路由过滤器了。例如: ``` spring: cloud: gateway: routes: - id: resource-service uri: http://localhost:8080 predicates: - Path=/resource/** filters: - OAuth2=custom-client ``` 在上面的路由定义中,我们使用了自定义OAuth2客户端custom-client。这将强制执行OAuth2认证,以确保只有已验证用户才能访问资源。 最后,我们需要创建一个OAuth2登录页面。使用spring-security-oauth2-autoconfigure依赖可以自动创建登录页面。在application.yml中添加以下配置: ``` spring: security: oauth2: client: registration: custom-client: clientName: Custom Client ``` 上面的配置中,我们定义了客户端的名称为Custom Client。当用户尝试访问受保护的资源时,系统将调用OAuth2登录页面并要求它输入凭据。 这就是在Spring Cloud Gateway中集成OAuth2的过程。通过使用Spring Cloud GatewayOAuth2,我们可以轻松地构建和保护API网关,实现更好的安全性和可扩展性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
写文章

热门文章

  • Spring Security整合CAS 10669
  • SpringGateway使用SpringSecurity防止CSRF攻击 7397
  • gitlab回退commit 4418
  • 将JWT与Spring Security OAuth2结合使用 4172
  • Spring项目的resources目录下的文件读取 4073

分类专栏

  • Spring Security OAuth2 14篇
  • Spring Security 1篇
  • Java基础 1篇
  • Spring Cloud 1篇
  • Spring Boot 9篇
  • Docker 4篇
  • 单元测试 1篇
  • Redis 6篇
  • 笔记 3篇

最新评论

  • Spring AI和Ollama

    hunanjj: chatClient.generate(message); 这个方法调用报错 java.lang.RuntimeException: [404] Not Found - {"error":"model 'mistral' not found, try pulling it first"}

  • Spring Security OAuth2 Opaque 令牌的简单使用指南

    ReLive27: 这个在框架里已经实现了

  • Spring Security OAuth2 Opaque 令牌的简单使用指南

    JacketLiao: code换token由框架完成,不需要取code么?

  • Spring Security OAuth2 Opaque 令牌的简单使用指南

    ReLive27: 首先我们先看下Spring Security 是如何处理回调接口的,在OAuth2AuthorizationCodeGrantFilter过滤器中matchesAuthorizationResponse方法里会获取接口的参数信息,如果参数中有code和state参数将返回true,将继续后面的校验,所以对于回调接口定义并没有明确规定。至于我文中所写的回调接口其实是按照OAuth2 登录的回调接口形式来定义的,可以查看OAuth2LoginAuthenticationFilter过滤器,这也是Spring Security为了区分OAuth2登录功能和OAuth2授权码模式

  • Spring Security OAuth2 Opaque 令牌的简单使用指南

    JacketLiao: 看了你的客户端,没有这个URL啊: redirect-uri: "http://127.0.0.1:8070/login/oauth2/code/{registrationId}",授权服务器回调会成功吗?

您愿意向朋友推荐“博客详情页”吗?

  • 强烈不推荐
  • 不推荐
  • 一般般
  • 推荐
  • 强烈推荐
提交

最新文章

  • 使用 Vue.js 构建 OAuth2 授权同意页面
  • 【转载】我从被解雇中学到的 5 个改变人生的教训,这将使你领先于 97% 的人
  • Spring AI和Ollama
2024年4篇
2023年3篇
2022年14篇
2021年3篇
2020年19篇

目录

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

深圳SEO优化公司肇庆网站推广系统报价丽水企业网站建设报价德宏网站定制多少钱河池百姓网标王太原网站制作推荐濮阳seo推荐湘潭百度网站优化大芬百度爱采购公司梅州SEO按天收费多少钱娄底阿里店铺托管价格迪庆百搜标王报价黔南网站优化推广推荐喀什seo排名报价常德模板推广报价漳州网站建设多少钱本溪网站优化推广多少钱木棉湾关键词按天计费公司安阳百度爱采购报价龙华网络营销哪家好北京网站推广报价芜湖网站搭建推荐泸州网站关键词优化多少钱哈密网络推广报价洛阳网站排名优化天水关键词按天扣费公司和田百度竞价公司泰安网络推广价格河源网站优化按天计费坪地模板网站建设推荐龙华网站制作公司歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

深圳SEO优化公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化