I am implementing spring security in my application and I want to prevent brute force password attempts for a particular user account.
Whenever a user tries to login with invalid credentials, I am throwing an AuthException() with a basic Invalid Username Or Password message and in addition to I now want to increase a counter in my database for password_attempt to handle brute force attack.
I tried implementing a case where I run a separate transaction using Propagation.REQUIRES_NEW but when throwing exception from outer transaction, inner transaction is also rolled back. How to prevent this?
Also I read it somewhere when searching about it to use spring application events to handle this implementation. But will it be a bad design? Because from UserAuthenticationService I will publish an event, the event will again #Autowired UserAuthenticationService and call the function to increase the counter in the database.
Or is there a way to call the spring event automatically whenever I throw AuthException Or BadCredentialException so that I dont have to publish it explicitly?
PS: I have added #Transactional on class level (UserAuthenticationService.class) snd both methods are in the same class
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void updatePasswordAttempt(Long userId, Integer attemptCount){
userRepository.updatePasswordAttempt(userId, attemptCount);
}
public void validatePassword(String password, UserEntity user){
if(Boolean.TRUE.equals(matchPassword(password, user.getPassword()))){
if(user.getPasswordAttempt().equals(0)) return;
updatePasswordAttempt(user.getId(), 0);
return;
}
int attemptCount = user.getPasswordAttempt() + 1;
updatePasswordAttempt(user.getId(), attemptCount);
throw new AuthException(INVALID_PASSWORD.getMessage(), INVALID_PASSWORD.getCode());
}
Related
I've got problem with managing transactions using Spring. In single transaction I save two new entities and only one can be found.
Here's my method annotated with #Transactional
#Transactional(propagation = Propagation.REQUIRES_NEW)
public User createUser(CreateUserRequest request) {
return userCreator.createUser(request);
}
and createUser:
User createUser(CreateUserRequest request) {
// some validation and business processing
User user = newUser(request);
User savedUser = userRepository.save(user);
publishNewUserCreatedEvent(savedUser);
return savedUser;
}
and publishNewUserCreatedEvent:
private void publishNewUserCreatedEvent(User user) {
UserCreatedEvent event = new UserCreatedEvent(user);
eventRepository.save(event);
}
Then I've got a worker thread that simply fetches the latest event from database and calls a handler for given event.
The problem is that for above example when the UserCreatedEvent's handler is calling UserRepository for userId it got EntityNotFound exception. So the event was persisted, but the user wasn't (yet).
I've tried multiple things (like having two methods annotated with #Transactional(propagation = Propagation.REQUIRES_NEW) where first one saves user to the repository so it should be commited first and the second saves event to the repository, I've tried with TransactionTemplate), but none has worked.
Most of the time retry works, meaning that executing the same event for the second time succeeds, so the user can be found. However it's annoying and I'd like to have control over transaction execution, so I'd expect that user will be saved before the event is saved, so when event handler loads event by ID and user by ID then both can be found.
In the service layer, I have some method who have a transactional annotation.
#Transactional
public void process() throws ProcessPaymentException{
try{
.... do some operation
catch (ProcessPaymentException ppe) {
save db problem issue.
}
}
It seem like if there are a issue, there are roll back... and nothing is saved in the db...
ProcessPaymentException extend Exception
Is there a way to rollback the process in the try but do the save in the catch?
Edit
Nested transaction could be a solution if this link is ok
https://www.credera.com/blog/technology-insights/java/common-oversights-utilizing-nested-transactions-spring/
Existing answer of using ControllerAdvise should help in normal setup that incoming requests are coming through Spring MVC (i.e. through a Controller).
For cases that is not, or you do not want to tie your exception handling logic to Spring MVC, here are some alternatives I can think of
(Here I assume you want to rely on declarative transaction control instead of programmatically controlling transactions yourself)
Separate service/component to save error in different transaction.
In short, you can have a separate service, which create its own transaction by propagation REQUIRES_NEW. e.g.
#Service
public class FooService
#Inject
private ErrorAuditService errorAuditService;
#Transactional
public void process() throws ProcessPaymentException{
try{
.... do some operation
catch (ProcessPaymentException ppe) {
errorAuditService.saveErrorAudit(ppe.getErrorText());
throw ppe; // I guess you want to re-throw the exception
}
}
}
#Service
public class ErrorAuditService
#Transactional(propagation=REQUIRES_NEW)
public void saveErrorAudit() {
// save to DB
}
}
One step further, if the error handling it the same for different services, you may create an advise, which will be called when service method throws exception. In that advise, you can save the error in db (using ErrorAuditService), and rethrow the exception.
Because processes of try-catch are wrapped by the same transaction.
The transaction manager do rollback whenever an exception is thrown. So, not thing would be saved.
Is there a way to rollback the process in the try but do the save in the catch?
Yes. Create Exception Handler to save db problem issue after rollback.
this is the idea
#ControllerAdvice
public class HandlerName {
#ExceptionHandler(ProcessPaymentException.class)
public void saveDbIssue(ProcessPaymentException ex) {
// save db problem issue.
}
But it only works if u want to save static data.
I am getting the LazyInitializationException when i try to retrieve information inside a POJO.
User.java
public class User implements java.io.Serializable {
private Set groups = new HashSet(0);
public Set getGroups() {
return this.groups;
}
}
UserController.java
#RequestMapping(value = "/home", method = RequestMethod.GET)
public ModelAndView getHome(HttpServletRequest request) throws Exception {
ModelAndView mv;
User user = SessionUtil.getSessionUser(request);
if (user == null) {
mv = new ModelAndView("redirect:/user/login");
} else {
mv = new ModelAndView("home");
user = this.userService.getUserById(user.getId());
// Exception here
Set<Group> groups = user.getGroups();
mv.addObject("groups", groups);
// This work fine
List<Group> invitation_groups = this.userService.getInvitationGroups(user);
mv.addObject("invitation_groups", invitation_groups);
// This work fine
List<Group> subscription_groups = this.userService.getSubscriptionGroups(user);
mv.addObject("subscription_groups", subscription_groups);
}
return mv;
}
Database
=====
-User-
id
login
=====
-Goup-
id
user (Foreign key to user)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:285)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:185)
at model.pojo.User_$$_jvst464_2.getGroups(User_$$_jvst464_2.java)
at controller.UserController.getHome(UserController.java:151)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
I think I understand why I get this exception : I always close the HibernateSession after all my transaction in my DAO so the session can't be open for the POJO request.
In an other hand user.getLogin() for exemple works. I think i dont understand well where the problem is. Is that because it uses a foreign key ?
I think i found a workaround here but I dont know how to implement it and if it's really efficient.
I know that if I remove session.close() from my DAO it will works but it's not the solution.
I hope someone can help me. Thanks.
Solution
Remove all the hand made transactions
Add transactionnal annotation
User OpenSessionInView filter.
Thanks guys.
Why are you handling your session manually? Do you need that?
If not, you should use OpenSessionInView pattern. It will keep your session open until the request ends, but, be careful, you can run in trouble with lots of queries made to the database because the lazy load of collections. So whenever you can, try to fetch your data eagerly if you know that they will be used.
Your user.getLogin() returns a string right? Even if it was the one side of a relationship mapping, it would be fetched eagerly by default.
I'm not used with spring but I think spring has an OpenSessionInView filter to manage your session.
Its normal to handle transaction in API layer and using DTO,
So you have: API -> Service -> DAO.
But since you only have transactional in DAO its probably okai, but then you have to take care of lazyload object in DAO., before transaction is closed.
// after this the transaction is open and closed, user object is hibernate jpa entity you usually get this.
user = this.userService.getUserById(user.getId());
The simplest solution is to loop through and do getId() in DAO, before returning user.
Set<Group> groups = user.getGroups();
for (Group group in groups){
group.getId();
}
I'm using the Wicket Auth/Roles and I ran into the same problem as the OP of this thread.
I need to access the DB service layer in the AuthenticatedWebSession (for user authentication). I followed Steve Flasby's suggestion and did the following:
#Override
public Session newSession(Request request, Response response) {
Session s = new MySession(request);
mInjector.inject(s);
return s;
}
Unfortunately this results in
java.lang.IllegalStateException: EntityManager is closed
(presumably due to the fact that (a) I'm using open session in view, and (b) the session spans over several requests).
I solved this by moving the injection into the AuthenticatedWebSession.authenticate method.
#Override
public boolean authenticate(String username, String pass) {
Injector.get().inject(this);
...
}
I suspect that this is not best practice, because now I need to access to the service layer in other methods too, and it doesn't seem like a good idea to add Injector.get().inject(this) in each such method.
My question:
How do I perform injection into the session object upon each request? (Or, if this is a bad approach all together, how do I access the service layer in the AuthenticatedWebSession?)
You can implement IRequestCycleListener (extend AbstractRequestCycleListener) and implement:
#Override
public void onBeginRequest(RequestCycle cycle)
{
if (Session.exists()) {
Injector.get().inject(Session.get());
}
}
Register your IRequestCycleListener in Application#init() with getRequestCycleListeners().add(new YourRequestCycleListener()).
I save current user in session, and when i use current user (example:user.getRole.getRoleName()), i got LIE. How can i solve this problem, my code is like this
Controller:
public String home(){
Users users = userService.getCurrentUser();
if(users.getRole().getRoleName().equals("admin")){ //causing LIE
....
}
UserService :
#Override
public Users getCurrentUser(){
session = ActionContext.getContext().getSession();
return (Users) session.get("user");
}
But, when i change userService.getCurrentUser() to be like this, error is resolved but i think this is not a right manner, because it need connection to database every time i use current user.
#Override
public Users getCurrentUser(){
session = ActionContext.getContext().getSession();
return daoManager.getDetailUser(((Users) session.get("user")).getUsername());
}
DaoManager.getDetailUser is like this
#Override
public Users getDetailUser(String username) {
try {
Users user = (Users)sessionFactory.getCurrentSession().createQuery("SELECT U FROM Users U WHERE U.username=:USERNAME")
.setParameter("USERNAME", username)
.uniqueResult();
return user;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
is there any other better way to solve this problem? Thank you.
The easiest way to resolve this is just to access the lazy fetched field before the session is closed:
#Override
public Users getCurrentUser(){
session = ActionContext.getContext().getSession();
Users user = (Users) session.get("user");
user.getRole(); //Accessed to resolve lazy field
return user;
}
I do not recommend FetchType.EAGER. With that you cannot control access, it is always fetched whether you need it or not.. Add a few EAGER fields to your data model and suddenly your fetching the entire database for the simplest requests..
For queries you can also use JOIN FETCH
The most likely explanation will be that Spring closes the current session when you are exiting the service layer (UserService), however, after this happens, because Hibernate attempts to lazily load the children objects to avoid unnecessarily loading data (see also What is lazy loading in Hibernate?).
To avoid this, you could either ensure that hibernate does not do lazy loading by specifying fetch=FetchType.EAGER on the role, or use the OpenSessionInView pattern (however, that's sometimes considered an antipattern, see Why is Hibernate Open Session in View considered a bad practice?).