Spring security, bypass login page after changing password - spring

I'm relatively new to Java/Spring/MVC. I hope someone can shine some light on this, because this problem is about to ruin yet another weekend without getting solved.
At the moment I'm working on a project, implementing a couple of RFC's. Each RFC has it's own 'challenges', ranging from changing hibernate queries and stored procedures to minor textual changes and adding extra fields to forms.
The problem I am now facing has (I think) to do with Spring security.
The present situation:
To login to the application, user is presented by a login page (/user/login.jsp). After entering a username and password, user is sent to welcome page (/welcome/hello.jsp).
If a user forgets their password, they use a link that will sent an email providing a link to a page (/welcome/newPassword?code=xxx) where a new password can be entered. Entering the Submit-button will do some validation, save the password in the database and displays a 'password changed' message on the page.
To enter the application, user will now have to login with the new password.
What the customer wants:
After submitting the new password, the user should be automatically logged in with the new password and redirected to /welcome/hello.jsp
I searched the web, but couldn't find the scenario I was looking for (or maybe didn't recognize it!).
What I thought would do the trick:
I have this method in WelcomeController which presently handles the new password:
#RequestMapping(method = RequestMethod.POST)
public void newPassword(Model model, HttpSession httpSession, ModelAttribute(PASSWORD) Passwords password, BindingResult result) {
logger.debug("Posting password for new password");
ScreenMessage message = null;
//
try {
// errors from initBinder - back to page
if (result.hasErrors()) {
return;
}
// Validate input: on error go back to form
passwordValidator.validate(password, result);
if (result.hasErrors()) {
return;
}
// Save password
passwordService.createNewOwnPassword(result, password);
if (result.hasErrors()) {
return;
}
// No errors
message = new ScreenMessage();
message.setText(CHANGED);
message.setArg(THIS_PASSWORD, 0);
model.addAttribute(MESSAGE, message);
httpSession.setAttribute(ACTION_READ, Boolean.TRUE);
// Errors are fatal.
} catch (Exception e) {
logger.error("ERROR-CHANGE-password for user: " + password.getUser().getLoginName(), e);
result.reject( ERROR_FATAL, new Object[] { password}, ERROR_FATAL);
} finally {
model.addAttribute(PASSWORD, password);
}
}
I thought I could simply change this method to have it return a ModelAndView and use redirect:
ModelAndView mav = new ModelAndView("redirect:/welcome/hello", (ModelMap) model);
return mav;
But: It keeps sending me to /user/login, exactly the page I don't want to go to...
Extra information that could be handy:
As far as I can tell the application is using Spring 2.5.
(I hope relevant) Part of my security-applicationContext.xml:
<!-- always-use-default-target="true" -->
<security:form-login login-page="/dyn/user/login"
login-processing-url="/dyn/user/j_spring_security_check"
default-target-url="/dyn/welcome/hello"
always-use-default-target="true"
authentication-failure-handler-ref="exceptionTranslationRouting" />
<security:access-denied-handler error-page="/dyn/welcome/hello" />
<security:logout logout-success-url="/dyn/user/logout" invalidate-session="true" />
Is my idea of redirecting to /welcome/hello too simple or am I just missing something small?

Firstly, you'd better move your logic from web controller (I don't like password validation) to component dedicated to password change. This will allow you to logically separate parts of your application. Filter maybe? (more later) Or special service?
Secondly, take a look at the source code of Spring Security. From my experience, I can tell you that any customization in Spring Security is only possible if you understand its architecture (especially filter chain). I would recommend taking look at class AbstractAuthenticationProcessingFilter.
It's responsible for authenticating users when they provide username and password in your application. Take a look at the method AbstractAuthenticationProcessingFilter.successfulAuthentication.
This line of that method sets security context which makes the user logged in:
SecurityContextHolder.getContext().setAuthentication(authResult);
Reference authResult is of type Authentication. It is an interface implemented by for instance UsernamePasswordAuthenticationToken. Take a look at UsernamePasswordAuthenticationFilter.attemptAuthentication method for example of how to construct such an object.
Going back to filter solution:
I personally would think about implementing my own filter that would extend AbstractAuthenticationProcessingFilter. It would be responsible for password change. That way you don't have to think about things like session change on successful authentication. You just need to inject it in you XML.
To sum up,
classes that are recommended to look at:
AbstractAuthenticationProcessingFilter
UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationFilter
You should read Spring Security Documentation, especially parts about architecture, filter chain and its role in authenticating users.
http://static.springsource.org/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#overall-architecture

Related

A much better way to automatically log user in after account verification in spring boot?

