How to secure REST APIs and not JpaRepository - spring

I am trying to have unsecured DAOs and secured REST APIs using Spring Data, Spring Data JPA and Spring Data Rest.
For example, I have the DAO repository which should not be secured since I want to call its methods from anywhere without having to provide an authentication:
#RepositoryRestResource(exported = false, path = "persons")
public interface UserRepository extends JpaRepository<User, Long> {
int countByUsername(String username);
void deleteByUsername(String username);
Optional<User> findByUsername(String username);
}
And I have the REST repository, which must be secured using authorizations of the current user:
#RepositoryRestResource(path = "persons")
#PreAuthorize("hasAuthority('FETCH_USER')")
public interface UserRestRepository extends PagingAndSortingRepository<User, Long> {
#PreAuthorize("hasAuthority('DELETE_USER')")
#Override
void delete(User user);
#PreAuthorize("hasAuthority('DELETE_USER')")
#Override
void deleteAll();
#PreAuthorize("hasAuthority('DELETE_USER')")
#Override
void deleteAll(Iterable<? extends User> iterable);
#PreAuthorize("hasAuthority('DELETE_USER')")
#Override
void deleteById(Long id);
#Override
Iterable<User> findAll();
#Override
Page<User> findAll(Pageable pageable);
#Override
Iterable<User> findAll(Sort sort);
#Override
Iterable<User> findAllById(Iterable<Long> iterable);
#Override
Optional<User> findById(Long id);
#PreAuthorize("hasAuthority('SAVE_USER')")
#Override
<S extends User> S save(S s);
#PreAuthorize("hasAuthority('SAVE_USER')")
#Override
<S extends User> Iterable<S> saveAll(Iterable<S> iterable);
}
The problem here is that it seems to automatically use the JpaRepository to create and configure the REST service, so I am forced to add exported = false on it to only expose methods of the REST repository, BUT there's still a problem, if I want to set change path (to persons) on the REST service, it doesn't work, I still need to duplicate it on the JpaRepository...
Am I not supposed to do like this ?
Is it not possible to create a separate JpaRepository and PagingAndSortingRepository, one for the DAO and the other for the REST ?

Secure the endpoints (URLs) using Spring Security rather than the
methods. javabullets.com/spring-security-spring-data-rest
See Alan Hays comment

Related

Custom sql statement using Spring JPA

I am trying to insert a record into a table called user. The method that I am trying to troubleshoot is insertNewUser which is located in my class UserRepositoryCustom. I am using JPARepository and EntityManger to accomplish this. The I am still unable to insert a user into the table. Any help with this would be very much appreciated.
UserRepositoryImpl
public class UserRepositoryImpl implements UserRepositoryCustom {
#PersistenceContext
private EntityManager entityManager;
public void insertNewUser(User user) {
entityManager.createNativeQuery("INSERT INTO user (username,password) VALUES (?,?)")
.setParameter(1,user.getUserName())
.setParameter(2,user.getPassword());
}
}
UserRepositoryCustom
public interface UserRepositoryCustom {
public void insertNewUser(User user);
}
UserRepository
#Repository
public interface UserRepository extends JpaRepository<User, Integer>, UserRepositoryCustom {
User findById(int id);
User findByUserName(String userName);
}
The issue was that I was not using #Autowired on my service class's contructor to inject the repository beans. When I invoked the Repository bean methods nothing was happening. Essentially the Repository beans were just null objects.

Caching Spring Data repositories / CacheEvict not working

