使用自定义客户端Spring Security OAuth2获得太多重定向

时间:2020-07-29 21:12:56

标签: spring-boot spring-security-oauth2

我正在尝试通过Spring Boot设置OAuth2登录。

我配置了Google,Facebook和一个自定义客户端。谷歌和Facebook运行良好。但是,当我调用自定义客户端配置时,会收到错误too many redirects

我调试了代码,发现请求来自OAuth2AuthorizationRequestRedirectFilter class

首先,它调用OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request);

在此方法中,首先获取registrationId,在我的情况下为zapier

最终它将调用this.sendRedirectForAuthorization(request, response, authorizationRequest);

此方法最终会调用response.sendRedirect(redirectUrl);

zapier情况下的重定向URL是

http://localhost:8080/springbootoauth2app/oauth2/authorization/zapier?response_type=code&client_id=zapier&scope=openid%20USE_WEB_SERVICES%20address%20phone&state=6qjwS_tZnX4Y1F0BeM1Hzek6vt_4ySdAwhayOBGnZSE%3D&redirect_uri=http://localhost:8080/springbootoauth2app/oauth2/code/zapier&nonce=Q5ETGHZwuVK--hYyKM7mnnnpOSm_4UFc43VU7oLiZbY

所有工作都在Spring之前完成。

问题是在调用response.sendRedirect(zapier)之后进入了循环,并且我收到太多重定向错误。因为response.sendRedirect()一次又一次地打电话。如果是Google和Facebook。一旦response.sendRidirect()调用,它就会转到Google和Facebook页面。

这是我的配置

application-oauth2.properties

spring.security.oauth2.client.registration.google.client-id=368238083842-3d4gc7p54rs6bponn0qhn4nmf6apf24a.apps.googleusercontent.com
spring.security.oauth2.client.registration.google.client-secret=2RM2QkEaf3A8-iCNqSfdG8wP

spring.security.oauth2.client.registration.facebook.client-id=151640435578187
spring.security.oauth2.client.registration.facebook.client-secret=3724fb293d401245b1ce7b2d70e97571

我的OAuth2配置:

@Configuration
@PropertySource("application-oauth2.properties")
public class OAuth2Configuration {

    // additional configuration for non-Spring Boot projects
    private static List<String> clients = Arrays.asList("google", "facebook", "zapier");

    private static String CLIENT_PROPERTY_KEY = "spring.security.oauth2.client.registration.";

    @Autowired
    private Environment env;

    @Bean
    public OAuth2AuthorizedClientService authorizedClientService(ClientRegistrationRepository clientRegistrationRepository) {
        return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
    }

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

    @Bean
    public ClientRegistrationRepository clientRegistrationRepository() {
    
        List<ClientRegistration> registrations = clients.stream()
                .map(c -> getRegistration(c))
                .filter(registration -> registration != null)
                .collect(Collectors.toList());
    
        return new InMemoryClientRegistrationRepository(registrations);
    }

    private ClientRegistration getRegistration(String client) {
    
        if (client.equals("zapier")) {
            ClientRegistration zapierClientRegistration = zapierClientRegistration();
            return zapierClientRegistration;
        }
    
        String clientId = env.getProperty(CLIENT_PROPERTY_KEY + client + ".client-id");

        if (clientId == null) {
            return null;
        }

        String clientSecret = env.getProperty(CLIENT_PROPERTY_KEY + client + ".client-secret");
        if (client.equals("google")) {
            return CommonOAuth2Provider.GOOGLE.getBuilder(client)
                .clientId(clientId)
                .clientSecret(clientSecret)
                .build();
        }
    
        if (client.equals("facebook")) {
            return CommonOAuth2Provider.FACEBOOK.getBuilder(client)
                .clientId(clientId)
                .clientSecret(clientSecret)
                .build();
        }

        return null;
    }

    private ClientRegistration zapierClientRegistration() {
        return ClientRegistration.withRegistrationId("zapier")
            .clientId("zapier") 
            .clientSecret("Zapier") 
            .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)   
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            .redirectUriTemplate("http://localhost:8080/springbootoauth2app/oauth2/code/zapier")
            .scope("openid", "USE_WEB_SERVICES", "address", "phone")    
            .authorizationUri("http://localhost:8080/springbootoauth2app/oauth2/authorization/zapier")  
            .tokenUri("http://localhost:8080/springbootoauth2app/oauth2/token")
            .userInfoUri("http://localhost:8080/springbootoauth2app/api/message")
            .userNameAttributeName(IdTokenClaimNames.SUB)
            .jwkSetUri("http://localhost:8080/springbootoauth2app/oauth2/.well-known/jwks.json")
            .clientName("Zapier")
            .build();
    }
}

我的安全配置

@Configuration
@EnableGlobalMethodSecurity(
    prePostEnabled = true, 
    order = 0, 
    mode = AdviceMode.PROXY,
    proxyTargetClass = false
)
@EnableWebSecurity
public class SecurityConfiguration  {
    
    private static final String KEY_STORE_FILE = "oauth2/basit-jwt.jks";
    private static final String KEY_STORE_PASSWORD = "basit-pass";
    private static final String KEY_ALIAS = "basit-oauth-jwt";

    @Autowired 
    private UserPrincipalService userPrincipalService;

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

    @Autowired
    protected void configureGlobal(AuthenticationManagerBuilder builder) throws Exception {
         builder
             .userDetailsService(this.userPrincipalService)
                 .passwordEncoder(passwordEncoder())
             .and()
                 .eraseCredentials(true);
    }

    @Configuration
    @Order(1)
    public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
        
