Unable to resolve CORS errors - spring-boot

Assumptions
We are developing a web application with the following library.
When a request is sent from the front end to the back end, a CORS error occurs.
Frontend: Vue.js (Version: 3)
Backend: SpringBoot (version: 2.7.6)
Authentication: SpringSecurity
What we want to achieve
We would like to resolve the following CORS errors that occur when a request is sent from the front-end side to the back-end side.
Access to XMLHttpRequest at 'http://localhost:8085/users/profile/1' from origin 'http://localhost:8888' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Source code
Send request to Spring in Vue.js (Edit.vue)
onClickDelete() {
const path = 'users/profile/'
axios.delete(
process.env.VUE_APP_ROOT_API + path + this.$store.state.user_id,{
headers: {
"Authorization": "Bearer " + this.$store.state.jwt_token,
},
})
.then(response => {
})
.catch(error => {
console.log(error)
})
},
Receiving process in Spring (UsersController.java)
#RestController
#RequestMapping("/users/profile")
public class UsersController {
#DeleteMapping("/{user_id}")
#ResponseStatus(code = HttpStatus.NO_CONTENT, value = HttpStatus.NO_CONTENT)
public void profiledelete(#PathVariable("user_id") Long id) throws Exception {
}
}
SpringSecurity configuration file (WebSecurityConfig.java)
#Profile("production")
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserRepository userRepository;
private final JsonRequestAuthenticationProvider jsonRequestAuthenticationProvider;
#Value("${security.secret-key:secret}")
private String secretKey = "secret";
public WebSecurityConfig(JsonRequestAuthenticationProvider jsonRequestAuthenticationProvider// ,
) {
this.jsonRequestAuthenticationProvider = jsonRequestAuthenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
JsonRequestAuthenticationFilter jsonAuthFilter =
new JsonRequestAuthenticationFilter(userRepository);
jsonAuthFilter.setAuthenticationManager(authenticationManagerBean());
http.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues());
http.addFilter(jsonAuthFilter);
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler())
.and()
.csrf().
disable()
.addFilterBefore(tokenFilter(), UsernamePasswordAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
;
}
What we tried
#CrossOrigin to the process (UsersController.java) that receives the process in Spring
What we did
Receive process in Spring (UsersController.java)
#RestController
#RequestMapping("/users/profile")
#CrossOrigin
public class UsersController {
#DeleteMapping("/{user_id}")
#ResponseStatus(code = HttpStatus.NO_CONTENT, value = HttpStatus.NO_CONTENT)
public void profiledelete(#PathVariable("user_id") Long id) throws Exception {
}
}
Result
The CORS error is still displayed.
Additional Information
Before SpringSecurity was installed, I think that granting #CrossOrigin on the Spring side solved the CORS error.
When the GET method is used in other requests, it succeeds without any CORS errors with the Spring side.

This seems to be an issue with your setup with spring security.
There are two primary ways to fix this error; however, I would also recommend upgrading to a newer version of spring security, because WebSecurityConfigurerAdapter has now been deprecated.
Primary method
CORS on Spring security (2.x)
#Profile("production")
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserRepository userRepository;
private final JsonRequestAuthenticationProvider jsonRequestAuthenticationProvider;
#Value("${security.secret-key:secret}")
private String secretKey = "secret";
public WebSecurityConfig(JsonRequestAuthenticationProvider jsonRequestAuthenticationProvider// ,
) {
this.jsonRequestAuthenticationProvider = jsonRequestAuthenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
JsonRequestAuthenticationFilter jsonAuthFilter =
new JsonRequestAuthenticationFilter(userRepository);
jsonAuthFilter.setAuthenticationManager(authenticationManagerBean());
http.cors().configurationSource(request -> {
var cors = new CorsConfiguration();
cors.setAllowedOrigins(List.of("*"));
cors.setAllowedMethods(List.of("GET","POST", "PUT", "DELETE", "OPTIONS"));
cors.setAllowedHeaders(List.of("*"));
return cors;
});
http.addFilter(jsonAuthFilter);
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler())
.and()
.csrf().
disable()
.addFilterBefore(tokenFilter(), UsernamePasswordAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
;
}
CORS disable
#Profile("production")
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserRepository userRepository;
private final JsonRequestAuthenticationProvider jsonRequestAuthenticationProvider;
#Value("${security.secret-key:secret}")
private String secretKey = "secret";
public WebSecurityConfig(JsonRequestAuthenticationProvider jsonRequestAuthenticationProvider// ,
) {
this.jsonRequestAuthenticationProvider = jsonRequestAuthenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
JsonRequestAuthenticationFilter jsonAuthFilter =
new JsonRequestAuthenticationFilter(userRepository);
jsonAuthFilter.setAuthenticationManager(authenticationManagerBean());
http.cors().disable();
http.addFilter(jsonAuthFilter);
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler())
.and()
.csrf().
disable()
.addFilterBefore(tokenFilter(), UsernamePasswordAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
;
}
CORS on Spring security (3.x)
#Configuration
public class WebConfiguration implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("*");
}
}
Always go for the second method.

Related

Spring erro Cors

I have a problem with the spring Cors.
I get this error on chome:
Access to XMLHttpRequest at 'http://localhost:8080/api/informationWS' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
My file WebSecurityConfigurerAdapter
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private LoginService loginService;
#Bean
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(loginService)
.passwordEncoder(this.passwordEncoderAutentication());
}
#Bean
public PasswordEncoder passwordEncoderAutentication() {
String idForEncode = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
PasswordEncoder passwordEncoder = new DelegatingPasswordEncoder(idForEncode, encoders);
return passwordEncoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.cors();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
My file ResourceServerConfigurerAdapter
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/informationWS").permitAll()
.antMatchers(HttpMethod.POST, "/api/work").authenticated()
.anyRequest().denyAll();
}
}
I tried to work with Cors in the two ways below, but neither of them worked, generating the same error
My file cors
#Configuration
#EnableWebMvc
public class Cors implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:4200");
}
}
My file Cors2
#Configuration
public class Cors {
#Bean
public FilterRegistrationBean<CorsFilter> corsFilterFilterRegistrationBean(){
List<String> host = Arrays.asList("http://localhost:4200");
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(host);
corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
corsConfiguration.setAllowedMethods(Arrays.asList("*"));
corsConfiguration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", corsConfiguration);
CorsFilter corsFilter = new CorsFilter(source);
FilterRegistrationBean<CorsFilter> filter = new FilterRegistrationBean<>(corsFilter);
filter.setOrder(Ordered.HIGHEST_PRECEDENCE);
return filter;
}
}
What you could try/check:
check if the application code is executed - maybe server stops execution for some reason, and so your spring code cannot add a header.
maybe there is preflight request and server does not allow it (so again server stopped execution and your backend code could not send the header)
maybe you yourself stop script somewhere before the header is added, like System.exit(0);
maybe there is redirect to code which does not add header, for example some exception
try running the request from Postman - you should not get the error and maybe you will see something surprising.
does this .antMatchers(HttpMethod.GET, "/api/informationWS") really match the request? Maybe there is a way to add wildcard just for testing and see if it works? Are you sending GET request?
More details, technologies different but concept same: https://dariuscoder.com/2021/09/16/how-to-debug-cors/

Spring Security pre authentication filter gets called every time

I have a Spring Boot app where I have custom pre authentication filter. I want to ignore security for health URL but I am not able to do it. Below is my configuration.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Order(1000)
public class UserSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> userDetailsService;
#Autowired
private IUserIdentityService iUserIdentityService;
#Value("${spring.profiles.active}")
private String profileType;
#Autowired
#Qualifier("publicEndpoints")
private Map<String, String> publicEndpoints;
#Autowired
private GenericDataService genericDataService;
#Bean(name = "preAuthProvider")
PreAuthenticatedAuthenticationProvider preauthAuthProvider() {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(userDetailsService);
return provider;
}
#Bean
AppPreAuthenticatedProcessingFilter appPreAuthenticatedProcessingFilter() throws Exception {
appPreAuthenticatedProcessingFilter filter = new appPreAuthenticatedProcessingFilter(iUserIdentityService, genericDataService);
filter.setAuthenticationManager(super.authenticationManagerBean());
filter.setContinueFilterChainOnUnsuccessfulAuthentication(false);
filter.setCheckForPrincipalChanges(true);
return filter;
}
/**
* Uses JEE pre-authentication filter, that assumes that the user has been
* pre-authenticated into the container.
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/health/e2e").permitAll()
.and()
.addFilter(appPreAuthenticatedProcessingFilter())
.authorizeRequests()
.anyRequest().authenticated()
.and()
.authenticationProvider(preauthAuthProvider())
.csrf()
.csrfTokenRepository(this.csrfTokenRepository())
.and()
.httpBasic().disable();
// Disabling the CSRF implementation, if "csrf.disabled" property set to "true"
// in System Properties.
if (!StringUtils.isEmpty(profileType) && profileType.equals("local")) {
http.csrf().disable();
}
}
/**
* Method to ignore web security for urls
*/
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("*/api-docs", "/configuration/ui", "/swagger-resources/**", "/configuration/**", "/swagger-ui.html", "/webjars/**", "/health/e2e", "*/health/e2e", "**/health/e2e");
}
/**
* Method to to return CsrfTokenRepository
*/
private CsrfTokenRepository csrfTokenRepository() {
CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
tokenRepository.setCookiePath("/");
return tokenRepository;
}
}
Custom authentication filter looks like
#Slf4j
public class AppPreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter {
private IUserIdentityService iUserIdentityService;
private GenericDataService genericDataService;
public AppPreAuthenticatedProcessingFilter(IUserIdentityService iUserIdentityService, GenericDataService genericDataService) {
this.iUserIdentityService = iUserIdentityService;
this.genericDataService = genericDataService;
}
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
return iUserIdentityService.getUserName();
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return AppConst.DEFAULT_CREDENTIAL;
}
}
I am not sure why /health/e2e is secured?
P.S. I tried removing #Bean from pre auth filter but in that case, filter never gets called for any request.
The problem is two fold
Your security setup contains an error
The filter is added to the regular filter bean as well.
With your current security setup the AppPreAuthenticatedProcessingFilter is added only to the /health/e2d URL. Your attempt to fix something has actually broken things instead.
Your configuration should be something along the lines of
http.authorizeRequests().anyRequest().authenticated()
.and().httpBasic()
.and().authenticationProvider(preauthAuthProvider())
.csrf().csrfTokenRepository(this.csrfTokenRepository())
.and().addFilterBefore(appPreAuthenticatedProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
// in System Properties.
if (!StringUtils.isEmpty(profileType) && profileType.equals("local")) {
http.csrf().disable();
}
Spring Boot will by default register an javax.servlet.Filter in the normal filter chain, to disable this you need to add a FilterRegistrationBean to disable this.
#Bean
public FilterRegistrationBean<AppPreAuthenticatedProcessingFilter> preAuthenticationFilterRegistrationBean(AppPreAuthenticatedProcessingFilter filter) {
FilterRegistrationBean<AppPreAuthenticatedProcessingFilter> frb = new FilterRegistrationBean<>(filter);
frb.setEnabled(false);
return frb;
}

Spring Boot Cross origin blocking requests

I have this Spring Boot application with a controller for Login endpoint. I've set these web configurations:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, jsr250Enabled = true, prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, PathConstants.USER_AUTH +"/**", PathConstants.HELIOS+"/dashboard/**").permitAll()
.antMatchers(HttpMethod.GET, "/"+PathConstants.PROCESS_DEFINITION+"/**").permitAll()
.antMatchers(HttpMethod.POST, "/"+PathConstants.PROCESS_DEFINITION+"/**").permitAll()
.antMatchers(HttpMethod.GET, "/"+PathConstants.PROCESS_INSTANCE+"/**").permitAll()
//.antMatchers(HttpMethod.POST, PathConstants.LOGIN_ACTION).permitAll()
//.anyRequest().authenticated()
.anyRequest().permitAll()
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// custom jwt filter.
http.addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class);
}
and web mvc configurations:
#Configuration
#EnableWebMvc
public class WebMvcConfiguration implements WebMvcConfigurer {
private final long MAX_AGE = 3600;
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("*")
.allowedMethods("HEAD", "OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE")
.allowedHeaders("Content-Type", "X-Requested-With", "accept", "Origin", "Access-Control-Request-Method",
"Access-Control-Request-Headers")
.exposedHeaders("Access-Control-Allow-Origin", "Access-Control-Allow-Credentials")
.maxAge(MAX_AGE);
}
I've also tried to add .allowCredentials(true), but when I perform the login action it gives me cross origin error and my request is blocked.
This is my controller class:
#RestController
#RequestMapping
#CrossOrigin
public class AuthenticationControllerImpl implements AuthenticationController {
#PostMapping(PathConstants.LOGIN_ACTION)
#Override
public SysdataUser autenticate(#Valid #RequestBody LoginRequest request) {
Authentication auth = authManager
.authenticate(new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));
SecurityContextHolder.getContext().setAuthentication(auth);
String token = jwtProvider.generateToken(auth);
SysdataUser user = sysdataUserService.getUserProfile(request.getUsername());
user.setToken(token);
return user;
}
What's missing in configurations?
I don't know why your config doesn't override default behavior. this is what I do and it always works for me, change #CrossOrigin in your AuthenticationControllerImpl class to #CrossOrigin(origins = "*"). let me know if it worked for you.

"addCorsMapping" blocking Swagger UI

I'm working on a Spring application and I have some troubles with Swagger and Spring Security.
I had to add a specific configuration to allow almost every access (CORS) and it worked well so far, but somehow it is blocking Swagger....
This is my SwaggerConfiguration.java
#Configuration
#EnableSwagger2
#SwaggerDefinition(
info = #Info(
description = "Web Service",
version = "V0.0.1",
title = "Web Service",
contact = #Contact(
name = "Me",
email = "dev#me.com",
url = "https://www.me.com/"
)
),
consumes = {"application/json"},
produces = {"application/json"},
schemes = {SwaggerDefinition.Scheme.HTTP, SwaggerDefinition.Scheme.HTTPS}
)
public class SwaggerConfiguration {
/** List of Swagger endpoints (used by {#code WebSecurityConfig}) */
static final String[] SWAGGER_ENDPOINTS = {
"/v2/api-docs",
"/swagger-resources",
"/swagger-resources/**",
"/configuration/ui",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**"
};
#Bean
public Docket swaggerSpringMvcPlugin() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("admin-api")
.select()
.paths(paths()) // and by paths
.build();
}
private Predicate<String> paths() {
return or(
regex("/admin.*"),
regex("/issuer.*"),
regex("/validator.*"),
regex("/data.*"));
}
}
And this is my WebSecurityConfig.java :
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtTokenDecoder jwtTokenDecoder;
#Bean
// Mandatory to be able to have % in URL
// FIXME Set it only for dev environment
public HttpFirewall allowUrlEncodedPercentHttpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowUrlEncodedPercent(true);
firewall.setAllowUrlEncodedSlash(true);
return firewall;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.httpBasic().disable()
.formLogin().disable()
.logout().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Install the JWT authentication filter
http.addFilterBefore(new JwtAuthenticationFilter(jwtTokenDecoder), BasicAuthenticationFilter.class);
// Authorize only authenticated requests
http.authorizeRequests()
.anyRequest().authenticated();
http.cors();
}
#Override
public void configure(WebSecurity web) {
// Allow access to /admin/login without authentication
web.ignoring().mvcMatchers("/admin/login", "/admin/validate", "/campaigns", "/data/**", "/issuer/**", "/validator/**");
web.ignoring().antMatchers(SwaggerConfiguration.SWAGGER_ENDPOINTS);
web.httpFirewall(allowUrlEncodedPercentHttpFirewall());
}
}
Finally, I have a WebConfig.java used to set CORS authorizations.
Here it is :
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE");
}
}
Very simple. It should authorize almost any access.
When I remove it, Swagger is available from URL localhost:8080/swagger-ui.html (but not my webservices...)
When I put it back, it is blocked, with a 403 error (forbidden)
Any idea of what I am missing ?
So the solution was to add some configuration in WebConfig
I have added this implementation
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry
.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}

