Skip to content

DATAREST-573 Add support for Spring Web MVC CORS configuration mechanisms. #233

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from

Conversation

mp911de
Copy link
Member

@mp911de mp911de commented Oct 10, 2016

We now support CORS configuration mechanisms introduced in Spring 4.2. CORS can be configured on multiple levels: Repository interface, Repository REST controller and global level. Spring Data REST CORS configuration is isolated so Spring Web MVC'S CORS configuration does not apply to Spring Data REST resources.

Multiple configuration sources are merged so different aspects of CORS can be configured in separate locations.

@CrossOrigin
interface PersonRepository extends CrudRepository<Person, Long> {}

@RepositoryRestController
@RequestMapping("/person")
public class PersonController {

    @CrossOrigin(maxAge = 3600)
    @RequestMapping(method = RequestMethod.GET, "/xml/{id}", produces = MediaType.APPLICATION_XML_VALUE)
    public Person retrieve(@PathVariable Long id) {
        // ...
    }
}

@Component
public class SpringDataRestCustomization extends RepositoryRestConfigurerAdapter {

  @Override
  public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {

    config.addCorsMapping("/person/**")
        .allowedOrigins("http://domain2.com")
        .allowedMethods("PUT", "DELETE")
        .allowedHeaders("header1", "header2", "header3")
        .exposedHeaders("header1", "header2")
        .allowCredentials(false).maxAge(3600);
  }
}

…oduced in Spring 4.2.

Prepare issue branch.
Copy link
Member

@odrotbohm odrotbohm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great one. Added a few minor comments. I wouldn't mind having @sdeleuze skim over the changes, especially the parts that use the new API for whether he can detect some usage patterns that look unnatural.

String repositoryLookupPath = new BaseUri(configuration.getBaseUri()).getRepositoryLookupPath(lookupPath);

// Repository root resource
if (StringUtils.hasText(repositoryLookupPath) && repositories != null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we invert the conditions to be able to use early returns and thus avoid the nesting?

*/
CorsConfigurationAccessor(ResourceMappings mappings, Repositories repositories,
StringValueResolver embeddedValueResolver) {
this.mappings = mappings;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-null assertions missing.

CorsConfiguration findCorsConfiguration(String lookupPath) {

ResourceMetadata resource = getResourceMetadata(getRepositoryBasePath(lookupPath));
if (resource != null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ternary if?

}

String allowCredentials = resolveCorsAnnotationValue(annotation.allowCredentials());
if ("true".equalsIgnoreCase(allowCredentials)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Insert blank line before.

* The {@link HandlerMapping} to delegate requests to Spring Data REST controllers. Sets up a
* {@link DelegatingHandlerMapping} to make sure manually implemented {@link BasePathAwareController} instances that
* register custom handlers for certain media types don't cause the {@link RepositoryRestHandlerMapping} to be
* omitted.
*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid whitespace change.

@RunWith(MockitoJUnitRunner.class)
public class CorsConfigurationAccessorUnitTests {

private CorsConfigurationAccessor accessor;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fields don't need to be private in tests.

}
}

@RepositoryRestController
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move those classes under the test methods so that we stay consistent to other tests. Configuration can stay here.

/**
* Creates a new {@link RepositoryCorsRegistry}.
*/
public RepositoryCorsRegistry() {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need the default constructor, do we?

* @author Mark Paluch
* @since 2.6
*/
public class RepositoryCorsRegistry extends CorsRegistry {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason we need this dedicated class? It seems we could just get away with using CorsRegistry, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CorsRegistry's getCorsConfigurations method is protected so we need to make it usable for us. Didn't want to rely on reflection.

*
* @param mappings must not be {@literal null}.
* @param config must not be {@literal null}.
* @param repositories must not be {@literal null}.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like repositories can in fact be null.


== Repository interface CORS configuration

You can add a `@CrossOrigin` annotation to your repository interfaces to enable CORS for the whole repository. By default `@CrossOrigin` allows origins and `HEAD` and `GET` HTTP methods:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned here, the current behaviour with Spring Data REST + @CrossOrigin is to allow GET and HEAD HTTP methods (default for the low level CorsConfiguration class).

In Spring MVC, the default behaviour for @CrossOrigin annotations without any method specified is to use methods of the related @RequestMapping annotation. I am wondering if by default in Spring Data REST, we could not just allow HTTP methods of the related REST endpoint, in order to avoid surprising the user when for example his repository methods exposed with a POST HTTP method will not work by default. Any thoughts?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GET and HEAD are in sync with Spring MVC (initially it's the same behavior) and to not allow any methods that change data by default.

We check the allowed methods on collection/item level inside the particular controllers – we could derive the allowed methods from there but the CORS configuration would be required to mirror the logic that is already present inside the controllers.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After discussing with @sdeleuze, @CrossOrigin on a repository will enable all HTTP methods by default. This default can be overridden. We can evaluate in a later step how we can provide the enabled repository methods to expose only the allowed CORS methods on a resource.

@sdeleuze
Copy link

Nice work @mp911de ! I have mainly a question about default HTTP methods with @CrossOrigin in Spring Data REST context.

…oduced in Spring 4.2.

We now support CORS configuration mechanisms introduced in Spring 4.2. CORS can be configured on multiple levels: Repository interface, Repository REST controller and global level. Spring Data REST CORS configuration is isolated so Spring Web MVC'S CORS configuration does not apply to Spring Data REST resources.

 Multiple configuration sources are merged so different aspects of CORS can be configured in separate locations.

@crossorigin
interface PersonRepository extends CrudRepository<Person, Long> {}

@RepositoryRestController
@RequestMapping("/person")
public class PersonController {

	@crossorigin(maxAge = 3600)
	@RequestMapping(method = RequestMethod.GET, "/xml/{id}", produces = MediaType.APPLICATION_XML_VALUE)
	public Person retrieve(@PathVariable Long id) {
		// ...
	}
}

@component
public class SpringDataRestCustomization extends RepositoryRestConfigurerAdapter {

  @OverRide
  public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {

    config.addCorsMapping("/person/**")
        .allowedOrigins("http://domain2.com")
        .allowedMethods("PUT", "DELETE")
        .allowedHeaders("header1", "header2", "header3")
        .exposedHeaders("header1", "header2")
        .allowCredentials(false).maxAge(3600);
  }
}
@mp911de mp911de force-pushed the issue/DATAREST-573 branch from b223a2e to 273dac7 Compare October 17, 2016 07:34
odrotbohm added a commit that referenced this pull request Oct 28, 2016
Removed RepositoryRestConfiguration.addCorsMapping(…) as we currently don't have any other shortcut methods for configuration like this.

Tweaked the setup of (now Repository)CorsConfigurationAccessor to be created earlier so that we avoid recreation for every lookup. Introduced a NoOpStringValueResolver to be used by default so that we don't need to deal with the case of the resolver being null at the end of the call chain. Replaced constructor of RepositoryCorsConfigurationAccessor with corresponding Lombok annotation.

Updated reference documentation accordingly.

Original pull request: #233.
@odrotbohm
Copy link
Member

That's been merged a while ago.

@odrotbohm odrotbohm closed this Dec 4, 2016
@odrotbohm odrotbohm self-assigned this Dec 4, 2016
@neodevelop
Copy link

So, which version has actually this new feature and how can I use it?

@odrotbohm
Copy link
Member

The referenced ticket has all the information.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants