Is this a bug in Spring Security's RunAsManagerImpl, or are my expectations wrong?
My understanding of the (limited) documentation, is that with a RunAsManagerImpl defined in my config if I call doFoo() in the following:
#Secured({"ROLE_FOO", "RUN_AS_BAR"})
public void doFoo() {
doBar();
}
#Secured("ROLE_BAR")
public void doBar() {
// ...
}
then, provided the current Authentication has the role "FOO", doBar() will execute successfully.
But it doesn't, Spring throws an AccessDeniedException. However, changing doBar()'s annotation to:
#Secured("ROLE_RUN_AS_BAR")
works successfully.
Upon examination of the source code, the reason is fairly clear - if it encounters an attribute that starts with "RUN_AS_", it creates:
GrantedAuthority extraAuthority = new SimpleGrantedAuthority(getRolePrefix() + attribute.getAttribute());
where, by default:
private String rolePrefix = "ROLE_";
So the authority that is applied is "ROLE_RUN_AS_BAR", which doesn't seem right at all. Is this a bug that I should raise, or have I misunderstood the intended use of this functionality?
It's the expected behavior, as described in the documentation:
The created GrantedAuthorityImpls will be prefixed with a special
prefix indicating that it is a role (default prefix value is ROLE_),
and then the remainder of the RUN_AS_ keyword. For example, RUN_AS_FOO
will result in the creation of a granted authority of ROLE_RUN_AS_FOO.
The purpose of such basic implementation is not to impersonate a user, but to acquire a "technical role". For example, some part of your code should require a technical role of "database manager". No user has this role but I can be acquired programmatically.
Of course, you can bypass this code by just updating the Authentication in SecurityContextHolder, but having a central implementation point to "upgrade" an Authentication object can be more secure when used by a jvm securitymanager.
However, the RunAsManager is a really simple interface, in order to be easily reimplemented: If the default behavior doesn't match what you need, you only have one method to reimplement.
Related
I have a Problem that i cannot solve after researching alot.
I have a Keyloak with Clients(Application) and Roles that secure the Application.
Inside my Application i check with .hasRole()-Method if the Role of the User or other Application matches with the defined Role. Everything works excepted.
The Problem is i want to combine Roles and check them in the Application.
To access my Application the user should have the role 'read' AND 'write'.
In Spring the hasRole()-Method checks only one Role at a Time.
The hasAnyRole()-Method checks if one of the Roles matches.
Is there any Method like say hasAllRoles? Which checks if all the Roles match?
One request is to solve that Problem only with Configuration but the implemented Method in the Application is hasRole() so i except that there is no possible way of solving this with only configuration on Keycloak or Application.properties inside the Application
The easiest solution is probably to find a way to explain to the person who decided "to solve that Problem only with Configuration" that using #PreAuthorize meta-data is a better solution :
controllers code is not "polluted" with access-control: annotation are not inside methods body and are evaluated only if enabled in spring security (which makes no difference with access-control in conf)
access-control rules are close to access-point definition, which makes it much more readable (easy to grasp what is applied to a specific route and HTTP verb)
SpEL is much more powerful than config request matchers. You can even define your own DSL to write expressions like #PreAuthorize("is(#username) or isNice() or onBehalfOf(#username).can('greet')") (this taken from the most advanced of my tutorials).
access-control can decorate any method of any #Component (including #Service and #Repository) and not only to public methods of #Controller decorated with #RequestMapping or one of its declination.
With SpEL, you can do what you need (read and write) plus define rules based on the accessed resource itself (who created it, what it is linked to,...)
To give a try:
add #EnableMethodSecurity to your security conf class
only define with request matchers what is accessible to anonymous and what requires authentication
add #PreAuthorize("hasRole('read') and hasRole('write')") just above your controller method
In addition to what #ch4mp explained, I'd like to offer a few other principles to keep in mind.
First, your question. You can do hasAllRoles in two ways. The first is with AuthorizationManagers.allOf and the second is with SpEL:
http.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/endpoint").access(
allOf(hasAuthority("read"), hasAuthority("write"))
)
)
and
#PreAuthorize("hasAuthority('read') and hasAuthority('write')")
Read on for some additional recommendations relative to your comment:
The Problem is i want to combine Roles and check them in the Application.
A Bean
A nice way to extract authorization logic into a component is to reference an authorization bean in your expression.
For example, you can do:
#Component("authz")
public final class MyAuthorizationDecider {
public boolean check(MethodSecurityExpressionOperations operations) {
// ... place authorization logic here
}
}
And then you can do:
#PreAuthorize("#authz.check(#root)")
(If I'm not mistaken, you can still use #ch4mp's library with this approach, simply calling the library's DSL from a Java method instead of within a SpEL expression.)
Hierarchies
It's also the case that some permissions imply others. It may be the case for you that message:write implies message:read. In such a case, your expressions can be simplified by codifying this relationship in a RoleHierarchy instance.
At Login Time
At times, it can be helpful to map authorities at login time. For example, the role of USER might translate into message:read and ADMIN into message:read and message:write. It might go the other way as well. If the client granted message:read and message:write, perhaps this translates into a single permission of message:redact.
If you perform this translation at login time, it can allow for fewer computations at request time and a single location to reason about authorities being granted.
For example, instead of doing
#PreAuthorize("hasAuthority('message:read') and hasAuthority('message:write')")
or
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/message/redact").access(
allOf(hasAuthority("message:read"), hasAuthority("message:write"))
)
)
you'd do:
#PreAuthorize("hasAuthority('message:redact')")
or
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/message/redact").hasAuthority("message:redact")
)
Since you are using Keycloak, in this case, you'd consider a custom JwtGrantedAuthoritiesConverter if you are a Resource Server or GrantedAuthoritiesMapper if you are a Client to map the granted authorities from the Jwt to authorities that map to what you are doing in your app.
Please note: someone seems to be serially DVing my questions without explanation. This question is on topic, is not a duplicate, shows research and provides an SSCCE. If you wish to DV or CV it, that's fine, but please provide a comment as to why so I can have a chance to address your concerns...
Spring Boot 2.3.x and Spring Security here.
I have some pretty complicated authorization logic, and so I believe I need to write my own AccessDecisionManager impl and wire it into my WebSecurityConfigurerAdapter impl (if that's wrong or misunderstood in any way, please correct me!).
So then, to implement your own AccessDecisionManager you need to implement 3 methods, one of which is:
public class MyCustomAccessDecisionManager implements AccessDecisionManager {
#Override
public void decide(
Authentication authentication,
Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
// TODO
}
}
I have scoured the Google Gods high and low, and for the life of me I cannot find a meaningful, real world example of what the Object object and Collection<ConfigAttribute> configAttributes arguments are, what they are used for, how they are intended on being used, and what some real world (concrete) examples of them will be at runtime.
The Authentication argument is obvious: it is my auth token and will contain the principal, possibly their credential, and a list of GrantedAuthorities (permissions) associated with the principal.
But the other two arguments (object and configAttributes ) are absolute mysteries to me.
Does anybody know what these arguments are, what some real world use cases of them are, and how they are intended to be used?
As JavaDoc for AccessDecisionManager says:
object – the secured object being called
Usually, it's an instance of the MethodInvocation interface and it represents the method for which call security decision should be performed.
configAttributes - the configuration attributes associated with the secured object being invoked
It's a collection of metadata attributes related to the security object (Method). For example, it can contain information about annotations related to this method, such as #PermitAll, #PreAuthorize, #PostFilter, etc.
There are several scenarios, where I want to update the user/principal data such that the changes are reflected while the user stays logged in (I do not want to force re-authentication)
From "within" the session this is not a Problem:
#PostMapping("/updateInfo")
fun updateMyData(
#AuthenticationPrincipal user: AppUser,
#Valid #RequestBody newInfo: UpdateDataRequest
): ResponseEntity<TestUserInfo> {
val testInfo = TestUserInfo(user, newInfo)
user.info = testInfo
val updatedUser = users.save(user)
return ResponseEntity.ok(updatedUser.info!!)
}
When I allow the user to for example change the their own data, I can easily access and change the #AuthenticationPrincipal - in successive requests i can observe that the data is updated.
This is different when I need to change the user data from 'outside' the session.
use cases
There are 2 use cases for this:
a). an administrator changes user-data
b). the user confirms his email address
Now a). clearly happens from within another http-session where the principal is a user with some admin privileges.
For b). you might ask, why this doesn't happen within a session: I want a simple one-time confirmation link, i.e. a get request. I cannot assume, that the user is logged in via a session on the device the confirmation link is opened. It wouldn't feel right to me, to do a separate preauthentication provider or something to get the user authenticated - then there will an unnecessary session opened on a browser that is never used again.
So in both cases, when I fetch the user via a JPArepository, update data, and save it back, the change is up to date in the databse - but the logged-in users don't know of that change, because their user data is stored in the http session and doesn't know that it needs to be updated.
Note that I am not using redis/spring-session anything - this is just a plain http session, so from my understanding I can not use FindByIndexNameSessionRepository.
What I have tried
In spring-security issue #3849 it was suggested by rwinch to override SecurityContextRepository - however, there is no further information on how to do that exactly - I tried to understand the interface but couldn't get much further there.
I tried to get through the responses tothe followinf SO post:
How to reload authorities on user update with Spring Security (ignoring answers using redis.)
the most upvoted answer by leo doesn't help, as mentioned in the comments there
Aure77 suggests using SessionRegistry, which I tried to use also following bealdung - but to no avail: I cannot the right session, getallprincipals() is always empty when there is an active session for a logged in user. In case I had the right session I'm still not even sure how to move on from there, as Aure just suggests using expireNow() which forces reauthentication - I want to avoid that.
alexkasko suggests something similar - from his I am thinking that maybe spring boot uses a thread-local securityContextRepository by default, and thats why i have no principals. He the suggests something that i haven'T yet understood - also the answers are quite old (2012) and I don'T feel very secure about trying to understand and apply that
TwiN suggests using a HandlerInterceptor. Hasler Choo suggests a modified version with a hashset that seems to be more close to what i need. As described below - it has its problems though.
HandlerInterceptor based approach
This is the only solution so far that I could successfully implement - but it doesn't seem very flexible. My implementation so far will only cover user-role changes.
Configuration:
#Configuration
class WebMvcConfig : WebMvcConfigurer {
#Autowired
private lateinit var updateUserDataInterceptor : UpdateUserDataInterceptor
override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(updateUserDataInterceptor)
}
}
The HandlerInterceptor:
#Component
class UpdateUserDataInterceptor(
#Autowired
private val users: AppUserRepository
) : HandlerInterceptor {
private val usersToUpdate = ConcurrentHashMap.newKeySet<Long>()
fun markUpdate(user: AppUser) = usersToUpdate.add(user.id)
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
val auth = SecurityContextHolder.getContext().authentication
(auth.principal as? AppUser)?.apply {
synchronized(usersToUpdate) {
if (id in usersToUpdate) {
role = users.findById(id).get().role
usersToUpdate.remove(id)
}
}
}
return true
}
}
Instead of just updating the role, what I would rather like, is just replace the entire principle - but the principal is final in the Authentication object.
So whenever a would wnat something else than the role updated, this has to specifically be mentioned here.
Remaining questions:
Are there other solutions than the HandlerInterceptor?
Is there a HandlerInterceptor based solution, that allows me to fully update the principal object
I am not considering single instance applications
1. Three factors in play
How quickly you want the changes reflected ( current session and current request vs current session and next request vs next session)
Do you have to keep the response time minimally affected by using distributed memory or cache?
Do you want to cut the cost (cannot use distributed memory) at the expense of response time?
Now you can you choose one option from first factor. But with second and third factors, you optimise one factor at the expensive of other one. Or you try to find a balance like your attempt to keep a list of affected users in memory and then hit the database for those affected.
( Unfortunately your optimisation to keep list of affected users in UpdateUserDataInterceptor as it is not stored in distributed memory won't work unless it is a single instance application)
2. Now based on my understanding of your question, I am making the following answers to the three factors in play.
current session next request
reduced cost (no distributed memory)
performance hit with database calls
( I will later update my thoughts on other possible paths and possible implementations for those paths)
3. Implementation options for the selected path - next-request-with-db-calls-and-no-distributed-memory
Any component that is part of request filter chain with the ability to call the database can achieve this by updating the SecurityContext. If you do this in the SecurityContextRepository, you are doing it at the earliest opportunity and you may even have the opportunity to restore the SecurityContext with updated principle instead of updating the already created SecurityContext. But any other filter or Interceptor can achieve this too by updating the SecurityContext.
4. Detailed look into each Implementation
SecurityContextRepository Option :
Looking at the HttpSessionSecurityContextRepository, it seems straight forward to extend it.
public class HttpSessionSecurityContextRepository
implements SecurityContextRepository {
.....
public SecurityContext loadContext(HttpRequestResponseHolder reqRespHolder) {
HttpServletRequest request = reqRespHolder.getRequest();
HttpServletResponse response = reqRespHolder.getResponse();
HttpSession httpSession = request.getSession(false);
SecurityContext context = readSecurityContextFromSession(httpSession);
........
//retrieve the user details from db
//and update the principal.
.......
return context;
}
}
SecurityContextHolderStrategy Option
Looking at the ThreadLocalSecurityContextHolderStrategy, it also looks straightforward
final class ThreadLocalSecurityContextHolderStrategy
implements SecurityContextHolderStrategy {
private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
....
public void setContext(SecurityContext context) {
// you can intercept this call here, manipulate the SecurityContext and set it
Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
contextHolder.set(context);
}
.....
}
Another filter or HandlerInterceptor //TODO WILL UPDATE
Note:
You mentioned principal is final in authentication object and you want to replace it. You can achieve this by creating a mutable wrapper of UserDetails, extending your current UserDetailsService and returning that wrapper. Then you can update the principal,
YourWrapper principalWrapper =(YourWrapper) securityContext
.getAuthentication().getPrincipal();
principalWrapper.setPrincipal(updated);
I am using the following code to create an in-memory user:
#Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("pass").authorities("READ","WRITE").roles("USER").build());
System.out.println("user created");
return manager;
}
I have the following code in one of my REST Controller methods:
System.out.println("printing authorities..");
User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
user.getAuthorities().forEach(item-> System.out.println(item.toString()));
I get the following output:
printing authorities..
ROLE_USER
My question, is this supposed to be so? If a do a chained method call authorities("XX").roles("YY"), the later over writes the former.
I want to use a Custom PermissionEvaluator to evaluate permission, on top of a check for the role. How can I provide additional permissions like read/write to the user?
My question, is this supposed to be so? If a do a chained method call
authorities("XX").roles("YY"), the later over writes the former.
As #dur mentioned, this is the way it is implemented. But be aware that
hasRole('XX') will check for Role_XX in your app, whereas
hasAuthroirty('XX') will check for XX. Depends on, how your application is defining the roles. If every role is prefixed with Role_ in your app, hasRole provides a convenient way to auto append that prefix.
I want to use a Custom PermissionEvaluator to evaluate permission, on
top of a check for the role. How can I provide additional permissions
like read/write to the user?
I guess you have already put that in your code with this line:
.authorities("READ","WRITE")
Since this .roles("USER") is overriding the previous declaration, you can change it to:
.authorities("READ","WRITE","ROLE_USER")
and remove the roles declaration.
I'm working on an existing project at the buggy authentication part for an API. As a Spring beginner I feel like I'm missing something important about Security here so I would like some help or advice.
My version of Spring Security is 3.2.5.RELEASE.
I have this method in one of my controllers:
#Override
#ResponseBody
#ResponseStatus(HttpStatus.OK)
#RequestMapping(method = RequestMethod.POST, produces = "text/plain", consumes = "application/xml")
#PreAuthorize("principal.manager.id == #managerId and hasAuthority('ADMIN')")
public String create(#PathVariable("manager-id") Short managerId, #RequestBody Key key) {
return super.create(managerId, key);
}
The important part here is the #Preauthorize line. Indeed, when I try and call this method as a non-authenticated user I get a failure, and logs tell me the following:
Field or property 'manager' cannot be found on object of type 'java.lang.String'
Manager is a field from a custom User class, which implements Serializable and UserDetails.
I'm confused: why would the principal variable be a String and not something like a User object (for example) which is the kind of thing I'm expecting to use and get the info from. This error ends with:
Wrapped by: java.lang.IllegalArgumentException:
Failed to evaluate expression "principal.manager.id == #managerId and hasAuthority('ADMIN')"
Because it obviously can't get a manager attribute from principal, which is a String variable.
To log more about this, I tried to externalize things a bit, and changed my Preauthorize line to:
#PreAuthorize("hasAuthority('ADMIN') and #userService.hasRightManager(principal, #managerId)")
I implemented a very simple hasRightManager method: for now it does nothing except logging the value of the principal variable.
It returned "admin" in the logs, which is indeed a String and not a User object.
Now I'm stuck because even the Spring Security documentation seems to say that a UserDetails object (or some other user info class) should be expected when fetching this principal variable and all I get is a String:
Object getPrincipal() The identity of the principal being
authenticated. In the case of an authentication request with username
and password, this would be the username. Callers are expected to
populate the principal for an authentication request. The
AuthenticationManager implementation will often return an
Authentication containing richer information as the principal for use
by the application. Many of the authentication providers will create a
UserDetails object as the principal.
Returns: the Principal being authenticated or the authenticated
principal after authentication.
I was not the one implementing authentication for this API so it feels kind of blurry to me.
Knowing this, could you please shed some light on this type issue and give me a lead on how to do things differently and get a User object instead of a String when fetching principal?
Do not hesitate to ask me more questions if my explanation happens to be too incomplete to help my case. Thank you.