Spring Security OAuth2 - Need clarification and help to configure Implicit flow

I am struggling to configure Spring Security OAuth2 to support implicit flow (I had no problems with password or authorization code).
These are the different endpoints:
Authorization server
http://localhost:8082/oauth/authorize
http://localhost:8082/oauth/token
...
Resource server
http://localhost:8081/users (protected resource)
Client
http://localhost:8080/api/users invokes http://localhost:8081/users initiating the OAuth2 dance.
What I see is:
http://localhost:8080/api/users gets redirected to the authorization server with this in the URL: http://localhost:8082/oauth/authorize?client_id=themostuntrustedclientid&response_type=token&redirect_uri=http://localhost:8080/api/accessTokenExtractor
I am prompted with the OAuth approval screen, where I grant all the scopes. Then the browser is redirected to the redirect_uri: http://localhost:8080/api/accessTokenExtractor with a fragment containing the access_token: http://localhost:8080/api/accessTokenExtractor#access_token=3e614eca-4abe-49a3-bbba-1b8eea05c147&token_type=bearer&expires_in=55&scope=read%20write
QUESTIONS:
a. HOW CAN I RESUME AUTOMATICALLY THE EXECUTION OF THE ORIGINAL REQUEST?
The spec defines this behaviour with the access_token as a fragment in the URL: since the fragments aren't sent directly to the servers, we have to use a web page script to extract it and send it to the client (my spring-mvc application). This implies setting a redirect_uri pointing at the script, instead of to the original request:
http://localhost:8080/api/accessTokenExtractor#access_token=3e614eca-4abe-49a3-bbba-1b8eea05c147&token_type=bearer&expires_in=55&scope=read%20write
The accessTokenExtractor web page sends the token to the client. The problem is I don't have the original call (http://localhost:8080/api/users) anymore...
b. Below you can see the client invocation:
restTemplate.getOAuth2ClientContext().getAccessTokenRequest()
.setAll(['client_id': 'themostuntrustedclientid',
'response_type': 'token',
'redirect_uri': 'http://localhost:8080/api/accessTokenExtractor'])
HttpHeaders headers = new HttpHeaders()
ResponseEntity<List<String>> response = restTemplate.exchange('http://localhost:8081/users', HttpMethod.GET, null, new ParameterizedTypeReference<List<String>>(){}, [])
response.getBody()
if I don't set manually the parameters client_id, response_type and redirect_uri (necessary for the UserRedirectRequiredException) the authorization server complains, it needs them. ARE WE EXPECTED TO SET THEM MANUALLY?
The strange thing is that they are available in ImplicitAccessorProvider.obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest request):
ImplicitResourceDetails resource = (ImplicitResourceDetails) details;
try {
...
resource contains all of them, however they are not copied to request.
If we compare with AuthorizationCodeAccessTokenProvider here the private method getRedirectForAuthorization() does it automatically...WHY THE DIFFERENCE?
CONFIGURATION:
Authorization Server config:
#EnableAuthorizationServer
#SpringBootApplication
class Oauth2AuthorizationServerApplication {
static void main(String[] args) {
SpringApplication.run Oauth2AuthorizationServerApplication, args
}
}
#Configuration
class OAuth2Config extends AuthorizationServerConfigurerAdapter{
#Autowired
private AuthenticationManager authenticationManager
#Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager([])
manager.createUser(new User("jose","mypassword", [new SimpleGrantedAuthority("ROLE_USER")]))
manager.createUser(new User("themostuntrustedclientid","themostuntrustedclientsecret", [new SimpleGrantedAuthority("ROLE_USER")]))
return manager
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//curl trustedclient:trustedclientsecret#localhost:8082/oauth/token -d grant_type=password -d username=user -d password=cec31d99-e5ee-4f1d-b9a3-8d16d0c6eeb5 -d scope=read
.withClient("themostuntrustedclientid")
.secret("themostuntrustedclientsecret")
.authorizedGrantTypes("implicit")
.authorities("ROLE_USER")
.scopes("read", "write")
.accessTokenValiditySeconds(60)
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(this.authenticationManager);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//security.checkTokenAccess('hasRole("ROLE_RESOURCE_PROVIDER")')
security.checkTokenAccess('isAuthenticated()')
}
}
resource server config and protected endpoint:
#EnableResourceServer
#SpringBootApplication
class Oauth2ResourceServerApplication {
static void main(String[] args) {
SpringApplication.run Oauth2ResourceServerApplication, args
}
}
#Configuration
class OAuth2Config extends ResourceServerConfigurerAdapter{
#Value('${security.oauth2.resource.token-info-uri}')
private String checkTokenEndpointUrl
#Override
public void configure(HttpSecurity http) throws Exception {
http
// Since we want the protected resources to be accessible in the UI as well we need
// session creation to be allowed (it's disabled by default in 2.0.6)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.requestMatchers().antMatchers("/users/**")
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/users").access("#oauth2.hasScope('read')")
.antMatchers(HttpMethod.PUT, "/users/**").access("#oauth2.hasScope('write')")
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
RemoteTokenServices remoteTokenServices = new RemoteTokenServices()
remoteTokenServices.setCheckTokenEndpointUrl(checkTokenEndpointUrl)
remoteTokenServices.setClientId("usersResourceProvider")
remoteTokenServices.setClientSecret("usersResourceProviderSecret")
resources.tokenServices(remoteTokenServices)
}
}
#RestController
class UsersRestController {
private Set<String> users = ["jose", "ana"]
#GetMapping("/users")
def getUser(){
return users
}
#PutMapping("/users/{user}")
void postUser(#PathVariable String user){
users.add(user)
}
}
And this is the client config:
#EnableOAuth2Client
#SpringBootApplication
class SpringBootOauth2ClientApplication {
static void main(String[] args) {
SpringApplication.run SpringBootOauth2ClientApplication, args
}
}
#Configuration
class SecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.eraseCredentials(false)
.inMemoryAuthentication().withUser("jose").password("mypassword").roles('USER')
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.anyRequest().hasRole('USER')
.and()
.formLogin()
}
}
#Configuration
class OAuth2Config {
#Value('${oauth.resource:http://localhost:8082}')
private String baseUrl
#Value('${oauth.authorize:http://localhost:8082/oauth/authorize}')
private String authorizeUrl
#Value('${oauth.token:http://localhost:8082/oauth/token}')
private String tokenUrl
#Autowired
private OAuth2ClientContext oauth2Context
#Bean
OAuth2ProtectedResourceDetails resource() {
ImplicitResourceDetails resource = new ImplicitResourceDetails()
resource.setAuthenticationScheme(AuthenticationScheme.header)
resource.setAccessTokenUri(authorizeUrl)
resource.setUserAuthorizationUri(authorizeUrl);
resource.setClientId("themostuntrustedclientid")
resource.setClientSecret("themostuntrustedclientsecret")
resource.setScope(['read', 'write'])
resource
}
#Bean
OAuth2RestTemplate restTemplate() {
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resource(), oauth2Context)
//restTemplate.setAuthenticator(new ApiConnectOAuth2RequestAuthenticator())
restTemplate
}
}
My client has the following controller that invokes a protected aouth2 endpoint from the resource server:
#RestController
class ClientRestController {
#Autowired
private OAuth2RestTemplate restTemplate
def exceptionHandler(InsufficientScopeException ex){
ex
}
#GetMapping("/home")
def getHome(HttpSession session){
session.getId()
}
#GetMapping("/users")
def getUsers(HttpSession session){
println 'Session id: '+ session.getId()
//TODO Move to after authentication
Authentication auth = SecurityContextHolder.getContext().getAuthentication()
restTemplate.getOAuth2ClientContext().getAccessTokenRequest().setAll(['client_id': 'themostuntrustedclientid', 'response_type': 'token', 'redirect_uri': 'http://localhost:8080/api/users'])
HttpHeaders headers = new HttpHeaders()
ResponseEntity<List<String>> response = restTemplate.exchange('http://localhost:8081/users', HttpMethod.GET, null, new ParameterizedTypeReference<List<String>>(){}, [])
response.getBody()
}
}

Resources