我正在尝试通过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")
端点?我正在使用Spring Boot 2.3.1
谢谢