        @Override
        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }

        @Bean
        public KeyPair keyPair() {
            ClassPathResource ksFile = new ClassPathResource(KEY_STORE_FILE);
            KeyStoreKeyFactory ksFactory = new KeyStoreKeyFactory(ksFile, KEY_STORE_PASSWORD.toCharArray());
            KeyPair keyPair = ksFactory.getKeyPair(KEY_ALIAS);
            return keyPair;
        }

        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
        }

        @Bean
        public OpaqueTokenIntrospector introspector() {
            return new JwtOpaqueTokenIntrospector();
        }

        @Bean
        public JWKSet jwkSet() {
        
            RSAKey.Builder builder = new RSAKey.Builder((RSAPublicKey)keyPair().getPublic())
                .keyUse(KeyUse.SIGNATURE)
                .algorithm(JWSAlgorithm.RS256)
                .keyID("bael-key-id");
        
            return new JWKSet(builder.build());
        }

        @Override
        protected void configure(HttpSecurity apiHttpSecurity) throws Exception {
        
            apiHttpSecurity.httpBasic().disable()
                .formLogin(AbstractHttpConfigurer::disable)
                .csrf(AbstractHttpConfigurer::disable)
                .antMatcher("/api/**")
                .authorizeRequests(
                    authorize -> authorize.mvcMatchers(HttpMethod.POST, "/oauth2/token").permitAll()
                        .mvcMatchers(HttpMethod.POST, "/oauth2/introspect").permitAll()
                        .mvcMatchers(HttpMethod.GET, "/api/message").hasAuthority("READ")  
                        .mvcMatchers(HttpMethod.POST, "/api/message").hasAuthority("WRITE")
                        .anyRequest().authenticated()
            )
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken)
            .sessionManagement(sessionManagement -> 
                sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
        }
    }

    @Configuration
    @Order(2)
    public static class OAuth2LoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
        
        @Autowired
        private CsrfTokenRepository csrfTokenRepository;
    
        @Override
        protected void configure(HttpSecurity oauthHttpSecurity) throws Exception {
        
            oauthHttpSecurity
                .antMatcher("/oauth2/**")
                .authorizeRequests(
                    authorize -> authorize.mvcMatchers(HttpMethod.GET, "/oauth2/login").permitAll()
                        .anyRequest().authenticated()
            )
           .oauth2Login(oauth2 -> 
               oauth2.loginPage("/oauth2/login")
               .authorizationEndpoint(authorization -> authorization
                    .baseUri("/oauth2/authorization")
                )
            )
            .csrf()
                .csrfTokenRepository(this.csrfTokenRepository);
        }
    }

    @Configuration
    public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
    
        @Bean
        protected SessionRegistry sessionRegistryImpl() {
            return new SessionRegistryImpl();
        }

         @Bean
        public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
            return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
        }
    
        @Bean
        public CsrfTokenRepository csrfTokenRepository(){
            HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
            return repository;
        }

        @Override
        public void configure(WebSecurity security) {
            security.ignoring().antMatchers("/css/**", "/img/**", "/webjars/**", "/favicon.ico");
        }

        @Override
        protected void configure(HttpSecurity security) throws Exception {

            security
                .authorizeRequests()
                    .antMatchers("/session/list").hasAuthority("VIEW_USER_SESSIONS")
                    .anyRequest()
                    .authenticated()
                .and()
                    .formLogin()
                    .loginPage("/login")
                    .failureUrl("/login?loginFailed")
                    .defaultSuccessUrl("/ticket/list")
                    .usernameParameter("username")
                    .passwordParameter("password")
                    .permitAll()
                .and()
                    .logout()
                    .logoutUrl("/logout")
                    .logoutSuccessUrl("/login?loggedOut")
                    .invalidateHttpSession(true)
                    .clearAuthentication(true)
                    .deleteCookies("JSESSIONID")
                    .permitAll()
                .and()
                    .sessionManagement()
                    .sessionFixation()
                    .changeSessionId()
                    .maximumSessions(1)
                    .maxSessionsPreventsLogin(true)
                    .sessionRegistry(this.sessionRegistryImpl())
                    .and()
                .and()
                    .csrf()
                        .csrfTokenRepository(csrfTokenRepository())
                        .requireCsrfProtectionMatcher((r) -> {
                            String m = r.getMethod();
                            return !r.getServletPath().startsWith("/api/") && ("POST".equals(m) || "PUT".equals(m) ||
                                    "DELETE".equals(m) || "PATCH".equals(m));
                    });

        }
    }
}

这是我的控制人

@Controller
@RequestMapping("/oauth2")
public class OAuth2LoginController {
    
    @GetMapping("/login")
    public String getLoginPage(Model model) {
        Iterable<ClientRegistration> clientRegistrations = null;
        ResolvableType type = ResolvableType.forInstance(clientRegistrationRepository).as(Iterable.class);
        if (type != ResolvableType.NONE && ClientRegistration.class.isAssignableFrom(type.resolveGenerics()[0])) {
            clientRegistrations = (Iterable<ClientRegistration>) clientRegistrationRepository;
        }

        String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = "oauth2/authorization";
    
        clientRegistrations.forEach(registration -> oauth2AuthenticationUrls.put(registration.getClientName(), DEFAULT_AUTHORIZATION_REQUEST_BASE_URI  + "/" + registration.getRegistrationId()));
        model.addAttribute("urls", oauth2AuthenticationUrls);

        return "oauth2/login";
    }

    @GetMapping("/authorization/zapier")
    public void authorizeZapier(@RequestParam Map<String,String> allParams) {
        String name = "basit";
    }
}
  • 为什么重定向时它不会到达@GetMapping("/authorization/zapier")端点?
  • 为什么会陷入循环?
  • 与localhost:8080有关吗?
  • 我应该将具有不同端口的单独项目用作授权端点吗?
  • 我的配置有问题吗?

我正在使用Spring Boot 2.3.1

谢谢

0 个答案:

没有答案