I'm working on a practice project with spring boot and thymeleaf.
I have a registration form. After the user has registered their account, a verification code will be sent to their email. The next page is where they will enter the code.
I'm using RedirectAttributes to store the verification code id so I will be able to lookup/confirm the code in the verification code page. I am also using RedirectAttributes to pass the user email and raw password to the verification code page so they can be automatically logged in after confirming their account.
#PostMapping("/register")
public String processRegisterForm(#ModelAttribute NewAccountRequest newAccountRequest, RedirectAttributes redirectAttributes) {
Long verifyCodeId = accountService.createAccount(newAccountRequest);
redirectAttributes.addFlashAttribute("verifyCodeId", verifyCodeId);
redirectAttributes.addFlashAttribute("userEmail", newAccountRequest.getEmail());
redirectAttributes.addFlashAttribute("rawPassword", newAccountRequest.getPassword());
return "redirect:/register/verify-account";
}
Now, here is where it gets messy. In the GetMapping for the page to enter verification code, I also had to add the exact same attribute I added in the above PostMapping. Like this:
#GetMapping("/register/verify-account")
public String loadVerificationPage(#ModelAttribute("verifyCodeId") String verifyCodeId, #ModelAttribute("userEmail") String userEmail, #ModelAttribute("rawPassword") String rawPassword, Model model) {
model.addAttribute("verifyCodeId", verifyCodeId);
model.addAttribute("userEmail", userEmail);
model.addAttribute("rawPassword", rawPassword);
return "verify-account";
}
And in thymeleaf, I did this:
<input type="hidden" name="verificationCodeId" th:value="${verifyCodeId}">
<input type="hidden" name="userEmail", th:value="${userEmail}">
<input type="hidden" name="rawPassword" th:value="${rawPassword}">
And in the PostMapping for the page to enter verification code:
#PostMapping("/register/verify-account")
public String confirmVerifyCode(#RequestParam String enteredVerificationCode, #RequestParam String verificationCodeId, #RequestParam String userEmail, #RequestParam String rawPassword,
HttpServletRequest request) {
accountService.confirmVerificationCode(enteredVerificationCode, verificationCodeId);
try {
request.login(userEmail, rawPassword);
}
catch (ServletException e) {
System.out.println("Login error: " + e);
}
return "redirect:/?loginSuccess";
}
I can't get the flash attribute directly in the PostMapping of register/verify-account so I needed to use input fields in thymeleaf
I feel like there's a much easier and simpler way to do this because this just seem really messy. Any tips? Please also note that I haven't added any validations so ignore that. And could their be any security risks in using flash attributes to hold verification codes and passwords? Or to storing verification code in "hidden" input fields?
The registration flow:
User loads the register page
When the form is submitted, processRegisterForm() is called;
The user's account is created and the verification code id is returned.
To confirm the verification code in the next page, the id is stored in a flash attribute. The user's email and raw password is also stored. User is redirected to the page to enter verification code
For the GetMapping of the page to enter verification code, model attributes are added to store the code id, user email, and password. In thymeleaf, hidden input fields are used to hold those 3 values mentioned.
The PostMapping of the page to enter verification code gets those 3 fields and uses it to verify/auto login the user.
I'm hopping there is a much better way to achieve this. Sorry for the long post. Any help will be appreciated, thanks
I ended up cleaning the code up by using a DTO instead of those request params. Don't know why I didn't do that earlier.
In the /register/verify-account mapping, before logging in the user, I did this:
if (passwordEncoder.matches(userPassword, dbPassword)) {
try {
request.login(account.getEmail(), userPassword);
}
catch (ServletException e) {
System.out.println("Login after account verification failed" + e);
}
}
else {
System.out.println("fishy fishy");
}
I'm not sure if this really adds any security but it's good enough for me for now. Special thanks to these people for helping me: , , and
EDIT: I ended up using HttpSession

Unable to find the group information of the logged in user -JWT token OKTA

I am new to Okta so apologies if my questions are not clear.
So what I want to do is basically parse the JWT token generated by
okta and extract the group information of the logged in user
associated with it.
I am under the impression that this information should be there in the
OidcUser object. I do see user name/email id / token validity etc
information inside this object. Unfortunately I can't see group id
which I need for further processing.
#RequestMapping("/")
public String hello(#AuthenticationPrincipal OidcUser user){
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, Object> entry : user.getClaims().entrySet()) {
sb.append(entry.getKey() + ":" + entry.getValue().toString());
sb.append("\n");
}
sb.append("|");
sb.append(user.getClaims());
return sb.toString();
}
Here is my okta plugin inside spring boot
okta.oauth2.issuer=https://dev-XXXXXXXX.okta.com/oauth2/default
okta.oauth2.client-id=XXXXXXXXXX
okta.oauth2.client-secret=XXXXXXXXXXXX
I am wondering if my approach is proper and what more I need to do to extract User group from Okta JWT token.
To get user groups you need to make an additional request to /userinfo endpoint, assuming you requested groups scope during /authorize call.
Please see a guide here
Not exactly spring-boot response, but it's always beneficial to know how things work under-the-hood

