401在与google成功进行oauth2身份验证后由spring Boot 后端发送到angular frontend

b5lpy0ml  于 2022-11-21  发布在  Spring
关注(0)|答案(1)|浏览(94)

Sping Boot 的rest api在后面,angular在前面。大家好,我有一个问题后,成功的oauth2认证与谷歌。在srping引导调试我可以看到以下:默认Cors处理器:跳过:响应已包含“Access-Control-Allow-Origin”。然后,将401发送到angular,要求访问/api/user/ resource的完全身份验证,这是访问后端用户详细信息的根。WebConfig.java

import java.util.Locale;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;

@Configuration
public class WebConfig implements WebMvcConfigurer {

  private final long MAX_AGE_SECS = 3600;

  @Override
  public void addCorsMappings(CorsRegistry registry) {
    registry
      .addMapping("/**")
      .allowedOrigins("*")
      .allowedMethods(
        "HEAD",
        "OPTIONS",
        "GET",
        "POST",
        "PUT",
        "PATCH",
        "DELETE"
      )
      .maxAge(MAX_AGE_SECS);
  }

  @Bean
  public MessageSource messageSource() {
    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
    messageSource.setBasename("classpath:messages");
    messageSource.setDefaultEncoding("UTF-8");
    return messageSource;
  }

  @Bean
  public LocaleResolver localeResolver() {
    final CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver();
    cookieLocaleResolver.setDefaultLocale(Locale.ENGLISH);
    return cookieLocaleResolver;
  }

  @Override
  public Validator getValidator() {
    LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
    validator.setValidationMessageSource(messageSource());
    return validator;
  }
}

SecurityConfig.java

import java.util.Arrays;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.client.RestTemplate;

import com.springboot.dashboard.security.jwt.TokenAuthenticationFilter;
import com.springboot.dashboard.security.oauth2.DashBoardOAuth2UserService;
import com.springboot.dashboard.security.oauth2.DashBoardOidcUserService;
import com.springboot.dashboard.security.oauth2.HttpCookieOAuth2AuthorizationRequestRepository;
import com.springboot.dashboard.security.oauth2.OAuth2AccessTokenResponseConverterWithDefaults;
import com.springboot.dashboard.security.oauth2.OAuth2AuthenticationFailureHandler;
import com.springboot.dashboard.security.oauth2.OAuth2AuthenticationSuccessHandler;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
  prePostEnabled = true,
  securedEnabled = true,
  jsr250Enabled = true
)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Autowired
  private UserDetailsService userDetailsService;

  @Autowired
  private DashBoardOAuth2UserService dashBoardOAuth2UserService;

  @Autowired
  private DashBoardOidcUserService dashBoardOidcUserService;

  @Autowired
  private OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler;

  @Autowired
  private OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler;

  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder auth)
    throws Exception {
    auth
      .userDetailsService(userDetailsService)
      .passwordEncoder(passwordEncoder());
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .cors()
      .and()
      .sessionManagement()
      .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
      .and()
      .csrf()
      .disable()
      .formLogin()
      .disable()
      .httpBasic()
      .disable()
      .exceptionHandling()
      .authenticationEntryPoint(new RestAuthenticationEntryPoint())
      .and()
      .authorizeRequests()
      .antMatchers("/", "/error", "/api/all", "/api/auth/**", "/oauth2/**")
      .permitAll()
      .anyRequest()
      .authenticated()
      .and()
      .oauth2Login()
      .authorizationEndpoint()
      .authorizationRequestRepository(cookieAuthorizationRequestRepository())
      .and()
      .redirectionEndpoint()
      .and()
      .userInfoEndpoint()
      .oidcUserService(dashBoardOidcUserService)
      .userService(dashBoardOAuth2UserService)
      .and()
      .tokenEndpoint()
      .accessTokenResponseClient(authorizationCodeTokenResponseClient())
      .and()
      .successHandler(oAuth2AuthenticationSuccessHandler)
      .failureHandler(oAuth2AuthenticationFailureHandler);
    // Add our custom Token based authentication filter
    http.addFilterBefore(
      tokenAuthenticationFilter(),
      UsernamePasswordAuthenticationFilter.class
    );
  }

  @Bean
  public TokenAuthenticationFilter tokenAuthenticationFilter() {
    return new TokenAuthenticationFilter();
  }

  /*
   * By default, Spring OAuth2 uses
   * HttpSessionOAuth2AuthorizationRequestRepository to save the authorization
   * request. But, since our service is stateless, we can't save it in the
   * session. We'll save the request in a Base64 encoded cookie instead.
   */
  @Bean
  public HttpCookieOAuth2AuthorizationRequestRepository cookieAuthorizationRequestRepository() {
    return new HttpCookieOAuth2AuthorizationRequestRepository();
  }

  // This bean is load the user specific data when form login is used.
  @Override
  public UserDetailsService userDetailsService() {
    return userDetailsService;
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(10);
  }

  @Bean(BeanIds.AUTHENTICATION_MANAGER)
  @Override
  public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
  }

  private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeTokenResponseClient() {
    OAuth2AccessTokenResponseHttpMessageConverter tokenResponseHttpMessageConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
    tokenResponseHttpMessageConverter.setTokenResponseConverter(
      new OAuth2AccessTokenResponseConverterWithDefaults()
    );
    RestTemplate restTemplate = new RestTemplate(
      Arrays.asList(
        new FormHttpMessageConverter(),
        tokenResponseHttpMessageConverter
      )
    );
    restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
    DefaultAuthorizationCodeTokenResponseClient tokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
    tokenResponseClient.setRestOperations(restTemplate);
    return tokenResponseClient;
  }
}

提前感谢您的帮助。
oauth2登录成功导致用户数据成功写入数据库,但可以访问完整身份验证资源。

2ekbmq32

2ekbmq321#

WebSecurityConfigurerAdapter已被弃用,不要使用它(在spring-boot 3中甚至不再有它)。

@Bean
    SecurityFilterChain filterChain(HttpSecurity http) {
        // http configuration
        return http.build();
    }

资源服务器(REST API)

您可以使用一个spring-boot启动器here,而不是在java @Configuration中编写所有spring-boot-starter-oauth2-resource-server安全配置(CSRF、CORS、JWT解码器或令牌内省器、权限Map、公共路由):
第一个

客户端(Angular 应用程序)

使用OAuth2客户端库。我最喜欢的Angular是angular-auth-oidc-client。它将为您保存以下工作:

  • 将用户重定向到授权服务器进行登录
  • 处理从具有授权码授权服务器返回的重定向
  • 交换令牌的授权码(当然是访问令牌,但如果您请求offline_accessopenid作用域,还需要刷新和ID令牌)
  • 在访问令牌过期前自动刷新访问令牌(如果您获得了refresh-token)
  • 自动授权与配置模式匹配的请求(添加带有access-token的Authorization承载报头)
  • 配备角形路线防护装置
  • ...

授权服务器

除非你将资源服务器部署到Google云,否则它很可能无法直接使用Google授权服务器。你可以在它前面使用其他能够进行身份联合的授权服务器。Keycloak做得很好:

  • 运行Keycloak示例并将其配置为Google作为身份提供者
  • 配置您资源服务器以使用Keycloak作为授权服务器(如上面的代码所示)
  • 配置Angular以使用Keycloak作为授权服务器

我这里指的是Keycloak,但大多数严肃的OIDC提供商(无论是premize还是SaaS,如Auth 0、Okta等)都支持“社交”登录,并允许Google用户登录(以及Facebook、Github、Tweeter等)。

相关问题