I´ve largely used JPA and Hibernate, but I'm relativy new to Spring Data JPA. I'm trying to inject the user logged so queries get visibility restriction upon user.
Let's say we have this entity:
public class Group {
private String code;
private String name;
private String creationUserId;
}
And CreationUserId is a FK to the column Id of entity User. This tries to represent that this group should only be accessed to the user whose Id equals this creationUserId.
So, taking into account I'm using JWT (the subject on the token is the userID) and springSecurity (so the user is in SecurityContextHolder.getContext().getAuthentication()), is there any fancy way of doing something like
public List<Groups> findMyGroups () {
return groupsRepository.findMyGroups();
}
using the capabilities SpringDataJPA gives us?
Im trying to avoid doing something like
public List<Groups> findMyGroups () {
MyUser u = (MyUser)SecurityContextHolder.getContext().getAuthentication().getDetails();
return groupsRepository.findGroupsByUserCreationId(u.getId());
}
I don't have a problem on doing so, but it would result in such boilerplate, and maybe there is some workaround for achieving it.
Of course I don't want this on every single query, just in somes (there are two different security roles for listing, group:readAll and group:readMine).
Spring Security provides integration with Spring Data to achieve what you want. Take a look at the reference documentation about that.
In short, you have to add the dependency and expose a Bean of type SecurityEvaluationContextExtension, like so:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-data</artifactId>
</dependency>
#Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
When you have this, you can use the Authentication in your queries:
#Query("SELECT g FROM Group g WHERE g.creationUserId = ?#{principal?.id}")
List<Group> readMine();
Related
I'm trying to create a REST API using Spring Boot (Version 1.4.0.M2) with Spring Data Neo4j, Spring Data Rest and Spring Security. The Domain is consisting of three types of Entities:
The user entity storing the username/password and relationships to all user owned entities:
#NodeEntity
public class User extends Entity {
#Relationship(type = "OWNS")
private Set<Activity> activities;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
private String username;
}
Content entities created by/owned by a user:
#NodeEntity
public class Activity extends Entity {
private String name;
#Relationship(type = "ON", direction = Relationship.INCOMING)
private Set<Period> periods;
#Relationship(type = "HAS", direction = Relationship.INCOMING)
private User user;
}
Data entities storing date and time informations accessible to all users
#NodeEntity
public class Period extends Entity {
#Relationship(type = "HAS")
private Set<Activity> activities;
#Relationship(type = "HAS_PERIOD", direction = Relationship.INCOMING)
private Day day;
private PeriodNames name;
}
I'm trying to keep everything as simple as possible for now so I only use Repositories extending GraphRepository and a Spring Security configuration that uses Basic Authentication and the User Object/Repository in the UserDetailsService. This is working as expected, I am able to create a user, create some objects and so on.
The problem is, I want a user only to access their own entities. Currently, everybody can access everything. As I understood it from my research, I have three ways to achieve this:
Use Spring Security Annotations on the repository methods like this:
#PostAuthorize("returnObject.user.username == principal.username")
#Override
Activity findOne(Long id);
#PostFilter("filterObject.user.username == principal.username")
#Override
Iterable<Activity> findAll();
Annotate the methods with #Query and use a custom query to get the data.
Create custom controllers and services that do the actual data querying, similar to this: sdn4-university
.
Now my question:
What would be the best way to implement the desired functionality using my available tools?
For me it seems to be the preferred way to use #2, the custom query. This way I can only fetch the data that I actually need. I would have to try to find a way to create a query that enables paging for Page<Activity> findAll(Pageable pageable) but I hope this is possible. I wasn't able to use principal.username in the custom query though. It seems as if spring-data-neo4j doesn't have support for SpEL right now. Is this correct or is there another way to access the currently authenticated user in a query?
Way #1, using Spring Security Annotations works for me (see the code above) but I could not figure out how to filter Page<Activity> findAll(Pageable pageable) because it returns a Page object and not an entity or collection. Also I'm not sure if this way is efficient as the database always has to query for all entities and not only the ones owned by a specific user. This seems like a waste of resources. Is this incorrect?
Or should I just go with #3 and implement custom controllers and services? Is there another way that I didn't read about?
I'm very grateful for any input on this topic!
Thanks,
Daniel
Since no one has answered yet, let me try...
1) I agree with your assessment that using Spring #PostAuthorize Security is not the way to go here. For filtering data it seems not to be the perfect way to do it here. As you mentioned, it would either load all the data and then filter it, creating a heavy load or probably wreck the paging mechanism:
Imagine you have a million results, loading them all would be heavy. And if you filter them later, you might end up with let's say 1.000 valid results. But I strongly doubt that the paging mechanism will be able to cope with that, more likely that in the end you will seem to have many empty pages. So if you loaded the, let's say, first 20 results, you might end up with an empty result, because they were all filtered out.
Perhaps for some stuff you could use #PreAuthorize to prevent a query from happening, if you only want to get a single result, like in findOne. This could lead to a 403, if not allowed, which would, imho, be ok. But for filtering collections, Spring security doesn't seem a good idea.
3) That's always a possibility, but I wouldn't go there without trying for alternatives. Spring Data Rest is intended to make our code cleaner and coding easier, so we should not throw it away without being 100% sure that we cannot get it to do what we need.
2) Thomas Darimont wrote in this blog posting that there is a way to use principal (and other stuff) in #Query annotations. Let me sumarize to have the answer here...
The basic idea is to create a new EvaluationContextExcentionSupport:
class SecurityEvaluationContextExtension extends EvaluationContextExtensionSupport {
#Override
public String getExtensionId() {
return "security";
}
#Override
public SecurityExpressionRoot getRootObject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new SecurityExpressionRoot(authentication) {};
}
}
...and...
#Configuration
#EnableJpaRepositories
class SecurityConfiguration {
#Bean
EvaluationContextExtension securityExtension() {
return new SecurityEvaluationContextExtension();
}
}
...which now allows a #Query like this...
#Query("select o from BusinessObject o where o.owner.emailAddress like "+
"?#{hasRole('ROLE_ADMIN') ? '%' : principal.emailAddress}")
To me, that seems to be the most clean solution, since your #Query now uses the principal without you having to write all the controllers yourself.
Ok, i think I have found a solution. I guess it's not very pretty but it works for now. I used #PostAuthorize("returnObject.user.username == principal.username") or similar for repository methods that work with single entities and created a default implementation for Page<Activity> findAll(Pageable pageable) that just gets the username by calling SecurityContextHolder.getContext().getAuthentication().getName() and calls a custom query method that gets the correct data:
#RestResource(exported = false)
#Query("MATCH (u:User)-[:HAS]->(a:Activity) WHERE u.username={ username } RETURN a ORDER BY CASE WHEN NOT { sortingProperty} IS NULL THEN a[{ sortingProperty }] ELSE null END SKIP { skip } LIMIT { limit }")
List<Activity> findAllForUsernamePagedAndSorted(#Param("username") String username, #Param("sortingProperty") String sortingProperty, #Param("skip") int skip, #Param("limit") int limit);
I've implemented the Specification pattern for filtering some of my JpaRepositories. After I have implemented a Specification, I can use it with a JpaRepository like this:
Page<Entity> page = entityJpaRepository.findAll(entitySpecification, pageable)
Is there a feature, or planned feature, for supporting Specifications in ElasticsearchRepository?
I understand your pain however the thing is that Specification interface belongs to the JPA API that currently none of the Elastic Search Spring Data repositories implement and I don't think they will.
If you really want to use them you should rather migrate from ElasticsearchRepository coming from spring-data-elasticsearch into JPA-based repositories.
Did not try to use JPA together with ElasticSearch myself but noticed that some people does it, check out this link:
https://programmertoday.com/spring-boot-elastic-search-with-spring-data-jpa/
I don't know about Specifications, but Spring provides a bean ElasticsearchOperations which has a "search" method which accepts org.springframework.data.elasticsearch.core.query.CriteriaQuery which is similar to standard hibernate Criteria. Try mapping Specification to CriteriaQuery.
#Service
#RequiredArgsConstructor
public class FooService {
private final ElasticsearchOperations elasticsearchTemplate; // autowired bean
public void search() {
Criteria criteria = new Criteria();
criteria.and(new Criteria("foo").is(foo));
criteria.and(new Criteria("bar").in(bars));
CriteriaQuery criteriaQuery = new CriteriaQuery(criteria);
elasticsearchOperations.search(criteriaQuery,
FooElasticEntity.class).stream()
.map(SearchHit::getContent)
.collect(Collectors.toList())
}
}
Also it's worth to note that there is a SearchHitSupport utility class which adds support for Pageable.
In the existing Grails application, I have 2 user domains, say UserAdmin and UserBasic. Both these domains have few common fields and also some distinct fields(respective to domain) and currently the login/logout is maintained using sessions for both types of users separately. I want to integrate spring-security in the existing application.
What would be the best approach to do it ? Considering that both domains also have different field. Can we inherit both the domains in a single domain and use it as Spring Security user class ? Please suggest.
Spring Security Core uses one of the implementations of UserDetails interface as a projection of authenticated user. Grails provides e.g. GrailsUser class:
https://github.com/grails-plugins/grails-spring-security-core/blob/master/src/java/grails/plugin/springsecurity/userdetails/GrailsUser.java
Keep in mind, that this class is not a "domain" class in terms of Grails application layout - it does not get persisted in the database, it's just a projection of the user that is bounded to the current session.
If you have 2 different domain classes that represents users in your application, you can try to provide your own implementation of UserDetailsService, e.g.
class CustomUserDetailsService implements UserDetailsService {
#Override
UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// 1. Check if expected user is type of UserBasic
// 2. If not, check if expected user is type of UserAdmin
// 3. If nothing found, throw an exception
// 4. Otherwise create new GrailsUser instance using UserBasic or UserAdmin data
// 5. Return created GrailsUser instance
return null
}
}
Then you have to inject your implementation by adding or modifying an entry in grails-app/conf/spring/resources.groovy e.g.
// Place your Spring DSL code here
beans = {
// other beans goes here
// ...
userDetailsService(CustomUserDetailsService)
}
This is just a concept that you can start from.
I'm trying to use NHibernate for a new app with a legacy database. It's going pretty well but I'm stuck and can't find a good solution for a problem.
Let's say I have this model :
a Service table (Id, ServiceName..)
a Movie table (Id, Title, ...)
a Contents table which associates a service and a movie (IdContent, Name, IdMovie, IdService)
So I mapped this and it all went good. Now I can retrieve a movie, get all the contents associated, ...
My app is a movies shop "generator". Each "service" is in fact a different shop, when a user enter my website, he's redirected to one of the shops and obviously, I must show him only movies available for his shop. The idea is : user comes, his service is recognized, I present him movies which have contents linked to his service. I need to be able to retrieve all contents for a movie for the backoffice too.
I'm trying to find the most transparent way to accomplish this with NHibernate. I can't really make changes to the db model.
I thought about a few solutions :
Add the service condition into all my queries. Would work but it's a bit cumbersome. The model is very complex and has tons of tables/queries..
Use nhibernate filter. Seemed ideal and worked pretty good, I added the filter on serviceid in all my mappings and did the EnableFilter as soon as my user's service was recognized but.. nhibernate filtered collections don't work with 2nd lvl cache (redis in my case) and 2nd lvl cache usage is mandatory.
Add computed properties to my object like Movie.PublishedContents(Int32 serviceId). Probably would work but requires to write a lot of code and "pollutes" my domain.
Add new entities inheriting from my nhibernate entity like a PublishedMovie : Movie wich only presents the contextual data
None of these really satisfies me. Is there a good way to do this ?
Thanks !
You're asking about multi-tenancy with all the tenants in the same database. I've handled that scenario effectively using Ninject dependency injection. In my application the tenant is called "manual" and I'll use that in the sample code.
The route needs to contain the tenant e.g.
{manual}/{controller}/{action}/{id}
A constraint can be set on the tenant to limit the allowed tenants.
I use Ninject to configure and supply the ISessionFactory as a singleton and ISession in session-per-request strategy. This is encapsulated using Ninject Provider classes.
I do the filtering using lightweight repository classes, e.g.
public class ManualRepository
{
private readonly int _manualId;
private readonly ISession _session;
public ManualRepository(int manualId, ISession session)
{
_manualId = manualId;
_session = session;
}
public IQueryable<Manual> GetManual()
{
return _session.Query<Manual>().Where(m => m.ManualId == _manualId);
}
}
If you want pretty urls you'll need to translate the tenant route parameter into its corresponding database value. I have these set up in web.config and I load them into a dictionary at startup. An IRouteConstraint implementation reads the "manual" route value, looks it up, and sets the "manualId" route value.
Ninject can handle injecting the ISession into the repository and the repository into the controller. Any queries in the controller actions must be based on the repository method so that the filter is applied. The trick is injecting the manualId from the routing value. In NinjectWebCommon I have two methods to accomplish this:
private static int GetManualIdForRequest()
{
var httpContext = HttpContext.Current;
var routeValues = httpContext.Request.RequestContext.RouteData.Values;
if (routeValues.ContainsKey("manualId"))
{
return int.Parse(routeValues["manualId"].ToString());
}
const string msg = "Route does not contain 'manualId' required to construct object.";
throw new HttpException((int)HttpStatusCode.BadRequest, msg);
}
/// <summary>
/// Binding extension that injects the manualId from route data values to the ctor.
/// </summary>
private static void WithManualIdConstructor<T>(this IBindingWithSyntax<T> binding)
{
binding.WithConstructorArgument("manualId", context => GetManualIdForRequest());
}
And the repository bindings are declared to inject the manualId. There may be a better way to accomplish this through conventions.
kernel.Bind<ManualRepository>().ToSelf().WithManualIdConstructor();
The end result is that queries follow the pattern
var manual = _manualRepository
.GetManual()
.Where(m => m.EffectiveDate <= DateTime.Today)
.Select(m => new ManualView
{
ManualId = m.ManualId,
ManualName = m.Name
}).List();
and I don't need to worry about filtering per tenant in my queries.
As for the 2nd level cache, I don't use it in this app but my understanding is that you can set the cache region to segregate tenants. This should get you started: http://ayende.com/blog/1708/nhibernate-caching-the-secong-level-cache-space-is-shared
I'm trying to make an application using spring 3.0.
Now I've decided to try my hand at spring-security and hibernate.
I've already seen that it's possible to back it with a databasem and I've seen a reference to defining your own queries?
Now the problem I have is that the tutorials I've been finding aren't too clear and that they assume that a user can only have one role. I want to give some users multiple roles.
So I was thinking about a database scheme along the lines of:
User:
user_id
username
password
registrationDate
User_Role:
user_id
role_id
Role:
role_id
rolename
Now I was wondering if anyone had some pointers to some usefull tutorials/advice/comments.
You need to implement your own UserDetails (supports multiple roles for each user). This custom UserDetails implementation is then returned by your own UserDetailsService implementation that's injected on your daoAuthenticationProvider.
See also my answer # Spring Security 3 database authentication with Hibernate for a complete example.
Something like this:
public class CustomUserService implements UserDetailsService {
private UserDao userDao;
public CustomUserService(UserDao u) {
userDao = u;
}
public UserDetails loadUserByUsername(String username) {
CustomUser user = userDao.getUser(username);
if (user == null)
throw new UserNotFoundException("User "+username+" does not exist");
return user;
}
}
And your UserDao implementation is a simple DAO that can easily use hibernate annotations and assign multple roles to your CustomUser object. Pretty basic.