【发布时间】: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