Skip to content

Add SpEL Support for Reactive AuthorizeExchangeSpec #6512

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
ghost opened this issue Feb 7, 2019 · 4 comments
Closed

Add SpEL Support for Reactive AuthorizeExchangeSpec #6512

ghost opened this issue Feb 7, 2019 · 4 comments
Assignees
Labels
in: web An issue in web modules (web, webmvc) status: declined A suggestion or change that we don't feel we should currently apply

Comments

@ghost
Copy link

ghost commented Feb 7, 2019

Summary

Non-reactive spring security (4.x) provides access method where you can pass an Expression-Based-Access-Control to evaluate and/or authority/role permissions for allowing access.
In the reactive spring security the access method require an implementation of ReactiveAuthorizationManager or you can use a static method of it:

public static <T> AuthorityReactiveAuthorizationManager<T> hasAuthority(String authority)

In the spring-security 5.2.x development version you are supporting also the hasAnyAuthority and hasAnyRole methods, as spring-security 4.x, specified in #6306:

private final List<String> authorities;

	private AuthorityReactiveAuthorizationManager(String... authorities) {
		this.authorities = Arrays.asList(authorities);
	}

	@Override
	public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, T object) {
		return authentication
			.filter(a -> a.isAuthenticated())
			.flatMapIterable( a -> a.getAuthorities())
			.map(g -> g.getAuthority())
			.any(a -> this.authorities.contains(a))
			.map( hasAuthority -> new AuthorizationDecision(hasAuthority))
			.defaultIfEmpty(new AuthorizationDecision(false));
}

/**
	 * Creates an instance of {@link AuthorityReactiveAuthorizationManager} with the
	 * provided authorities.
	 *
	 * @author Robbie Martinus
	 * @param authorities the authorities to check for
	 * @param <T> the type of object being authorized
	 * @return the new instance
	 */
	public static <T> AuthorityReactiveAuthorizationManager<T> hasAnyAuthority(String... authorities) {
		Assert.notNull(authorities, "authorities cannot be null");
		for (String authority : authorities) {
			Assert.notNull(authority, "authority cannot be null");
		}

		return new AuthorityReactiveAuthorizationManager<>(authorities);
}

It would be great to have also the access method with String Expression-Based-Access-Control in the reactive API as well.

Actual Behavior

org.springframework.security.config.web.server.ServerHttpSecurity.AuthorizeExchangeSpec.pathMatchers(" ").access(new CustomReactiveAuthorizationManager<>("USER", "USER2"))

Where CustomReactiveAuthorizationManager is a my implementation of ReactiveAuthorizationManager interface similar to AuthorityReactiveAuthorizationManager.
But the last I can't use because the constructor is private.
So I can use only the static methods provided to evaluate OR boolean result from a list of roles/authorities:

org.springframework.security.config.web.server.ServerHttpSecurity.AuthorizeExchangeSpec.pathMatchers(" ").hasAnyAuthority(String... authorities);

or

org.springframework.security.config.web.server.ServerHttpSecurity.AuthorizeExchangeSpec.pathMatchers(" ").hasAnyRole(String... roles);

Expected Behavior

org.springframework.security.config.web.server.ServerHttpSecurity.AuthorizeExchangeSpec.pathMatchers(" ").access(String EBAC);

Configuration

Not applicable.

Version

5.2.x

Sample

Not applicable.

@ghost ghost changed the title Nice to have: access(String Expression-Based-Access-Control) in AuthorizeExchangeSpec as Spring 4.x Nice to have: access(String Expression-Based-Access-Control) in AuthorizeExchangeSpec as Spring-security 4.x Feb 7, 2019
@rwinch rwinch changed the title Nice to have: access(String Expression-Based-Access-Control) in AuthorizeExchangeSpec as Spring-security 4.x Add SpEL Support for Reactive AuthorizeExchangeSpec Feb 14, 2019
@rwinch
Copy link
Member

rwinch commented Feb 14, 2019

Thanks for the feedback.

I'm not sure it makes sense to allow SpEL here given that the baseline JDK version is Java 8 which allows for lambdas. Using a lambda is much simpler and will allow for better performance.

Can you explain why you want SpEL vs using a lambda?

PS: It should be noted that SpEL also doesn't work that well for reactive types. For example, expressing something like bean.go(exchange).flatMap(r -> ...) is not possible.

@rwinch rwinch added the status: waiting-for-feedback We need additional information before we can continue label Feb 14, 2019
@ghost
Copy link
Author

ghost commented Feb 14, 2019

Hi @rwinch,
thanks for the response.
My issue is that I want to evaluate a particolar espression for permissions. For example:

.access("ROLE_1  || ROLE_2")

Or

.access("!ROLE_1")

and so on.

This feature is enabled for not reactive, passing has_Authority("ROLE_1") && has_Authority("ROLE_2") as SpEl in access method.