Changing back the url to original if exception thrown and return back to form

I have a thymeleaf signup form, which if we submit then a controller at "/signup_do" is called which validates and saves the user to database:
<form action="/signup_do" method="post">
...
</form>
The controller at "/signup_do" passes the request to the accountRegistration service method, which does the validation:
#PostMapping("/signup_do")
public String register(Account account, HttpSession session) {
session.setAttribute("accountToRegister", account);
accountManagement.accountRegistration(account);
return "Success";
}
The account registration method can throw an exception SignupFormException, which is handled by the #ExceptionHandler defined in that controller class:
#ExceptionHandler(value=SignupFormException.class)
public String handle(HttpSession session, Model response) {
Account returnDataToForm = (Account) session.getAttribute("accountToRegister");
response.addAttribute("name", returnDataToForm.getFirstName());
session.invalidate();
return "signup";
}
Now the problem is that when exception occurs, the inputs entered in the form is passed back to the signup form, and the entered data remains intact, but the url still remains as /signup_do.
I have tried using return "redirect:/signup" instead, which does change the url, but it ends up making a get request to the /signup url like
/signup?name=John...
but my /signup controller is not designed to handle a get request, it just knows to display the form, so the information is lost.
#GetMapping("/signup")
public String signupPage() {return "signup";}
I also tried using forward:/signup, but that just ended up throwing 405 error.
I figured out a clean workaround a few hours after asking this question.
What I did is change the name of the controller that handles the signup process to ("/signup") as well. Since the controller that displays the page is a #GetMapping("/signup") and the one that handles the signup process is a #PostMapping("/signup") there is no clash.
Now even if the controller changes, the url remains the same, since both of them are signup...
#GetMapping("/signup")
public String signupPage() {return "signup";}
#PostMapping("/signup")
public String register(Account account, HttpSession session) {
session.setAttribute("accountToRegister", account);
accountManagement.accountRegistration(account);
return "success";
}
And this works just like I wanted!!
Redirecting will make a get request to the controller looking for the view to display, which in your situation means losing your data for the reasons you give. I can think of two workarounds:
Don't do the redirect and change the URL manually with javascript everytime you enter this view. If you dislike having a "wrong" URL in a view, editing it manually looks the most reasonable and direct approach. You can see how to do this here, including it in a script that executes everytime the page loads/the submit button is pressed.
Do the redirect and avoid losing your info by storing it in the session for a while longer, accessing it in thymeleaf in this way, instead of getting it from a model attribute. This would mean you would have to be careful to remove this session attributes later. It's also not very "clean" that your get request for the form view includes the user info, so I wouldn't go with this solution if avoidable.

Spring Security - Further Restricting Methods

I have an application that is authorizing an OAuth2 token with Spring Security. Using the #PreAuthorize tag, I can easily make sure that a user has a permission before allowing them to access a method:
#PreAuthorize("#oauth2.hasScope('account.read')")
public void getAccount(int accountId);
{
//return account
}
This works great at restricting users without the account.read permission from accessing this method.
The only problem is now any user with this permission can access any account. I want to restrict the users to only access their own account. I'm sure this is a common scenario. How do other applications deal with this?
So, the question here - how would system know if account belongs to user?
The answer would be that you are probably storing User<->Account relationship in the database. The most simple solution would be to do the check right in your method:
#PreAuthorize("#oauth2.hasScope('account.read')")
public Account getAccount(int accountId) {
// get account from db
Account account = repository.findById(accountId);
// you will need a little helper to get your User from
//Spring SecurityContextHolder or whatever there for oauth2
User user = securityManager.getCurrentUser();
if (account.belongs(user)) {
return account;
} else {
throw new UnathorizedException("User is not authorized to view account");
}
}
Upd. one of possible improvements may be to first get the user, get id from it and do a repository.findByIdAndUserId(accountId, userId) or somthing like that. (or even repositoryFindByIdAndUser(accountId, user))

Update Facebook profile with Spring Social

I'm using Facebook login on my site with Spring Social. I'm also creating an user in my database with specific application preferences for each Facebook user. After a user logs in, I want to update his personal information, like name and email, in my db. What will be the best way to do it and how?
I finally did it by implementing ApplicationListener<InteractiveAuthenticationSuccessEvent>, which is called after the user logs in.
#Override
public void onApplicationEvent(InteractiveAuthenticationSuccessEvent authEvent) {
Authentication auth = authEvent.getAuthentication();
if (auth instanceof SocialAuthenticationToken && auth.getPrincipal() instanceof User) {
// every time a user authenticates through a social network, then we update his info from there
User user = (User)auth.getPrincipal();
// ... update user using ((SocialAuthenticationToken)auth).getConnection()
// ... and save it
}
}
There's also possible to do it by overriding ConnectionRepository#updateConnection.

Resources