Skip to content
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

Add PathPatternRequestMatcher #16499

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open

Conversation

jzheaux
Copy link
Contributor

@jzheaux jzheaux commented Jan 29, 2025

This PR adds PathPatternRequestMatcher, a request matcher that doesn't require HandlerMappingIntrospector.

Also, it adds a servlet-aware builder for specifying the servlet path (using this request matcher) in a way that looks similar to Kotlin and XML:

import static org.springframework.security.web.servlet.util.matcher.RequestMatchers.servlet;

@Bean
SecurityFilterChain appEndpoints(HttpSecurity http) {
	http
		.authorizeHttpRequests((authorize) -> authorize
			.requestMatchers(servlet("/spring-mvc").uris("/admin/**")).hasAuthority("admin")
			.requestMatchers(servlet("/spring-mvc").uris("/my/controller/**")).hasAuthority("controller")
			.anyRequest().authenticated()
		);

	return http.build();
}

Future tickets like #16509 are taking a look at a first-class way to represent the servlet inside the authorization DSL. This builder is still helpful since there are many other places in the Java DSL where request matchers are provided that may need the servlet path specified.

This static factory simplifes the creation of RequestMatchers
that specify the servlet path and other request elements

Closes spring-projectsgh-16430
@jzheaux jzheaux marked this pull request as ready for review February 7, 2025 18:08
@jzheaux jzheaux added in: web An issue in web modules (web, webmvc) type: enhancement A general enhancement labels Feb 7, 2025
Copy link
Member

@rwinch rwinch left a comment

Choose a reason for hiding this comment

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

Thanks for all of your work on this @jzheaux! I've provided feedback below:

* @return the object that is chained after creating the {@link RequestMatcher}
* @since 6.5
*/
public C requestMatchers(org.springframework.security.web.util.matcher.RequestMatchers.Builder builder) {
Copy link
Member

@rwinch rwinch Feb 7, 2025

Choose a reason for hiding this comment

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

I'd prefer that interfaces be passed in to the DSL rather than implementations. Implementations are quite limiting (which is why we tend to use interfaces).

Perhaps for now, users must leverage Builder.matcher() and pass this into the existing DSL since we plan on refining the DLS going forward.

Copy link
Member

Choose a reason for hiding this comment

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

I would like the DSL to avoid needing static imports in order to function. It makes discovery more difficult. Even if it is only a little more difficult, each small difficulty adds up.


== Use Absolute Authorization URIs

The Java DSL now requires that all URIs be absolute (less any context root).
Copy link
Member

Choose a reason for hiding this comment

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

I would rephrase this a bit to state that Spring Security 7 will require since I don't think that this is true now.


The Java DSL now requires that all URIs be absolute (less any context root).

This means any endpoints that are not part of the default servlet, xref:servlet/authorization/authorize-http-requests.adoc#match-by-mvc[the servlet path needs to be specified].
Copy link
Member

Choose a reason for hiding this comment

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

I'm having trouble understanding this section.

http {
authorizeHttpRequests {
authorize(mvc.pattern("/my/controller/**"), hasAuthority("controller"))
authorize("/spring-mvc", "/admin/**", hasAuthority("admin"))
Copy link
Member

Choose a reason for hiding this comment

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

Does this need updated? I think that authorize was in a different revision, but does not exist anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The documentation was mistaken. AFAIK, this is the correct way to specify the servlet path in the Kotlin DSL. See this test as an example.


[TIP]
=====
There are several other components that create request matchers for you like `PathRequest#toStaticResources#atCommonLocations`
Copy link
Member

Choose a reason for hiding this comment

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

What is PathRequest#toStaticResources#atCommonLocations?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is a Spring Boot feature. I'll add a link for additional context.

* {@code servletPath}.
*
* <p>
* The {@code servletPath} must correlate to a configured servlet in your application.
Copy link
Member

Choose a reason for hiding this comment

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

This sounds like servletPath might be the equivalent of <servlet-mapping>/<url-pattern>, but I suspect it is corresponding to HttpServletRequest.servletPath. Perhaps, this could be clarified?

* @return a {@link Builder} that treats URIs as relative to the context path, if any
* @since 6.5
*/
public static Builder request() {
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps this could be emptyServletPath() or similar?

* @since 6.5
*/
public static Builder request() {
return new Builder();
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if this is accurate since it matches any servletPath vs an empty servlet path?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Perhaps. Though this method is not really about declaring a request in absence of something else. I wouldn't, for example, want AntPathRequestMatcher#antPattern(String pattern) to be AntPathRequestMatcher#emptyMethodPattern(String pattern) because no method is specified.

I'm open to thinking about other names, but I don't see why we'd want to define this in terms of what is not specified.

Furthermore, I think the 95% case is that defining a servlet path isn't necessary, so I'd prefer not to define the common case in terms of a concept that commonly is abstracted away from the developer.

Could you elaborate on why it's important to you?

(as a side note, even though I think that specifying a servlet is not in the 95%, I like that having it as an entry point allows for the returned builder to be reused, and I also like that it makes it clear that such a matcher should only be described once)

*/
public final class PathPatternRequestMatcher implements RequestMatcher {

private final PathPattern pattern;
Copy link
Member

Choose a reason for hiding this comment

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

While PathPattern does not match on the servlet path, is that meaningful for Spring Security applications? I am concerned that this makes it too easy to do the wrong thing?

Copy link
Contributor Author

@jzheaux jzheaux Feb 7, 2025

Choose a reason for hiding this comment

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

I'm open to other possibilities. One that I considered was to enhance PathPatternRequestMatcher to be something more of an general-purpose request matcher. In this way, its pattern of construction would indicate a place for the servlet path. Perhaps the builder in RequestMatchers can move to PathPatternRequestMatcher.

What are your thoughts on this?

@@ -102,3 +102,12 @@ Xml::
</b:bean>
----
======

== Use Absolute Authorization URIs
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 add some examples?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web An issue in web modules (web, webmvc) type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants