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);
Related
Suppose there are two type of roles in the application -
Admin
Zonal Manager
Admins can get all the office ids while the zonal managers can get only the office assigned under his zone. In the controller I want something like this
#RequestMapping(method = RequestMethod.GET)
Collection<Long> getOfficeIds(){
// returns all office ids in system
}
#RequestMapping(method = RequestMethod.GET, value = "/{zoneId}")
Collection<Long> getOfficeIds(#RequestParam("zoneId") long zoneId){
// returns all office ids in the zone
}
Now I want all my users to make request with the no-arg version only (the first method). The system should get user role before hitting controller and should call appropriate controller method (if admin then call the first method, if zonal manager call the second one with appropriate zone).
The question is , is it possible at all ? If yes then what would be the best way of doing this ? I could try to modify the request in a servlet filter. Is there a way using method argument resolver ?
Per comments, I am posting the answer below.
The best thing to do to achieve your goal is to add a filter which runs before the request is handled by the controller. In this filter, you can apply the appropriate logic to determine the requesting user's role and act accordingly. If you follow the same URL pattern in all of your controllers to handle these different cases, you can simply rewrite the internal URL after determining which case to apply so that it can be handled by the appropriate controller. In this way, you can keep all of your user-role logic in one location and your controller logic can handle their own, separate flows accordingly.
To create such a filter using spring, you may do something like the following:
#Component("accountContextFilter") public class AccountContextFilter extends OncePerRequestFilter {
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException{
//user role and routing logic
}
}
The reference for the logic inside doFilterInternal can be found here: How to use a servlet filter in Java to change an incoming servlet request url?
Simply change the request path accordingly by appending to your route with the role-defined URLs and you're done.
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.
I have a webservice (cxf) which creates a new Thread each time a time-consuming method is invoked, returning an ID to the client, passed as a token to another web method to check that specific thread's execution state:
public String doWork(){
String id = /***randomnly generates an ID****/
executor.execute(**some runnable**);
// need to save the guy above
return id;
}
public String checkStatus(String id){
/* Here I would basically access to an hashmap
of tasks, pick the one identified by that ID
and check its state */
}
My problem, as you can see above, is to keep a reference to the task(s) so that I can track its execution. Idea I came up with is the following:
#Webservice
public class MyService{
private static HashMap<String, Future> futures = new HashMap<String,Future>();
But is there some better way?Moreover what could be the possible drawbacks of this choice?
Another alternative for storing global state is using a database. Not knowing all the details of your application, the only thing that comes to mind is that depending on how they're used static variables can have issues with thread safety.
It helps to know a few things about the underlying programming model of Java EE servlets, upon which JAX-WS and such are built. This answer explains things well.
To retain information associated with a client for the duration of a session, you can obtain the session in the body of your method and store information in it:
Message message = PhaseInterceptorChain.getCurrentMessage();
HttpServletRequest request = (HttpServletRequest)message.get(AbstractHTTPDestination.HTTP_REQUEST);
HttpSession session = request.getSession(true);
session.setAttribute("id", id);
Upon subsequent method invocations you can retrieve session data in the same manner.
A really basic (ancient) tutorial: Maintaining Client State.
I have having a design issue with asp.net web-api and would like to know how to solve this problem
public abstract class BaseApiController<TEntity> : ApiController where TEntity : Entity
{
protected string GetUsername()
{
return Utilities.GetUsername(Request.Headers.Authorization);
}
//some other code
}
public class StakeholderApiController : BaseApiController<Stakeholders>
{
ILogger _logger = new CustomApiLogger("StkhManager", GetUsername())
//some other code
}
now the problem I have is:
I have the BaseApiController which I am using the share certain functionality between all ApiControllers.
And then I have some specified ApiController for certain not shared functionality.
Now while doing logging, I do want to log the logged in user's name, the problem is I have to pass it everytime I create a new instance, is there a way I can make the logged in user's name global for the current api request so that it can be accessed everywhere.
I cannot make it static, otherwise for others request it will give wrong username.
is there a way I can avoid passing it everytime. Given that webapi is stateless, so session cant be used, is there anyother way??
I am using angularjs front end, hence I am NOT using any authorization technique provided by MVC/.net
Note:
I cannot move creation of Logger to base class for certain reasons
This is just one example, I want to use the logged in user's name in many other place. Hence dont want to pass it around.
There is a standard pattern in setting and accessing principal with ASP.NET Web API. From an ApiController such as your BaseApiController, you can just use the User property to retrieve the same. To set it, typically, the HttpRequestContext is obtained from the request object and the principal is set like so.
Request.GetRequestContext().Principal = new ClaimsPrincipal(...);
BTW, you can access the current request object in the Web API pipeline pretty much from anywhere.
I am wondering if there is a way to wrap all argument resolvers like for #PathVariables or #ModelAttributes into one single transaction? We are already using the OEMIV filter but spring/hibernate is spawning too many transactions (one per select if they are not wrapped within a service class which is be the case in pathvariable resolvers for example).
While the system is still pretty fast I think this is not necessary and neither consistent with the rest of the architecture.
Let me explain:
Let's assume that I have a request mapping including two entities and the conversion is based on a StringToEntityConverter
The actual URL would be like this if we support GET: http://localhost/app/link/User_231/Item_324
#RequestMapping("/link/{user}/{item}", method="POST")
public String linkUserAndItem(#PathVariable("user") User user, #PathVariable("item") Item item) {
userService.addItem(user, item);
return "linked";
}
#Converter
// simplified
public Object convert(String classAndId) {
return entityManager.find(getClass(classAndId), getId(classAndId));
}
The UserService.addItem() method is transactional so there is no issue here.
BUT:
The entity converter is resolving the User and the Item against the database before the call to the Controller, thus creating two selects, each running in it's own transaction. Then we have #ModelAttribute methods which might also issue some selects again and each will spawn a transaction.
And this is what I would like to change. I would like to create ONE readonly Transaction
I was not able to find any way to intercept/listen/etc... by the means of Spring.
First I wanted to override the RequestMappingHandlerAdapter but the resolver calls are well "hidden" inside the invokeHandleMethod method...
The ModelFactory is not a spring bean, so i cannot write an interceptor either.
So currently I only see a way by completely replacing the RequestMappingHandlerAdapter, but I would really like to avoid that.
And ideas?
This seems like a design failure to me. OEMIV is usually a sign that you're doing it wrong™.
Instead, do:
#RequestMapping("/link/User_{userId}/Item_{itemId}", method="POST")
public String linkUserAndItem(#PathVariable("userId") Long userId,
#PathVariable("itemId") Long itemId) {
userService.addItem(userId, itemId);
return "linked";
}
Where your service layer takes care of fetching and manipulating the entities. This logic doesn't belong in the controller.