问题
I upgraded our authorization server from Spring Boot 1.5.13.RELEASE to 2.1.3.RELEASE, and now I can authenticate, but I can no longer access the site. Here is the resulting URL and error after the POST to /login.
https://auth-service-test-examle.cfapps.io/oauth/authorize?client_id=proxy-service&redirect_uri=http://test.example.com/login&response_type=code&state=QihbF4
OAuth Error
error="invalid_request", error_description="At least one redirect_uri must be registered with the client."
To troubleshoot, I started a fresh project based on the Spring Security 5.1.4.RELEASE sample "oauth2authorizationserver." I layered on the features used in our Spring Boot 1.5.13 authorization server making sure the unit tests passed (except one test class). If I @Ignore the failing tests and deploy the code I get the problem described above.
The problem is reproducible in the AuthenticationTests.loginSucceeds() JUnit test that passed before the upgrade. It expects a 302, but now it gets a 403 because it goes to the root of the authentication server. I published the entire example on GitHub spring-security-5-upgrade_sso-auth-server
Clone the project and run the unit tests and you will see the failures.
Here are some of the key settings that can be found in the project on GitHub.
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
private final String privateKey;
private final String publicKey;
private final AuthClientDetailsService authClientDetailsService;
private final AuthenticationManager authenticationManager;
private final AuthUserDetailsService authUserDetailsService;
@Autowired
public AuthServerConfig(
@Value("${keyPair.privateKey}") final String privateKey,
@Value("${keyPair.publicKey}") final String publicKey,
final AuthClientDetailsService authClientDetailsService,
final AuthUserDetailsService authUserDetailsService,
final AuthenticationConfiguration authenticationConfiguration) throws Exception {
this.privateKey = privateKey;
this.publicKey = publicKey;
this.authClientDetailsService = authClientDetailsService;
this.authUserDetailsService = authUserDetailsService;
this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
}
@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(authClientDetailsService);
}
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager)
.accessTokenConverter(accessTokenConverter())
.userDetailsService(authUserDetailsService)
.tokenStore(tokenStore());
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(privateKey);
converter.setVerifierKey(publicKey);
return converter;
}
}
public class GlobalAuthenticationConfig extends GlobalAuthenticationConfigurerAdapter {
private final AuthUserDetailsService authUserDetailsService;
@Autowired
public GlobalAuthenticationConfig(final AuthUserDetailsService authUserDetailsService) {
this.authUserDetailsService = authUserDetailsService;
}
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(authUserDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
}
@Configuration
@Order(-20)
protected class LoginConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers().antMatchers(LOGIN, "/oauth/authorize", "/oauth/confirm_access")
.and()
.logout().permitAll()
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin().loginPage(LOGIN).permitAll();
// @formatter:on
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManager);
}
}
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final AuthUserDetailsService authUserDetailsService;
@Autowired
public WebSecurityConfig(AuthUserDetailsService authUserDetailsService) {
this.authUserDetailsService = authUserDetailsService;
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(authUserDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
}
What else needs to be done in Spring Boot 2.1.3.RELEASE to redirect the user back to the original webpage?
回答1:
It's important that OAuth 2.0 clients register a redirect_uri
with Authorization Servers as an Open Redirector mitigation. As such, Spring Boot 2.1.x has this as its default behavior, which is why you're seeing the error.
You can do one of two things:
Add redirect_uri
s, one for each client
Ideally, you'd update your clients to each have a registered redirect_uri
, which would likely be retrieved in an implementation of ClientDetailsService
:
public class MyClientDetailsService implements ClientDetailsService {
private final MyRespository myRepository;
public ClientDetails loadClientByClientId(String clientId) {
return new MyClientDetails(this.myRepository.getMyDomainObject(clientId));
}
private static class MyClientDetails extends MyDomainObject implements ClientDetails {
private final MyDomainObject mine;
public MyClientDetails(MyDomainObject delegate) {
this.delegate = delegate;
}
// implement ClientDetails methods, delegating to your domain object
public Set<String> getRegisteredRedirectUri() {
return this.delegate.getRedirectUris();
}
}
}
This setup with the private subclass - while not necessary - is nice because it doesn't tie the domain object directly to Spring Security.
Add a custom RedirectResolver
Or, you can customize the RedirectResolver
, though this wouldn't secure against Open Redirects, which was the original reason for the change.
public MyRedirectResolver implements RedirectResolver {
private final RedirectResolver delegate = new DefaultRedirectResolver();
public String resolveRedirect(String redirectUri, ClientDetails clientDetails) {
try {
return this.delegate.resolveRedirect(redirectUri, clientDetails);
} catch ( InvalidRequestException ire ) {
// do custom resolution
}
}
}
来源:https://stackoverflow.com/questions/55382404/after-spring-boot-2-upgade-authorization-server-returns-at-least-one-redirect-u