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()
Related
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()).
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.
I want to expose data from a database as Restful APIs in a Spring(SpringBoot) application. Spring Data Rest appears to be an exact fit for purpose for this activity.
This database is read-only for my application needs. The default provides all the HTTP methods. Is there a configuration that I can use to restrict (in fact prevent) the other methods from being exposed?
From the Spring docs on Hiding repository CRUD methods:
16.2.3. Hiding repository CRUD methods
If you don’t want to expose a save or delete method on your
CrudRepository, you can use the #RestResource(exported = false)
setting by overriding the method you want to turn off and placing the
annotation on the overriden version. For example, to prevent HTTP
users from invoking the delete methods of CrudRepository, override all
of them and add the annotation to the overriden methods.
#RepositoryRestResource(path = "people", rel = "people")
interface PersonRepository extends CrudRepository<Person, Long> {
#Override
#RestResource(exported = false)
void delete(Long id);
#Override
#RestResource(exported = false)
void delete(Person entity);
}
It is important that you override both delete methods as the exporter
currently uses a somewhat naive algorithm for determing which CRUD
method to use in the interest of faster runtime performance. It’s not
currently possible to turn off the version of delete which takes an ID
but leave exported the version that takes an entity instance. For the
time being, you can either export the delete methods or not. If you
want turn them off, then just keep in mind you have to annotate both
versions with exported = false.
As of early 2018, there is now the ability to only expose repository methods explicitly declared for exposure (DATAREST-1176)
See RepositoryRestConfiguration
A Export false at Type level does not allow overriding with export true at Method level ticket (DATAREST-1034) was opened, but closed as a duplicate of DATAREST-1176. Oliver Gierke stated:
I'll resolve this as fixed against the version of DATAREST-1176 for
now but feel free to reopen in case there's anything else you need.
They are not exact duplicates and the functionality described in 1034 would have been more user friendly, but there are at least some options now.
By default, Spring boot exposes all methods to REST. You can set that to false.
config.setExposeRepositoryMethodsByDefault(false);
For more information, you can refer org.springframework.data.rest.core.config.RepositoryRestConfiguration.
Sample code snippet to do this:
#Configuration
public class ApplicationRepositoryConfig implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
..........
config.setExposeRepositoryMethodsByDefault(false);
}
}
Since Spring Data REST 3.1, we can configure exposure per HTTP method. I used the following snippet to disable exposure of PUT, PATCH, POST and DELETE methods for items and collections:
#Component
public class SpringDataRestCustomization implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
ExposureConfiguration exposureConfiguration = config.getExposureConfiguration();
exposureConfiguration.withItemExposure((metadata, httpMethods) -> httpMethods.disable(HttpMethod.PUT)
.disable(HttpMethod.PATCH).disable(HttpMethod.POST).disable(HttpMethod.DELETE))
.withCollectionExposure((metadata, httpMethods) -> httpMethods.disable(HttpMethod.PUT)
.disable(HttpMethod.PATCH).disable(HttpMethod.POST).disable(HttpMethod.DELETE));
}
}
I’m unable to use hasRole method in #PreAuthorize annotation. Also request.isUserInRole(“ADMIN”) gives false. What am I missing?
Although .hasAuthority(“ADMIN”) works fine.
I am assigning authorities to the users from a database.
You have to name your authority with prefix ROLE_ to use isUserInRole, see Spring Security Reference:
The HttpServletRequest.isUserInRole(String) will determine if SecurityContextHolder.getContext().getAuthentication().getAuthorities() contains a GrantedAuthority with the role passed into isUserInRole(String). Typically users should not pass in the "ROLE_" prefix into this method since it is added automatically. For example, if you want to determine if the current user has the authority "ROLE_ADMIN", you could use the following:
boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");
Same for hasRole (also hasAnyRole), see Spring Security Reference:
Returns true if the current principal has the specified role. By default if the supplied role does not start with 'ROLE_' it will be added. This can be customized by modifying the defaultRolePrefix on DefaultWebSecurityExpressionHandler.
See also Spring Security Reference:
46.3.3 What does "ROLE_" mean and why do I need it on my role names?
Spring Security has a voter-based architecture which means that an access decision is made by a series of AccessDecisionVoters. The voters act on the "configuration attributes" which are specified for a secured resource (such as a method invocation). With this approach, not all attributes may be relevant to all voters and a voter needs to know when it should ignore an attribute (abstain) and when it should vote to grant or deny access based on the attribute value. The most common voter is the RoleVoter which by default votes whenever it finds an attribute with the "ROLE_" prefix. It makes a simple comparison of the attribute (such as "ROLE_USER") with the names of the authorities which the current user has been assigned. If it finds a match (they have an authority called "ROLE_USER"), it votes to grant access, otherwise it votes to deny access.
I had to improvise a little, maybe there is other ways simpler then mine, but at the time I worked on this I had no other choice but to improvise a bit, after a thorough research came up with this solution.
Spring Security has an interface called AccessDecisionManager, you will need to implement it.
#Component
public class RolesAccessDecisionManager implements AccessDecisionManager {
private final static String AUTHENTICATED = "authenticated";
private final static String PERMIT_ALL = "permitAll";
#Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
collection.forEach(configAttribute -> {
if (!this.supports(configAttribute))
throw new AccessDeniedException("ACCESS DENIED");
});
}
#Override
public boolean supports(ConfigAttribute configAttribute) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
String rolesAsString = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining(","));
if (configAttribute.toString().contains(rolesAsString))
return true;
else
return (configAttribute.toString().contains(PERMIT_ALL) || configAttribute.toString().contains(AUTHENTICATED));
}
return true;
}
#Override
public boolean supports(Class<?> aClass) {
return true;
}
}
Now to support this custom access-decision-manager with your security config do this in the security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
// other configs
.accessDecisionManager(this.accessDecisionManager)
accessDecisionManager is the autowired bean of the AccessDecisionManager implementation you've created.
You can use either hasRole() or hasAuthority(). The difference is that, you have to user ROLE_ for hasAusthority() method.
So for the ROLE_ADMIN,
#PreAuthorize("hasRole('ADMIN')") == #PreAuthorize("hasAuthority('ROLE_ADMIN')")
I'm trying to design an application that should expose two global API path:
/ user must be authenticated
/public no authentication
Moreover /public API will offer light version of some / API by displaying less informations that is not authorize if no authentication is provided.
Even if Controller does not contains core function, some of them provide data validation or other check. Thus if I want to create a /public version of a current API I have 4 solutions:
Duplicate code
#Autowired / controller and use method call
forward request (I can't redirect because security filter will be applied)
Create Controller that manage both / and /public API
Is there any good practice or pattern for my scenario?
IMHO, Best way to solve this problem is by using 4th solution.
1st solution: First rule of computer science is you do not duplicate your code.
2nd solution: calling controller from another controller is a serious design flaw.
3rd solution: could have been a solution but ruled out by you.
4th solution: IMHO best one in your case
class MyController{
#RequestMapping("/getData")
public ResponseObject getData(#RequestBody SomeDTO dto){
Validator.validate(dto);
return myService.getData(dto);
}
#RequestMapping("/public/getData")
public ResponseObject getPublicData(#RequestBody SomeDTO dto){
Validator.validate(dto);
return myService.getPublicData(dto);
}
}
Filter data in your service layer.
It's possible to be achieved with Spring Security.
First, you will need to enable this URL to be called without security, like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
/** some security code **/
http
.authorizeRequests().antMatchers("/resources/**","/public/**").permitAll().anyRequest().authenticated().and()
/** other stuffs **/
}
#RestController
#RequestMapping(value="/public")
public class PublicRestController {
#Autowired private DataRepository data;
#RequestMapping(value = "/data/",method = RequestMethod.GET)
public Model getModelData(){
/** Do what you need here **/
}
}
So, all you have to do is build a REST Controller to match your URL and you are done.
And as you suggested, use #Autowired to expose only the code that you need. So you can put all your login on service/component beans and serve them as needed.