Setting user roles based on some kind of ownership in Spring Security - spring

In my Spring-based application, I currently have basic roles such as ADMIN, and USER.
Is it possible to define a user role such as PHOTO_UPLOADER, which inherits from USER, but also adds a check whether the user making the call is actually the owner of the photo?
I am tired of writing the same if (currentUser.id == photo.uploader.id) in my controller actions over and over again. It applies to other entities as well.

You can handle it with ACLs like Tomasz Nurkiewicz suggested. But Spring Securitz ACLs are complex and poor documented. (The best resource I know for it is this Book: Spring Security 3 - by the authors of Spring Security)
But If you really need only this simple if (currentUser.id == photo.uploader.id) test, then I would recommend an other technique.
It is possible to enhance the method security expressions you can use them in #PreAuthorize annotations. Like:
#PreAuthorize("isPhotoOwner(#photo)")
public void doSomething(final Photo photo) {
To implement such an expression isPhotoOwner the core is really simple:
public class ExtendedMethodSecurityExpressionRoot extends MethodSecurityExpressionRoot {
public ExtendedMethodSecurityExpressionRoot(final Authentication a) {
super(a);
}
/**
*
*/
public boolean isPhotoOwner(final Photo photoObject) {
if (photoObject == null) {
return false;
}
Photo photo = (photo) photoObject;
return photo.getCreator().getLogin().equals(authentication.getName());
}
}
Unfortunaly there is some addtional work to to register the ExtendedMethodSecurityExpressionRoot. --- I have no time at the moment, if you are willing to try this approach, then leave a commment, and I will descripe the rest

i don't know what types of data accessing technology you are using. i know you can write interceptor or event listener to do the security checking for hibernate. i think ibatis is also the same way. in my project, i wrote CRUD enable interface methods in the parent model/entity class, and doing security check in some events, such as before entity loading. spring security acl is a bit complex. implementing your security solution is more better.

Welcome in the world of ACLs - access control list. This tutorial is rather old but pretty comprehensive.

Related

Springboot allow access to endpoint if userId matches

I am following up from this question:
How to configure Spring Boot Security so that a user is only allowed to update their own profile
Imagine I had an end-point /user/edit/{id}, I want this to be accessible if the user either tries to edit themslves (eg: a user with ID 1 accessing /user/edit/1 but not being able to access user/edit/2) or, if they are an admin, to be able to edit any user.
Is there any way I can achieve this in the security configuration?
.antMatchers("/user/edit/**").hasRole("ADMIN")
Would restrict this to admin users, I want either admin or the id matching the user's id.
The only thing I can think of is inside the controller having something like
#GetMapping("/edit/{id}")
public void edit(#PathVariable("id") int id, Principal principal) {
User u = (User) userDetailsService.loadUserByUsername(principal.getName());
if(u.getId() == id || u.getRoles().contains("ADMIN")) {
//accept uer
}
}
But I was under the impression we shouldn't encode access logic in our controller?
It is possible to use Spring Security's Method Security Expressions to do this. Example copied from the docs:
#PreAuthorize("#c.name == authentication.name")
public void doSomething(#P("c") Contact contact);
Read the sections preceding, as there is some configuration needed. Also note that if an expression is used repeatedly you can define your own security annotations.
I was under the impression we shouldn't encode access logic in our
controller?
"Should" is maybe too strong a word, IMHO. Security expressions are powerful, and in theory would allow you to keep all security checks separate from the controller logic. Easier to spot when a check is wrong, or missing. Easier to compare with the Swagger annotations too, if you are using those to document your endpoints.
But it can get trickier when you have to do something like filter rows returned so that the user only sees some of the results. Spring Security can do that using #PostFilter. But sometimes it isn't optimal. For example, if you know that certain rows aren't going to be returned you may be able to run a faster query, rather than filter out rows after the fact.
My first Spring Security project had queries like that, so ever since I have tended to use controller logic instead of security annotations. But that's not a good reason to never use annotations! So by all means use security expressions when you can, but if you have trouble with them or other considerations arise, integrating security with your controller logic isn't so bad IMHO.
To control role access in your controller you can use annotations like #Secured or #PreAuthorize.
To use the #Secured, put in you security config class:
#EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {
// ...
}
And now you can use it in your controller:
#Secured("ROLE_ADMIN")
#PostMapping
public Account post(Account account, double amount){
// ...
}
To use the #PreAuthorize, put in you security config class:
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
// ...
}
And now you can use it in your controller:
#PreAuthorize("hasAuthority('ROLE_ADMIN')")
#PostMapping
public Account post(Account account, double amount){
// ...
}
For more information you can check here the spring docs.

Relax Security for a Spring Data REST Projection

