【问题标题】:How to request different sets of scopes for different types of users?如何为不同类型的用户请求不同的范围?
【发布时间】:2023-01-29 21:36:38
【问题描述】:

我有一个 React 应用程序,我有一个 Spring Boot 后端,目前使用 Spring Security 通过 Google 和 Azure 使用 OAuth2 代码授权流程对用户进行身份验证。

当我只有一种类型的用户要求我请求一组特定的范围(电子邮件访问和日历访问)时,这一切都很好。我能够以 Google/Azure 用户身份登录,请求所需范围的权限,获取访问令牌并访问用户的电子邮件和日历。让我们称这种类型的用户为“USER1”。

现在我有另一种类型的用户需求。对于这个用户,我只需要访问他们的个人资料信息,而不需要与日历等集成。他们仍然需要能够通过 React 应用程序使用 Google/Azure 登录(当然使用与 USER1 不同的页面/按钮)。让我们称这种类型的用户为“USER2”。

我似乎无法在 Spring Security 中解决如何配置 USER1 以触发登录过程并让 Spring 向 Google/Azure 询问一组范围以及让 USER2 触发登录过程并向 Google/Azure 询问另一组范围。

我现在的安全配置看起来像这样:

@EnableWebSecurity
public class OAuth2LoginSecurityConfig {

    private final long MAX_AGE = 3600;
    private final CustomOAuth2UserService customOAuth2UserService; //for google
    private final CustomOidcUserService customOidcUserService; //for azure
    
    private final MyUserRepository myUserRepository;
    private final OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler;
    private final OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler;
    private final AppConfig appConfig;

    public OAuth2LoginSecurityConfig(CustomOAuth2UserService customOAuth2UserService, CustomOidcUserService customOidcUserService, MyUserRepository myUserRepository, OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler, OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler, AppConfig appConfig) {
        this.customOAuth2UserService = customOAuth2UserService;
        this.customOidcUserService = customOidcUserService;
        this.myUserRepository = myUserRepository;
        this.oAuth2AuthenticationSuccessHandler = oAuth2AuthenticationSuccessHandler;
        this.oAuth2AuthenticationFailureHandler = oAuth2AuthenticationFailureHandler;
        this.appConfig = appConfig;
    }

    @Bean
    public HttpCookieOAuth2AuthorizationRequestRepository cookieAuthorizationRequestRepository() {
        return new HttpCookieOAuth2AuthorizationRequestRepository();
    }

    @Bean
    public AuthenticateRequestsFilter tokenAuthenticationFilter() {
        return new AuthenticateRequestsFilter(myUserRepository);
    }

    @Bean
    public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
        DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
                new DefaultAuthorizationCodeTokenResponseClient();

        OAuth2AccessTokenResponseHttpMessageConverter tokenResponseHttpMessageConverter =
                new OAuth2AccessTokenResponseHttpMessageConverter();
        tokenResponseHttpMessageConverter.setTokenResponseConverter(new CustomTokenResponseConverter());
        RestTemplate restTemplate = new RestTemplate(Arrays.asList(
                new FormHttpMessageConverter(), tokenResponseHttpMessageConverter));
        restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());

        accessTokenResponseClient.setRestOperations(restTemplate);
        return accessTokenResponseClient;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .cors()
                .and()
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
            .csrf().disable()
            .formLogin().disable()
            .httpBasic().disable()
            .exceptionHandling()
                .authenticationEntryPoint(new RestAuthenticationEntryPoint())
                .and()
            .authorizeHttpRequests()
                .antMatchers("/", "/error", "/data", "/data/*").permitAll()
                .antMatchers(HttpMethod.GET, "/someurl/*/thingx", "/someurl/*/thingy").permitAll()
                .antMatchers("/auth/**", "/oauth2/**", "/user-logout").permitAll()
                .anyRequest().authenticated()
                .and()
            .oauth2Login()
                .authorizationEndpoint()
                .baseUri("/oauth2/authorize")
                .authorizationRequestRepository(cookieAuthorizationRequestRepository())
                .and()
                .redirectionEndpoint()
                .baseUri("/oauth2/callback/*")
                .and()
                .tokenEndpoint()
                .accessTokenResponseClient(accessTokenResponseClient())
                .and()
                .userInfoEndpoint()
                .userService(customOAuth2UserService)
                .oidcUserService(customOidcUserService)
                .and()
                .successHandler(oAuth2AuthenticationSuccessHandler)
                .failureHandler(oAuth2AuthenticationFailureHandler)
                .and()
            .addFilterBefore(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    @Bean
    public CorsFilter corsFilter() {

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin(appConfig.getClientBaseUrl().toString());
        config.addAllowedHeader("*");
        config.addAllowedMethod("GET");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("PATCH");
        config.addAllowedMethod("DELETE");
        config.addAllowedMethod("OPTIONS");
        config.setMaxAge(MAX_AGE);
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

我的application.yml看起来像这样(一些不相关的东西被删除):

spring:
  mvc:
    format:
      date: yyyy-MM-dd
      time: HH:mm:ss
  security:
    oauth2:
      client:
        provider:
          azure:
            token-uri: https://login.microsoftonline.com/common/oauth2/v2.0/token
            authorization-uri: https://login.microsoftonline.com/common/oauth2/v2.0/authorize
            user-info-uri: https://graph.microsoft.com/oidc/userinfo
            jwk-set-uri: https://login.microsoftonline.com/common/discovery/v2.0/keys
            user-name-attribute: name
            user-info-authentication-method: header
          google:
            authorization-uri: https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline
        registration:
          azure:
            client-id: FROM_ENV
            client-secret: FROM_ENV
            redirect-uri: "{baseUrl}/oauth2/callback/{registrationId}"
            authorization-grant-type: authorization_code
            scope:
              - openid
              - email
              - profile
              - offline_access
              - https://graph.microsoft.com/Calendars.ReadWrite
              - https://graph.microsoft.com/User.Read
          google:
            client-id: FROM_ENV
            client-secret: FROM_ENV
            redirect-uri: "{baseUrl}/oauth2/callback/{registrationId}"
            scope:
              - email
              - profile
              - https://www.googleapis.com/auth/gmail.send
              - https://www.googleapis.com/auth/calendar

app:
  client-base-url: SOME_URL
  server-base-url: SOME_URL

非常感谢任何帮助或资源!

【问题讨论】:

  • 范围适用于客户端而不是用户,但用户可以选择为客户端授予哪些范围。我看到的唯一方法是为每个授权服务器配置两个客户端,为每种类型的用户配置一个客户端。

标签: java spring-boot spring-security oauth-2.0 scopes


【解决方案1】:

看起来您只是通过链接 oauth 和 oidc 提供程序来进行一次 oauth 调用。您是否尝试过两次单独的 oauth 调用,每个提供者一个?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多