Spring WebFlux Security PreAuthorize Best Practice - spring

as the title suggests, I have configured security in my Spring WebFlux application by using #EnableWebFluxSecurity and #EnableReactiveMethodSecurity.
I am using RouterFunction to handle the request routing. The following code is for the router:
#Component
public class UserServiceRequestRouter {
#Autowired
private UserServiceRequestHandler requestHandler;
#Bean
public RouterFunction<ServerResponse> route() {
//#formatter:off
return RouterFunctions
.route(GET("/user/{userId}"), requestHandler::getUserDetails);
//#formatter:on
}
}
And the request handler is:
#Component
public class UserServiceRequestHandler {
#Autowired
private UserService userService;
#PreAuthorize("#userServiceRequestAuthorizer.authorizeGetUserDetails(authentication, #request)")
public Mono<ServerResponse> getUserDetails(ServerRequest request) {
//#formatter:off
return userService.getUserDetails(request.pathVariable("userId"))
.convert()
.with(toMono())
.flatMap(
(UserDetails userDetails) -> ServerResponse.ok()
.contentType(APPLICATION_NDJSON)
.body(Mono.just(userDetails), UserDetails.class)
);
//#formatter:on
}
}
Note: The #Autowired UserService is to fetch data from the database in a reactive way.
Next, I have defined a #Component as:
#Component
#SuppressWarnings("unused")
#Qualifier("userServiceRequestAuthorizer")
public class UserServiceRequestAuthorizer {
public boolean authorizeGetUserDetails(JwtAuthenticationToken authentication, ServerRequest request) {
// #formatter:off
if (authentication == null) {
return false;
}
Collection<String> roles = authentication.getAuthorities()
.stream()
.map(Objects::toString)
.collect(Collectors.toSet());
if (roles.contains("Admin")) {
return true;
}
Jwt principal = (Jwt) authentication.getPrincipal();
String subject = principal.getSubject();
String userId = request.pathVariable("userId");
return Objects.equals(subject, userId);
// #formatter:on
}
}
It is notable here that I am using Spring OAuth2 Authorization Server, which is why the parameter authentication is of type JwtAuthenticationToken.
The application is working as per the expectation. But I am wondering if I am doing it the right way, meaning is this the best practice of doing method level Authorization in a reactive way?
The followings are my stack:
JDK 17
org.springframework.boot:3.0.0-M4
org.springframework.security:6.0.0-M6
Any advice you could give would be much appreciated.
Update
As mentioned by M. Deinum in the comment why shouldn't I use hasAuthority("Admin") or principal.subject == #userId, the reason is that the authorization code I provided is merely for demonstration purposes. It can get complicated and even if that complicacy might be managed by SpEL, I would rather not for the sake of simplicity.
Also the question is not about using inline SpEL, it's more about its reactiveness. I don't know if the SpEL mentioned in the #PreAuthorize is reactive! If it is reactive by nature then I can assume any expression mentioned in the #PreAuthorize would be evaluated reactively.

As far as I know, SpEL expressions evaluation is synchronous.
Unless your UserServiceRequestAuthorizer does more than checking access-token claims against static strings or request params and payload, I don't know why this would be an issue: it should be very, very fast.
Of course, if you want to check it against data from DB or a web-service this would be an other story, but I'd say that your design is broken and that this data access should be made once when issuing access-token (and set private claims) rather than once per security evaluation (which can happen several times in a single request).
Side notes
It is notable here that I am using Spring OAuth2 Authorization Server, which is why the parameter authentication is of type JwtAuthenticationToken.
I do not agree with that. It would be the same with any authorization-server (Keycloak, Auth0, Microsoft IdentityServer, ...). You have a JwtAuthenticationToken because you configured a resource-server with a JWT decoder and kept the default JwtAuthenticationConverter. You could configure any AbstractAuthenticationToken instead, as I do in this tutorial.
It can get complicated and even if that complicacy might be managed by SpEL, I would rather not for the sake of simplicity.
I join #M.Deinum point of view, writing your security rules in a service, like you do, makes it far less readable than inlining expressions: hard to guess what is checked while reading the expression => one has to quit current source file, open security service one and read the code.
If you refer to the tutorial already linked above, it is possible to enhance security DSL and write stuff like: #PreAuthorize("is(#username) or isNice() or onBehalfOf(#username).can('greet')") to stick to your sample, this would give #PreAuthorize("is(#userId) or isAdmin()).

Related

Spring boot auth server client/ resource server: OAuth 2.1 Authorization Code Grant programatic simulation? Password grant no longer exists

Spring Authorization Server OAuth 2.1.
How can i programatically simulate the authorization_code grant?
Since all grants except for authorization_code and client_credentials have been dropped this has become quite a headache.
The scenario calls for a #Scheduled job to login as a specific user where the client credentials are encoded properties within the server performing the login.
The user roles are important when executing downstream resources and is considered a regular user of the registered Client.
Using the Password grant was perfect for this scenario in OAuth 2.0.
Before i start hacking our Spring Auth server and implement a Password grant for registered resources or maybe overloading the client_credentials for user_credentialed payloads.
Quite a pain if you ask me, so please enlighten me? Are there any patterns for implementing this that i have not yet discovered?
While I'm curious what specific use case you have that needs to perform tasks as a particular user (as opposed to a single confidential client), it should still be possible with customization.
maybe overloading the client_credentials for user_credentialed payloads
This approach makes the most sense to me as a way to adapt supported flows in OAuth 2.1 to emulate a deprecated flow like the resource owner password grant. You can use a variation of this github gist, extending it with your user's authorities if needed. One possible solution might look like the following:
#Component
public final class DaoRegisteredClientRepository implements RegisteredClientRepository {
private final RegisteredClient registeredClient;
private final UserDetailsService userDetailsService;
public DaoRegisteredClientRepository(RegisteredClient registeredClient, UserDetailsService userDetailsService) {
this.registeredClient = registeredClient;
this.userDetailsService = userDetailsService;
}
#Override
public void save(RegisteredClient registeredClient) {
throw new UnsupportedOperationException();
}
#Override
public RegisteredClient findById(String id) {
return this.registeredClient.getId().equals(id) ? this.registeredClient : null;
}
#Override
public RegisteredClient findByClientId(String clientId) {
UserDetails userDetails;
try {
userDetails = this.userDetailsService.loadUserByUsername(clientId);
} catch (UsernameNotFoundException ignored) {
return null;
}
return RegisteredClient.from(this.registeredClient)
.clientId(userDetails.getUsername())
.clientSecret(userDetails.getPassword())
.clientSettings(ClientSettings.builder().setting("user.authorities", userDetails.getAuthorities()).build())
.build();
}
}
This uses a single client registration, but makes use of a UserDetailsService to resolve a subject representing your user's username and a secret which is actually the user's password. You would then need to provide an #Bean of type OAuth2TokenCustomizer<JwtEncodingContext> to access the user.authorities setting and add those authorities to the resulting access token (JWT) using whatever claim your resource server expects them in.
Alternatively, you could just override the scopes parameter of the returned RegisteredClient if desired.
I had the similar problem and ended up creating a password grant emulation servlet filter. Please refer to my example:
https://github.com/andreygrigoriev/spring_authorization_server_password_grant

Generic HTTP security path matcher, reference matched path in security expression

Right now, with Spring Security's HttpSecurity, we're able to restrict wildcard paths to specific roles/authorities:
.mvcMatchers(POST, "/users").hasAuthority("create:users")
.mvcMatchers(PUT, "/users/{id}").hasAuthority("update:users")
is there an easy way to do:
.mvcMatchers(POST, "/{whateverGoesHere}").hasAuthority("create:${whateverGoesHere}")
.mvcMatchers(PUT, "/{whateverGoesHere}/{id}").hasAuthority("update:${whateverGoesHere}")
?
It doesn't have to be a solution using the configure(HttpSecurity http) API specifically, I'm just looking for an easy way to generify authorization rules for multiple REST entities at once.
This is obviously a more advanced scenario, to say the least. However, improvements in Spring Security 5.5 have introduced the new AuthorizationManager interface and the http.authorizeHttpRequests() method for configuring authorization rules that utilize it. See The AuthorizationManager in the reference docs for more info. It is extremely powerful! I believe this is probably the best option for your use case.
There are numerous implementations available in Spring Security that can be used to build composite and/or delegating implementations. Here's an example that uses your convention:
public final class ResourceAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
private final String action;
public ResourceAuthorizationManager(String action) {
this.action = action;
}
#Override
public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
AuthorizationManager<RequestAuthorizationContext> delegate =
AuthorityAuthorizationManager.hasAuthority(createAuthority(context));
return delegate.check(authentication, context);
}
private String createAuthority(RequestAuthorizationContext context) {
String resource = context.getVariables().get("resource");
return String.format("%s:%s", this.action, resource);
}
}
The action can be create, read, update, delete or anything you like as part of your authority string. This implementation relies on URI variables provided through the RequestAuthorizationContext. As it happens, there's an existing implementation (RequestMatcherDelegatingAuthorizationManager) that handles that scenario. It is actually the one handling .mvcMatchers() authorization rules in the Spring Security DSL. Here's an example that uses it to delegate to the convention-based AuthorizationManager above:
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
.mvcMatchers(HttpMethod.POST, "/{resource}").access(new ResourceAuthorizationManager("create"))
.mvcMatchers(HttpMethod.PUT, "/{resource}/{id}").access(new ResourceAuthorizationManager("update"))
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
I think that you should not have hidden behavior in your code. If a developer wants to add a new endpoint and wants to have it require some authority, it should be done intentionally. Otherwise, it could become a debugging nightmare if the dev intends to add an open endpoint and wonders why it is secured.
But you could add a default behavior for all endpoints that you did not specify. That behavior could be to deny access. That way, every developer has to add some kind of access granting entry. That would guarantee that it is not forgotten, but it is still intentionally done.
...
.mvcMatchers(POST, "/users").hasAuthority("create:users")
.mvcMatchers(PUT, "/users/{id}").hasAuthority("update:users")
.anyRequest().denyAll()

jhipster fine grain authorization, remove ROLE based authorization

I have been searching on how to remove the ROLE based authorization and replace it with fine grain authorization. What I meant by fine grain is
All method has a #PreAuthorize("isAuthorize('GETCLIENT')") or directly #IsAuthorize("GETCLIENT").
If the user has GETCLIENT in Authorization List, then the method can be executed. Otherwise, the system give error message or just deny access.
Any clue or information regarding how to do that is very much appreciated.
Thank you.
Like I said in the comment one quick and easy way to do this is to add your new custom authorities in the AuthoritiesConstants.java class. You have examples of how to do this here and here.
public final class AuthoritiesConstants {
public static final String ADMIN = "ROLE_ADMIN";
public static final String USER = "ROLE_USER";
public static final String ANONYMOUS = "ROLE_ANONYMOUS";
public static final String GETCLIENT = "ROLE_GETCLIENT"; // custom
private AuthoritiesConstants() {
}
}
Remember to insert the new role into your jhi_authority database table. You can assign new authorities to a user via the user management interface admin/user-management, it's possible the user needs to relog for the change to take effect.
Then in the method you want to secure just add:
#GetMapping("/clients/{id}")
#PreAuthorize("hasRole(\"" + AuthoritiesConstants.GETCLIENT + "\")")
public ResponseEntity<ClientDTO> getClient(#PathVariable Long id) {
log.debug("REST request to get Client : {}", id);
Optional<ClientDTO> clientDTO = clientService.findOne(id);
return ResponseUtil.wrapOrNotFound(clientDTO);
}
I said #Secured before but in reality you should use #PreAuthorize since it is more powerful and lets you work with Spring Expression Language (SpEL).
The go to resource to understand how JHipster security works is here, but in reality it just follows the standard Spring Security guidelines (as with many other things) so the official documentation about Spring Security should apply too.
Also, if you find this is too simple or that it is breaking the default conventions I found this guide about custom privileges to be particularly great. It's a bit more work, but should work better since you separate authorities (roles) from privileges.

Using JdbcTokenStore for JWT

for my REST API I am using JWT's for OAuth2 authorization. Currently I am extending JwtTokenStore to store the refresh tokens in memory so that I am able to revoke them.
// TODO: This is a temporary in memory solution that needs to be replaced with a concrete persistent implementation.
public class MyJwtTokenStore extends JwtTokenStore {
private List<OAuth2RefreshToken> refreshTokens;
public MyJwtTokenStore(JwtAccessTokenConverter jwtTokenEnhancer) {
super(jwtTokenEnhancer);
refreshTokens = new ArrayList<>();
}
#Override
public OAuth2RefreshToken readRefreshToken(String tokenValue) {
OAuth2RefreshToken refreshToken = super.readRefreshToken(tokenValue);
if (!refreshTokens.contains(refreshToken)) {
throw new InvalidGrantException("Invalid refresh token: " + tokenValue);
}
return refreshToken;
}
#Override
public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
refreshTokens.add(refreshToken);
}
#Override
public void removeRefreshToken(OAuth2RefreshToken token) {
refreshTokens.remove(token);
}
}
I would like start storing these refresh tokens in a database rather than in memory. Spring provides us with JdbcTokenStore, but if I extend that class then I am unable to set a JwtAccessTokenConverter in the constructor. I know that I could just implement my own method of saving/retrieving the JWTs but I would like to take advantage of the out of the box support for the schema at https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql which the JdbcTokenStore provides.
create table oauth_refresh_token (
token_id VARCHAR(256),
token LONGVARBINARY,
authentication LONGVARBINARY
);
Does Spring support storing JWT in a data source? I need something like a "JwtJdbcTokenStore". What is a good way to go about doing this but still use the predefined queries and operations from JdbcTokenStore?
No, Spring doesn't supports this. Refer to this thread
https://github.com/spring-projects/spring-security-oauth/issues/687
Persisting JWT tokens is irrelevant since JWT tokens are self contained, everything you need to know is already available in that token.
Having said that, if you have requirement for persisting them, then you will have to write custom logic for the same.

