Reputation: 67
I need to implement Single Sign-On application using Spring Boot (2.1.9.RELEASE) and OAuth2. I have created two client application and authentication server as well. When i hit client application URL it successfully redirect to the authentication server and validate username and password. But when it redirect back to the client application always gives below oauth error.
error="invalid_grant", error_description="Invalid redirect: http://localhost:8082/app1/login does not match one of the registered values: [http://localhost:8082/app1]"
Here i have noticed that always append /login path automatically to the end of redirect uri. Maybe it is the default behavior of the Spring Boot SSO. I have tried many ways to resolve this error but couldn't. Can anyone help me.
I have followed sample project sample project
Steps
Authentication Server
POM file
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>11</java.version>
<spring-cloud.version>Finchley.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.0-b170127.1453</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Main Class
@SpringBootApplication
@EnableResourceServer
public class TestProjectApplication {
public static void main(String[] args) {
SpringApplication.run(TestProjectApplication.class, args);
}
}
Security config class
@Configuration
@Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("john")
.password(passwordEncoder().encode("123"))
.roles("USER");
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Client Application
POM File
<properties>
<java.version>11</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-cloud.version>Finchley.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Main Class
@SpringBootApplication
@EnableOAuth2Sso
public class App1Application implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
public static void main(String[] args) {
SpringApplication.run(App1Application.class, args);
}
}
Application.yml
server:
port: 8082
servlet:
context-path: /app1
spring:
main:
allow-bean-definition-overriding: true
security:
basic:
enabled: false
oauth2:
client:
clientId: foo
clientSecret: bar
accessTokenUri: http://localhost:8081/auth/oauth/token
userAuthorizationUri: http://localhost:8081/auth/oauth/authorize
resource:
userInfoUri: http://localhost:8080/auth/user/me
Upvotes: 1
Views: 1343
Reputation: 686
You can use Custom RedirectResolver.
Sample Code:
public class CustomRedirectResolver implements RedirectResolver {
private Collection<String> redirectGrantTypes = Arrays.asList("implicit", "authorization_code");
private boolean matchSubdomains = false;
private boolean matchPorts = true;
public void setMatchSubdomains(boolean matchSubdomains) {
this.matchSubdomains = matchSubdomains;
}
public void setMatchPorts(boolean matchPorts) {
this.matchPorts = matchPorts;
}
public void setRedirectGrantTypes(Collection<String> redirectGrantTypes) {
this.redirectGrantTypes = new HashSet<String>(redirectGrantTypes);
}
public String resolveRedirect(String requestedRedirect, ClientDetails client) throws OAuth2Exception {
Set<String> authorizedGrantTypes = client.getAuthorizedGrantTypes();
if (authorizedGrantTypes.isEmpty()) {
throw new InvalidGrantException("A client must have at least one authorized grant type.");
}
if (!containsRedirectGrantType(authorizedGrantTypes)) {
throw new InvalidGrantException(
"A redirect_uri can only be used by implicit or authorization_code grant types.");
}
Set<String> registeredRedirectUris = client.getRegisteredRedirectUri();
if (registeredRedirectUris == null || registeredRedirectUris.isEmpty()) {
throw new InvalidRequestException("At least one redirect_uri must be registered with the client.");
}
return obtainMatchingRedirect(registeredRedirectUris, requestedRedirect);
}
private boolean containsRedirectGrantType(Set<String> grantTypes) {
for (String type : grantTypes) {
if (redirectGrantTypes.contains(type)) {
return true;
}
}
return false;
}
protected boolean redirectMatches(String requestedRedirect, String redirectUri) {
UriComponents requestedRedirectUri = UriComponentsBuilder.fromUriString(requestedRedirect).build();
UriComponents registeredRedirectUri = UriComponentsBuilder.fromUriString(redirectUri).build();
boolean schemeMatch = isEqual(registeredRedirectUri.getScheme(), requestedRedirectUri.getScheme());
boolean userInfoMatch = isEqual(registeredRedirectUri.getUserInfo(), requestedRedirectUri.getUserInfo());
boolean hostMatch = hostMatches(registeredRedirectUri.getHost(), requestedRedirectUri.getHost());
boolean portMatch = !matchPorts || registeredRedirectUri.getPort() == requestedRedirectUri.getPort();
// path match condition removed
return schemeMatch && userInfoMatch && hostMatch && portMatch;
}
private boolean matchQueryParams(MultiValueMap<String, String> registeredRedirectUriQueryParams,
MultiValueMap<String, String> requestedRedirectUriQueryParams) {
Iterator<String> iter = registeredRedirectUriQueryParams.keySet().iterator();
while (iter.hasNext()) {
String key = iter.next();
List<String> registeredRedirectUriQueryParamsValues = registeredRedirectUriQueryParams.get(key);
List<String> requestedRedirectUriQueryParamsValues = requestedRedirectUriQueryParams.get(key);
if (!registeredRedirectUriQueryParamsValues.equals(requestedRedirectUriQueryParamsValues)) {
return false;
}
}
return true;
}
private boolean isEqual(String str1, String str2) {
if (StringUtils.isEmpty(str1) && StringUtils.isEmpty(str2)) {
return true;
} else if (!StringUtils.isEmpty(str1)) {
return str1.equals(str2);
} else {
return false;
}
}
protected boolean hostMatches(String registered, String requested) {
if (matchSubdomains) {
return isEqual(registered, requested) || (requested != null && requested.endsWith("." + registered));
}
return isEqual(registered, requested);
}
private String obtainMatchingRedirect(Set<String> redirectUris, String requestedRedirect) {
Assert.notEmpty(redirectUris, "Redirect URIs cannot be empty");
if (redirectUris.size() == 1 && requestedRedirect == null) {
return redirectUris.iterator().next();
}
for (String redirectUri : redirectUris) {
if (requestedRedirect != null && redirectMatches(requestedRedirect, redirectUri)) {
// Initialize with the registered redirect-uri
UriComponentsBuilder redirectUriBuilder = UriComponentsBuilder.fromUriString(redirectUri);
UriComponents requestedRedirectUri = UriComponentsBuilder.fromUriString(requestedRedirect).build();
if (this.matchSubdomains) {
redirectUriBuilder.host(requestedRedirectUri.getHost());
}
if (!this.matchPorts) {
redirectUriBuilder.port(requestedRedirectUri.getPort());
}
redirectUriBuilder.replaceQuery(requestedRedirectUri.getQuery()); // retain additional params (if any)
redirectUriBuilder.fragment(null);
// RedirectUri changed to RequestRedirectUri
return requestedRedirectUri.toUriString();
}
}
throw new RedirectMismatchException("Invalid redirect: " + requestedRedirect
+ " does not match one of the registered values.");
}
}
You need to configuration class:
@Configuration
public class AuthorizartionConfigurer extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// some configurations code here
// ...
endpoints.redirectResolver(new CustomRedirectResolver());
}
}
Upvotes: 0
Reputation: 121
Faced the similar issue :
error="invalid_grant", error_description="Invalid redirect: http://localhost:8082/login does not match one of the registered values: [http://localhost:8082/]"
Updating below line from
client.redirectUris("http://localhost:9292")
to
client.redirectUris("http://localhost:9292/login");
resolved the issue for me.
Upvotes: 0
Reputation: 67
Finally can be found the answer for this. Issue is spring boot version. I have changed my spring boot version to 2.1.3.RELEASE. now it is working as expected. But still don't have idea why it is not working in latest spring boot version.
Upvotes: 1