I am working on a sample Spring Boot app using OAuth2. The issue is that the client hosted on localhost:8080
calls out to https://localhost:8443/oauth/authorize
to authorize itself (implicit grant type) but since /oauth/authorize
requires that the user be authenticated they are redirected to the login page at https://localhost:8443/login
.
This is all expected, but when the user lands on the login page all of the query strings including redirect_uri are missing. The user logs in and is redirected to https://localhost:8443
rather than the specified redirect_uri of http://localhost:8080
.
Is there some way to have the user redirected back to the client after logging in w/ the server's login form? Am I missing something in my config? I can post more as needed.
The authorize request looks like: https://localhost:8443/oauth/authorize?response_type=token&state=6c2bb162-0f26-4caa-abbe-b65f7e5c6a2e&redirect_uri=http%3A%2F%2Flocalhost%3A8080&client_id=admin
SecurityConfig:
@Configuration
public static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final Logger log = LoggerFactory.getLogger(WebSecurityConfig.class);
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**");
}
@SuppressWarnings("deprecation")
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/**")
.and()
.addFilterAfter(new CsrfCookieGeneratorFilter(), CsrfFilter.class)
.exceptionHandling()
.accessDeniedPage("/login?authorization_error=true")
.and()
.authorizeRequests()
.antMatchers("/resources/**", "/csrf").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.usernameParameter("j_username")
.passwordParameter("j_password")
.defaultSuccessUrl("/", false)
.failureUrl("/login?authentication_error=true")
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID", "CSRF-TOKEN")
.permitAll()
.and()
.headers()
.frameOptions()
.disable();
}
OAuthConfig:
@Configuration
@EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Inject
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
@Primary
@Bean
public ResourceServerTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(tokenStore());
return tokenServices;
}
@Bean
public ApprovalStore approvalStore() throws Exception {
TokenApprovalStore approvalStore = new TokenApprovalStore();
approvalStore.setTokenStore(tokenStore());
return approvalStore;
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("read-only")
.secret("readme")
.resourceIds(RESOURCE_ID)
.authorizedGrantTypes("implicit", "password", "refresh_token")
.authorities(Constant.USER)
.scopes("read")
.autoApprove(true)
.redirectUris("https://localhost:8443")
.and()
.withClient("admin")
.secret("admin")
.resourceIds(RESOURCE_ID)
.authorizedGrantTypes("implicit", "password", "refresh_token")
.authorities(Constant.USER, Constant.ADMIN)
.scopes("read", "write")
.autoApprove(true)
.redirectUris("https://localhost:8443", "http://localhost:8080")
.and()
.withClient("super-admin")
.secret("super")
.resourceIds(RESOURCE_ID)
.authorizedGrantTypes("implicit", "password", "refresh_token")
.authorities(Constant.USER, Constant.ADMIN)
.scopes("read", "write", "delete")
.redirectUris("https://localhost:8443");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer configurer) throws Exception {
configurer
.tokenStore(tokenStore())
.authenticationManager(authenticationManager);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security)
throws Exception {
security.realm("hubble/client");
}
}
@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/api/**")
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/api/**").permitAll()
.antMatchers(HttpMethod.GET, "/api/**").access("#oauth2.hasScope('read')")
.antMatchers(HttpMethod.POST, "/api/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.PATCH, "/api/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.PUT, "/api/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.DELETE, "/api/**").access("#oauth2.hasScope('delete')")
.antMatchers("/api/**").access("hasRole('" + Constant.USER + "')")
.and()
.anonymous().authorities(Constant.ANONYMOUS)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
protected static class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
OAuth2MethodSecurityExpressionHandler methodHandler = new OAuth2MethodSecurityExpressionHandler();
return methodHandler;
}
}
The problem occurs with form authentication only and it is not related to OAuth. The org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint has a buildRedirectUrlToLoginPage method that creates the login URL and it forgets the query strings.
Currently we solved it with a workaround.
An example LoginController from step 2. could look like this:
@Controller
@RequestMapping(value = {"/login"})
public class LoginController {
@RequestMapping(method = RequestMethod.GET)
public String getPage(HttpServletRequest request, HttpServletResponse response, Principal principal)
throws IOException {
if (principal != null) { //depends on your security config, maybe you want to check the security context instead if you allow anonym access
String redirect_uri = request.getParameter("redirect_uri");
//here you must get all the other attributes thats needed for the authorize url
if (redirect_uri == null) {
redirect_uri = "https://your.default.app.url";
}
return "redirect:https://localhost:8443/oauth/authorize?response_type=token&state=6c2bb162-0f26-4caa-abbe-b65f7e5c6a2e&client_id=admin&redirect_uri=" + URLEncoder.encode(redirect_uri, "UTF-8");
}
return "login";
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With