How to evaluate a SpEL Security expression in custom java code?

i have come to need to invent a new type of annotations, one of fields of which would be a Spring Expression Language (aka SpEL) expression string.
After a bit googling and examining existing classes, i've figured out that the way of evaluating expression might be like this one (correct me if i am wrong in any way):
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("isAnonymous()"); // well, this is only an example
SecurityExpressionRoot context = ... obtaining the instance of subclass of SecurityExpressionRoot ...
System.out.println(exp.getValue(context)); // just an example
But here is the problem: the most suiting for my case MethodSecurityExpressionRoot is package-local. There is even a task about making it public in Spring Security JIRA which didn't got any attention from developers for a year.
And even if it wasn't package-local, i still have a weak understanding of where to obtain objects for methods setTrustResolver, setRoleHierarchy and setPermissionEvaluator of SecurityExpressionRoot class, which seems to be needed for it's proper functioning.
So, my question is: how do you properly get the correct SecurityExpressionRoot-subclass instance and how to populate it with required objects?
I am solving same problem. I have a list of menu items. Each menu item contains a security expression string (SpEl). I tried to use #PostFilter("filterObject.securityExpression") but I couldn't figure out how to evaluate a SpEl string inside a SpEl string.
So I ended up with custom evaluator bean. Heavily inspired by org.thymeleaf.extras.springsecurity4.auth.AuthUtils
The evaluator uses same SecurityExpressionHandler as web security filters. This means its necessary to provide request and response for an evaluation context. But this shouldn't be complicated since Spring injects those values into controller methods.
Evaluator:
#Component
public class WebSecurityExpressionEvaluator {
private static final FilterChain EMPTY_CHAIN = (request, response) -> {
throw new UnsupportedOperationException();
};
private final List<SecurityExpressionHandler> securityExpressionHandlers;
public WebSecurityExpressionEvaluator(List<SecurityExpressionHandler> securityExpressionHandlers) {
this.securityExpressionHandlers = securityExpressionHandlers;
}
public boolean evaluate(String securityExpression, HttpServletRequest request, HttpServletResponse response) {
SecurityExpressionHandler handler = getFilterSecurityHandler();
Expression expression = handler.getExpressionParser().parseExpression(securityExpression);
EvaluationContext evaluationContext = createEvaluationContext(handler, request, response);
return ExpressionUtils.evaluateAsBoolean(expression, evaluationContext);
}
#SuppressWarnings("unchecked")
private EvaluationContext createEvaluationContext(SecurityExpressionHandler handler, HttpServletRequest request, HttpServletResponse response) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
FilterInvocation filterInvocation = new FilterInvocation(request, response, EMPTY_CHAIN);
return handler.createEvaluationContext(authentication, filterInvocation);
}
private SecurityExpressionHandler getFilterSecurityHandler() {
return securityExpressionHandlers.stream()
.filter(handler -> FilterInvocation.class.equals(GenericTypeResolver.resolveTypeArgument(handler.getClass(), SecurityExpressionHandler.class)))
.findAny()
.orElseThrow(() -> new IllegalStateException("No filter invocation security expression handler has been found! Handlers: " + securityExpressionHandlers.size()));
}
}
Usage as a controller method:
#ModelAttribute("adminMenuItems")
public List<AdminMenuItem> getMenuItems(HttpServletRequest request, HttpServletResponse response) {
List<AdminMenuItem> menuItems = ...
return menuItems.stream().filter(item -> evaluator.evaluate(item.getSecurityExpression(), request, response)).collect(toList());
}
I managed to achieve exactly this without any new annotations. The first thing you need to do is wrap your menu item in a sec:authorize tag, where the sec namespace is from spring security taglibs. We use:
<sec:authorize access="hasRole('${menuItem.permission}')"></sec:authorzie>
where ${menuItem.permission} is the permission field of the current menuItem object (we're looping through menuItems that we've retrieved from the server). The SpEl hasRole() is implemented by spring in the org.springframework.security.access.expression.SecurityExpressionOperations class.
That won't give you security though, it'll just make the gui nice. The server also needs to be secured with something like this:
#PreAuthorize("hasRole('...')")
The #PreAuthorize annotation is also from spring security, and it stops a client from executing a method on your server unless the user has the given role. To make this work we had to implement the org.springframework.security.cas.userdetails.AbstractCasAssertionUserDetailsService. A similar class exists for most identity management servers. We also had to implement org.jasig.services.persondir.support.ldap.LdapPersonAttributeDao, but we're using ldap too. YMMV.

Resources