Can I have a similar possibility for the Reactive AuthorizeExchangeSpec? Or you can support me with an example (with lambda expression or not) that performs my Desiderable Behavior?

@rwinch rwinch self-assigned this Feb 20, 2019
@rwinch rwinch added for: stackoverflow A question that's better suited to stackoverflow.com in: web An issue in web modules (web, webmvc) status: declined A suggestion or change that we don't feel we should currently apply Reactive and removed status: waiting-for-feedback We need additional information before we can continue for: stackoverflow A question that's better suited to stackoverflow.com labels Feb 20, 2019
@rwinch
Copy link
Member

rwinch commented Feb 20, 2019

.access("ROLE_1 || ROLE_2")

For the first example, gh-6306 (5.2.0.M1) added support to use

http
    .authorizeExchange()
        .anyExchange().hasAnyRole("1", "2");

Alternatively, you can do something like:

import static org.springframework.security.authorization.AuthorityReactiveAuthorizationManager.hasRole;


ReactiveAuthorizationManager<AuthorizationContext> hasRole1OrRole2 = (authN, ctx) -> 
	Flux.just(hasRole("1"), hasRole("2"))
		.flatMap(a -> a.check(authN, ctx))
		.filter(AuthorizationDecision::isGranted)
		.next();
http
	.authorizeExchange()
		.anyExchange().access(hasRole1OrRole2);

.access("!ROLE_1")

This is a sample using lambdas and building off the components that already exist.

import static org.springframework.security.authorization.AuthorityReactiveAuthorizationManager.hasRole;


ReactiveAuthorizationManager<AuthorizationContext> notRole1 = (authN, ctx) ->
	hasRole("ROLE_1").check(authN, ctx)
		.map(r -> !r.isGranted())
		.map(AuthorizationDecision::new);
http
	.authorizeExchange()
		.anyExchange().access(notRole1);

If you want you can also look at the actual implementation of AuthorityReactiveAuthorizationManager to see how you could place this in your own class. Looking at that code gives you a better idea of exactly how you would do something even more advanced too.

I'm closing this as we don't plan on providing SpEL support for the reasons mentioned above

@rwinch rwinch closed this as completed Feb 20, 2019
@ghost
Copy link
Author

ghost commented Mar 8, 2019

.access("ROLE_1 || ROLE_2")

For the first example, gh-6306 (5.2.0.M1) added support to use

http
    .authorizeExchange()
        .anyExchange().hasAnyRole("1", "2");

Alternatively, you can do something like:

import static org.springframework.security.authorization.AuthorityReactiveAuthorizationManager.hasRole;


ReactiveAuthorizationManager<AuthorizationContext> hasRole1OrRole2 = (authN, ctx) -> 
	Flux.just(hasRole("1"), hasRole("2"))
		.flatMap(a -> a.check(authN, ctx))
		.filter(AuthorizationDecision::isGranted)
		.next();
http
	.authorizeExchange()
		.anyExchange().access(hasRole1OrRole2);

.access("!ROLE_1")

This is a sample using lambdas and building off the components that already exist.

import static org.springframework.security.authorization.AuthorityReactiveAuthorizationManager.hasRole;


ReactiveAuthorizationManager<AuthorizationContext> notRole1 = (authN, ctx) ->
	hasRole("ROLE_1").check(authN, ctx)
		.map(r -> !r.isGranted())
		.map(AuthorizationDecision::new);
http
	.authorizeExchange()
		.anyExchange().access(notRole1);

If you want you can also look at the actual implementation of AuthorityReactiveAuthorizationManager to see how you could place this in your own class. Looking at that code gives you a better idea of exactly how you would do something even more advanced too.

I'm closing this as we don't plan on providing SpEL support for the reasons mentioned above

Thanks for the suggestion. I have implemented OR and AND expression in reactive.

For the AND:

ReactiveAuthorizationManager<AuthorizationContext> hasRole1AndRole2 = (authN, ctx) -> 
	Flux.just(hasRole("1"), hasRole("2"))
		.flatMap(a -> a.check(authN, ctx))
		.filter(a -> !a.isGranted()).hasElements()
		.map(b -> b.booleanValue() ? new AuthorizationDecision(false) : new AuthorizationDecision(true));

http.authorizeExchange().anyExchange().access(hasRole1AndRole2);

But for the OR your code is not correct because it will authorize also when there isn't at least an user granted. So I have corrected in this:

ReactiveAuthorizationManager<AuthorizationContext> hasRole1OrRole2 = (authN, ctx) -> 
	Flux.just(hasRole("1"), hasRole("2"))
		.flatMap(a -> a.check(authN, ctx))
		.filter(a -> a.isGranted()).defaultIfEmpty(new AuthorizationDecision(false)).next();

http.authorizeExchange().anyExchange().access(hasRole1OrRole2);

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) status: declined A suggestion or change that we don't feel we should currently apply
Projects
None yet
Development

No branches or pull requests

1 participant