I am using Spring Boot, Spring Data, with QueryDSL. Most of my queries are QueryDSL based. I want to implement a simple cache that stores User queries, and once a single entity is updated/saved then the entire Cache is cleared.
So here is my Repository Interface:
#Repository
#CacheConfig(cacheNames = "us")
public interface UserRepository extends JpaRepository<User, Long>, QueryDslPredicateExecutor<User> {
#Cacheable
List<User> findAll();
#Override
#Cacheable
List<User> findAll(Sort sort);
#Override
#Cacheable
List<User> findAll(Iterable<Long> longs);
#Override
#CacheEvict(allEntries = true)
<S extends User> List<S> save(Iterable<S> entities);
#Override
#CacheEvict(allEntries = true)
<S extends User> S saveAndFlush(S entity);
#Override
#Cacheable
Page<User> findAll(Pageable pageable);
#Override
#CacheEvict(allEntries = true)
<S extends User> S save(S entity);
#Override
#Cacheable
User findOne(Long aLong);
#Override
#Cacheable
Iterable<User> findAll(Predicate predicate);
#Override
#Cacheable
User findOne(Predicate predicate);
#Override
#Cacheable
Iterable<User> findAll(Predicate predicate, Sort sort);
#Override
#Cacheable
Page<User> findAll(Predicate predicate, Pageable pageable);
}
As you can see I have cached all "Find" methods and using Save methods to Evict all entries. Usually the ones used are:
Page<User> findAll(Predicate predicate, Pageable pageable);
<S extends User> S save(S entity);
The Caching is set exclusively on the repositories, nothing on Services/Controllers etc.
Here is my config:
#Bean
public CacheManager cacheManager() {
GuavaCacheManager manager = new GuavaCacheManager();
CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder()
.removalListener(new RemovalListener<Object, Object>() {
#Override
public void onRemoval(RemovalNotification<Object, Object> notification) {
LOG.info("Cache evicted: " + notification);
}
})
.maximumSize(100);
manager.setCacheBuilder(builder);
return manager;
}
#Bean
public KeyGenerator keyGenerator() {
return new SimpleKeyGenerator();
}
I should also add that the User entity has a couple of subclasses but I am querying everything with the UserRepository
The problem is that even though my Search requests are successfully cached the cache is never evicted when I perform a save.
Anything I'm doing wrong?

How to implement AuditorAware with Spring Data JPA and Spring Security?