I have a User class and I want to authorize access such that only a user gets to see what he is entitled to.
This was easily achievable using Spring Security in conjunction with Spring Data Rest where in JPA Repository I did below -
public interface UserRepository extends JPARepository<User,Integer> {
#PreAuthorize("hasRole('LOGGED_IN') and principal.user.id == #id")
User findOne(#Param("id") Integer id);
}
In this way, a user when visits to Spring Data REST scaffolded URLs like -
/users/{id}
/users/{id}/userPosts
Only those logged in with {id} get to see these and everyone else gets 401 like I would have wanted.
My problem is that I have one of Projections which is a public view of each user and I am crating it using Spring Data Rest projections as below which I want to be accessible for every {id}
#Projection(name = "details", types = User.class)
public interface UserDetailsProjection {
..
}
So, /users/{id1}?projection=details as well as /users/{id2}?projection=details should give 200 OK and show data even though user is logged in by {id1}
I began implementing this by marking projection with #PreAuthorize("permitAll") but that won't work since Repository has harder security check. Can we have this functionality where for a projection we can relax security ?
I am using latest Spring Data Rest and Spring Security distributions
Seems reasonable to add a custom controller for this use-case.
Please also consider:
Evaluate access in projections using #Value annotations
Add another entity for the same database data but with different field set for read-only operations, e.g. using inheritance (be careful with caching, etc.) - depends on your data storage type
Modify model to split User entity into two different entities (profile, account) since they seem to have different access and possibly even operations
You can also add a ResourceProcessor<UserSummaryProjection> to evaluate access programmatically and replace resource content (projection) with a DTO
Example of evaluating access in projections with #Value annotations:
#Projection(types = User.class, name = "summary")
public interface UserSummaryProjection {
#Value("#{#userSecurity.canReadEmail(target) ? target.email: null}")
String getEmail();
}
Added spring security code in the data access layer is not a good idea. I would suggest you to add the #PreAuthorize annotation to the controller/service method. Since you have a query parameter, ?projection=details, you can have separate controller/service method for the details projection.
Add following to your details projection method:
#RequestMapping("/url", params = {"projection"})
#PreAuthorize("hasRole('LOGGED_IN') and principal.user.id == #id")

How to handle authorization with Breeze JS?

Currently my app looks at router parameter and logged in user (Principal.Identity) to authorize access to certain resources (e.g: Add student to your class [identity + class id]). However, If I'm not wrong, breeze js support just one bulk save. It seems to be that I will have to open each and every data and run through the validation/authorization. That is fine,
but what I may lose is nice separation of cross cutting concern out side my business logic (as a message handler) (finding what roles user has on the class) and nice Authroize annotation feature (just say what roles are needed). So do I have to trade off or is there better programming model which Breeze JS might suggest?
Update:
My question is more on how to separate the authorization (find assigned roles in message handler + verify if required roles are present by adding authorize attribute to controller methods) logic from business or data access logic. Without breeze, I will inspect the incoming message and its route parameter to fetch all its roles then in my put/post/delete methods I would annotate with required roles. I cannot use this technique with breeze (its not breeze's limitation, its trade off when you go for bulk save). So wanted to know if there is any programming model or design pattern already used by breeze guys. There is something on breeze's samples which is overriding context and using repository pattern, will follow that for now.
Breeze can have as many 'save' endpoints as you want. For example, a hypothetical server implementation might be
[BreezeController]
public class MyController : ApiController {
[HttpPost]
[Authorize(...)]
public SaveResult SaveCustomersAndOrders(JObject saveBundle) {
// CheckCustomersAndOrders would be a custom method that validates your data
ContextProvider.BeforeSaveEntitiesDelegate = CheckCustomerAndOrders;
return ContextProvider.SaveChanges(saveBundle);
}
[HttpPost]
[Authorize]
public SaveResult SaveSuppliersAndProducts(JObject saveBundle) {
...
}
You would call these endpoints like this
var so = new SaveOptions({ resourceName: "SaveWithFreight2", tag: "freight update" });
myEntityManager.saveChanges(customerAndOrderEntities, {
resourceName: "SaveCustomersAndOrder" }
).then(...)
or
myEntityManager.saveChanges(supplierAndProductEntities, {
resourceName: "SaveSuppliersAndProducts" }
).then(...)
Authorization is mediated via the [Authorize] attribute on each of the [HttpPost] methods. You can read more about the [Authorize] attribute here:
http://sixgun.wordpress.com/2012/02/29/asp-net-web-api-basic-authentication/
The proper way to do this IMHO is to separate the endpoint authorization and the database actions authorization.
First, create an entity that manages the grands per controller/method and role. For each method you have a value allowed - not allowed for the specific role. You create a special attribute (subclass of Authorize) that you apply to your controllers (breeze or plain web api) that reads the data and decides whether the specific endpoint can be called for the user/role. Otherwise it throws the Unauthorized exception.
On the breeze side (client) you extend the default adapter settings with a method that adds the authentication headers from identity that you received at login, something like this :
var origAjaxCtor = breeze.config.getAdapterInstance('ajax');
$.extend(true, origAjaxCtor.defaultSettings, Security.getAuthenticationHeaders());
On the server, add a second entity that manages the authorization for the CRUD operations. You need a table like (EntityName, AllowInsert, AllowUpdate, AllowDelete). Add a BeforeSave event on the Context Manager or on the ORM (EF or something else) that loops all entities and applies the policy specified on the table above.
This way you have a clear separation of the endpoint logic from the backend CRUD logic.
In all cases the authorization logic should first be implemented server side and if needed should be pushed to the clients.
The way breeze is implemented and with the above design you should not need more than 1 save endpoint.
Hope it helps.
However, If I'm not wrong, breeze js support just one bulk save.
That is entirely wrong. You have free reign to create your own save methods. Read the docs, it's all there.

Keep record of what users do in Spring MVC

I have a very large application written in Spring MVC. I want to keep an "activity record" that tracks into a database what users do in my application.
In the first stage I just want an activity log, it can be just a list of the controller methods that get called during user's actions, but later on I would like this info to be more "human readable", i.e. instead of "modifyAccount(accountId = 5, accountBalance =500) something like "user X updates balance for account 5 to 500".
The problem I see is, since my application is very large, I would not like to modify each of my actions to add this logging mechanism. Is there a more flexible, declarative way to do this?
You can make use of Aspect Oriented Programming (AOP) to automate the logging.
http://static.springsource.org/spring/docs/2.0.8/reference/aop.html
In the above page shows many examples on how to use AOP with spring. One example is the use of annotations to find methods you're interested in. The use of such annotation is an easy way to determine what methods should be logged.
The #Audit annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Auditable {
...
}
The auditing method
#After("#annotation(auditable)", argNames="joinPoint")
public void audit(JoinPoint joinPoint) {
logger.info("Called {} with arguments {}",
joinPoint.getSignature().getLongString(), joinPoint.getArgs());
}
I did not test this code but it gets the point across.

Passing User Info to MVC Controller

I am looking at various methods to implement Authentication in my MVC3 app. I would like to use my own code to do the authentication – something similar to Is it possible to create a Logon System with ASP.NET MVC but not use the MembershipProvider? (I know that there are other methods.) I would like to know once I authenticate a user using one of these methods, how do I get the user information to the Controller constructor. (By user information I mean username or userID).
One of the options I considered is putting that info into the Session. This works, but it is difficult to test since I get the Session out of the Context which does not exist during the test.
I would appreciate any ideas for how to pass user info to the controller constructor.
No. Do not use Session for authentication. It's less secure, and unstable (sessions can be destroyed at will by the server).
In MVC, you don't need to use membership at all, but.. and I will make a point of emphasizing the but... Doing authentication correctly is not a trivial task. It's very very very easy to get it wrong and not even realize it. Even if you know what you're doing. It's something that should be heavily analyzed, tested, verified, and re-analyzed.
I would suggest, if you don't want to extend this effort, you should probably just use the default providers (there are several you can choose from).
But in any event, if you are determined to do it yourself, all you need is some way to verify the user. MVC does not integrate with the membership provider like WebForms does. It uses it for convenience. If you look in the default AccountController that is generated for you if you create an Internet project, all it does is call Membership.VerifyUser().
The truly important thing is the Authentication cookie system, which MS provides in the form of the FormsAuthentication class. I would VERY strongly recommend using this for the cookie management, unless you REALLY REALLY REALLY know what you are doing.
Just look in the AccountController, and it should be very obvious how this works. FormsAuthentication is the part that integrates into the app and tells asp.net that the user has already been authenticated. It uses a secure, encrypted cookie, and it's well designed (it even allows you to store your own additional data in an encrypted format).
Forms Authentication is a set of classes that work together to provide a transparent authentication mechanism, and is integrated into MVC and Asp.net WebForms. They are basically an implementation of the IPrincipal and IIdentity system, which is integral to asp.net (if you type User.IsAuthenticated this uses the IPrincipal interface).
In my original post I was looking at passing User Info to the Controller constructor. I did not want the Controller to depend on HttpContext, because this would make it difficult to test.
While I thank Mystere Man for his solution, I hope the following alternate solution would help someone. I have a small project (about a dozen controllers) so it is not too bad.
I basically created my custom ControllerFactory inheriting from DefaultControllerFactory:
public class MyCustomControllerFactory : DefaultControllerFactory
{
public MyCustomControllerFactory ()
{
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
{
return null;
}
else
{
//Example of User Info - Customer ID
string customerIDStr = requestContext.HttpContext.Session["CustomerID"].ToString();
int customerID = Int32.Parse(customerIDStr);
//Now we create each of the Controllers manually
if (controllerType == typeof(MyFirstController))
{
return new MyFirstController(customerID);
}
else if (controllerType == typeof(MySecondController))
{
return new MySecondController(customerID);
}
//Add/Create Controllers similarly
else //For all normal Controllers i.e. with no Arguments
{
return base.GetControllerInstance(requestContext, controllerType);
}
}
}
}
I then set the ControllerFactory in the Global.asax.cs Application_Start() method.
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(new MyCustomControllerFactory ());
}
P.S. I looked into using DI Containers like Ninject, but I think they were too complicated for my current project. I would look at them in a few months when it really makes sense to use them.

Resources