We use Hibernate/JPA, Spring, Spring Data and Spring Security in our application. I have a standard User entity which is mapped using JPA. Further, I have a UserRepository
public interface UserRepository extends CrudRepository<User, Long> {
List<User> findByUsername(String username);
}
which follows the Spring Data convention for naming query methods. I have an entity
#Entity
public class Foo extends AbstractAuditable<User, Long> {
private String name;
}
I want to use Spring Data auditing support. (As descripe here.) Hence I created a AuditorService as follows:
#Service
public class AuditorService implements AuditorAware<User> {
private UserRepository userRepository;
#Override
public User getCurrentAuditor() {
String username = SecurityContextHolder.getContext().getAuthentication().getName();
List<User> users = userRepository.findByUsername(username);
if (users.size() > 0) {
return users.get(0);
} else {
throw new IllegalArgumentException();
}
}
#Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
When I create a method
#Transactional
public void createFoo() {
Foo bar = new Foo();
fooRepository.save(foo);
}
Where everything is correctly wired and FooRepository is a Spring Data CrudRepository. Then a StackOverflowError is thrown since the the call to findByUsername seems to trigger hibernate to flush the data to the database which triggers AuditingEntityListener who calls AuditorService#getCurrentAuditor which again triggers a flush and so on.
How to avoid this recursion? Is there a "canonical way" to load the User entity? Or is there a way to prevent Hibernate/JPA from flushing?
I got the same issue and what I did was just change the propagation on the findByUsername(username) method to Propagation.REQUIRES_NEW, I suspected that was a problem with the transactions, so I changed to use a new transaction and that worked well for me. I hope this can help.
#Repository
public interface UserRepository extends JpaRepository<User, String> {
#Transactional(propagation = Propagation.REQUIRES_NEW)
List<User> findByUsername(String username);
}
The solution is not to fetch the User record in the AuditorAware implementation. This triggers the described loop, since a select query triggers a flush (this is the case since Hibernate/JPA wants to write the data to the database to commit the transaction before executing the select), which triggers a call to AuditorAware#getCurrentAuditor.
The solution is to store the User record in the UserDetails provided to Spring Security. Hence I created my own implementation:
public class UserAwareUserDetails implements UserDetails {
private final User user;
private final Collection<? extends GrantedAuthority> grantedAuthorities;
public UserAwareUserDetails(User user) {
this(user, new ArrayList<GrantedAuthority>());
}
public UserAwareUserDetails(User user, Collection<? extends GrantedAuthority> grantedAuthorities) {
this.user = user;
this.grantedAuthorities = grantedAuthorities;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return grantedAuthorities;
}
#Override
public String getPassword() {
return user.getSaltedPassword();
}
#Override
public String getUsername() {
return user.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
public User getUser() {
return user;
}
}
Further, I changed my UserDetailsService to load the User and create UserAwareUserDetails. Now it is possible to access the User instance through the SercurityContextHolder:
#Override
public User getCurrentAuditor() {
return ((UserAwareUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUser();
}
It looks like you use a User entity for two different things:
authentication
audit
I think it will be better to prepare a special AuditableUser for audit purpose (it will have identical username field as original User).
Consider following case: you want to delete some User from database. If all your audit objects are linked to User then they will a) loose author b) may be deleted by cascade too (depends on how the link is implemented). Not sure that you want it.
So by using special AuditableUser you will have:
no recursion
ability to delete some User from the system and preserve all audit info about it
To be honest, You do not actually require one another entity.
For example, I had similar problem and I resolved it in following way:
public class SpringSecurityAuditorAware implements AuditorAware<SUser>, ApplicationListener<ContextRefreshedEvent> {
private static final Logger LOGGER = getLogger(SpringSecurityAuditorAware.class);
#Autowired
SUserRepository repository;
private SUser systemUser;
#Override
public SUser getCurrentAuditor() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
SUser principal;
if (authentication == null || !authentication.isAuthenticated()) {
principal = systemUser;
} else {
principal = (SUser) authentication.getPrincipal();
}
LOGGER.info(String.format("Current auditor is >>> %s", principal));
return principal;
}
#Override
public void onApplicationEvent(final ContextRefreshedEvent event) {
if (this.systemUser == null) {
LOGGER.info("%s >>> loading system user");
systemUser = this.repository.findOne(QSUser.sUser.credentials.login.eq("SYSTEM"));
}
}
}
Where SUser is both the class which I use for auditing as well as for the security.
I had maybe different use case than Yours and my approach will be deleted after, but it can be resolved like this.

How to add cache feature in Spring Data JPA CRUDRepository

I want to add "Cacheable" annotation in findOne method, and evict the cache when delete or happen methods happened.
How can I do that ?
virsir, there is one more way if you use Spring Data JPA (using just interfaces). Here what I have done, genereic dao for similar structured entities:
public interface CachingDao<T, ID extends Serializable> extends JpaRepository<T, ID>, JpaSpecificationExecutor<T> {
#Cacheable(value = "myCache")
T findOne(ID id);
#Cacheable(value = "myCache")
List<T> findAll();
#Cacheable(value = "myCache")
Page<T> findAll(Pageable pageable);
....
#CacheEvict(value = "myCache", allEntries = true)
<S extends T> S save(S entity);
....
#CacheEvict(value = "myCache", allEntries = true)
void delete(ID id);
}
I think basically #seven's answer is correct, but with 2 missing points:
We cannot define a generic interface, I'm afraid we have to declare every concrete interface separately since annotation cannot be inherited and we need to have different cache names for each repository.
save and delete should be CachePut, and findAll should be both Cacheable and CacheEvict
public interface CacheRepository extends CrudRepository<T, String> {
#Cacheable("cacheName")
T findOne(String name);
#Cacheable("cacheName")
#CacheEvict(value = "cacheName", allEntries = true)
Iterable<T> findAll();
#Override
#CachePut("cacheName")
T save(T entity);
#Override
#CacheEvict("cacheName")
void delete(String name);
}
Reference
I solved the this in the following way and its working fine
public interface BookRepositoryCustom {
Book findOne(Long id);
}
public class BookRepositoryImpl extends SimpleJpaRepository<Book,Long> implements BookRepositoryCustom {
#Inject
public BookRepositoryImpl(EntityManager entityManager) {
super(Book.class, entityManager);
}
#Cacheable(value = "books", key = "#id")
public Book findOne(Long id) {
return super.findOne(id);
}
}
public interface BookRepository extends JpaRepository<Book,Long>, BookRepositoryCustom {
}
Try provide MyCRUDRepository (an interface and an implementation) as explained here: Adding custom behaviour to all repositories. Then you can override and add annotations for these methods:
findOne(ID id)
delete(T entity)
delete(Iterable<? extends T> entities)
deleteAll()
delete(ID id)

requestfactory complain about find method

I have a spring (3.1) application with a service and dao layer.
I try to use requestfactory (gwt 2.4) withi this spring layer.
Here some of my class
My domain class
public class Account {
Long id;
String username;
// get, set
}
The bridge between spring and gwt
public class SpringServiceLocator implements ServiceLocator {
#Override
public Object getInstance(Class<?> clazz) {
HttpServletRequest request = RequestFactoryServlet.getThreadLocalRequest();
ServletContext servletContext = request.getSession().getServletContext();
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
return context.getBean(clazz);
}
}
My account proxy
#ProxyFor(value=Account.class, locator = AccountLocator.class)
public interface AccountProxy extends EntityProxy{
public Long getId();
public String getUsername();
public void setUsername(String userName);
public void setId(Long id);
}
RequestContext class
#Service(locator = SpringServiceLocator.class, value =AccountService.class)
public interface AccountRequest extends RequestContext {
Request<List<AccountProxy>> loadAllAccounts();
}
My requestFactory class
public interface AccountRequestFactory extends RequestFactory {
AccountRequest accountRequest();
}
My spring service
public interface AccountService {
public List<Account> loadAllAccounts();
}
#Service
public class AccountServiceImpl implements AccountService{
#Autowired
private AccountDAO accountDAO;
}
Account locator to avoid to put method in the entity
public class AccountLocator extends Locator<Account, Long> {
#Autowired
private AccountDAO accountDAO;
#Override
public Account create(Class<? extends Account> clazz) {
return new Account();
}
}
applicationContext.xml
<context:annotation-config />
<context:component-scan base-package="com.calibra" />
<bean id="accountService" class="org.calibra.server.service.AccountServiceImpl"/>
<bean id="accountDAO" class="org.calibra.server.dao.AccountDAOImpl"/>
The demo work but i get this error:
com.google.web.bindery.requestfactory.server.UnexpectedException: Could not find static method with a single parameter of a key type
Also on my AccountProxy i get this complain (a warning)
The domain type org.calibra.domain.Account has no Account findAccount(java.lang.Long) method. Attempting to send a AccountProxy to the server will result in a server error.
I don't want to add a find methond in my domain class.
I tried to put this method in my spring service, but i get the same warning.
Edit with the Locator that work fine
Just strange i need to put bean in the applicationContext, context:annotation and context:component-scan seem useless
Any idea?
The domain type org.calibra.domain.Account has no Account findAccount(java.lang.Long) method.
If you don't provide a find method of some kind, RequestFactory has no way of reconstituting objects when they get to the server - it can only create brand new ones, which prevents it from merging with existing data. Take this away, and you might as well have RPC again.
If you don't want static methods, provide a Locator instance which is able to find objects. From https://developers.google.com/web-toolkit/doc/latest/DevGuideRequestFactory#locators:
What if you don't want to implement persistence code in an entity itself? To implement the required entity locator methods, create an entity locator class that extends Locator:
public class EmployeeLocator extends Locator<Employee, Long> {
#Override
public Employee create(Class<? extends Employee> clazz)
{
return new Employee();
}
...
}
Then associate it with the entity in the #ProxyFor annotation:
#ProxyFor(value = Employee.class, locator = EmployeeLocator.class)
public interface EmployeeProxy extends EntityProxy {
...
}
You'll need to implement all of the methods, not just create - and the main one you are interested in is find(Class, Long). It may be possible to use one single Locator type for all proxies - as of 2.4.0 and 2.5.0-rc1 it is safe to fail to implement getDomainType(), and all of the other methods that need to know the exact type are provided with it as an argument.
Here is an example of what this can look like with JPA and Guice, but I think the idea is clear enough that it can be implemented with Spring and whatever persistence mechanism you are using. Here, all entities are expected to implement HasVersionAndId, allowing the locator to generalize on how to invoke getVersion and getId - you might have your own base class for all persisted entities.
(from https://github.com/niloc132/tvguide-sample-parent/blob/master/tvguide-client/src/main/java/com/acme/gwt/server/InjectingLocator.java)
public class InjectingLocator<T extends HasVersionAndId> extends Locator<T, Long> {
#Inject
Provider<EntityManager> data;
#Inject
Injector injector;
#Override
public T create(Class<? extends T> clazz) {
return injector.getInstance(clazz);
}
#Override
public T find(Class<? extends T> clazz, Long id) {
return data.get().find(clazz, id);
}
#Override
public Class<T> getDomainType() {
throw new UnsupportedOperationException();//unused
}
#Override
public Long getId(T domainObject) {
return domainObject.getId();
}
#Override
public Class<Long> getIdType() {
return Long.class;
}
#Override
public Object getVersion(T domainObject) {
return domainObject.getVersion();
}
}

Resources