What I'm trying to accomplish is to access my Spring Session within a custom constraint
Sample scenario:
Custom constraint #UniqueEmail verifies that the email is not already in use in the system. This validation is performed on the edit endpoint and should be ignore if the user didn't change the email, 1st: because there's no need for it and 2nd: because querying the db for that email will actually return a result, which is the user itself, although there's no way of telling it without accessing the session
This works:
If I use model attributes with custom editor though #InitBinder I can set a property in the to-be-validated bean before the validation occurs like so
#InitBinder(value="myModelObj")
protected void initBinder(WebDataBinder binder, HttpSession session) {
User user = (User) session.getAttribute("user");
binder.registerCustomEditor(User.class, "user", new UidPropertyEditor(user));
}
#RequestMapping(...)
public String updateUser(#Valid #ModelAttribute("myModelObj") MyModelObj form){
...
}
MyModelObj has an attribute which will be replaced with the actual session user. Problems:
There must be a property in the bean to hold the user, even though it is not editable through the web form
The web form must submit this property as well, in my case using an input[type="hidden"] field (user can change it at will, we never trust what the user sends)
This does not work
The new endpoints have to use #RequestBody rather than #ModelAttribute, which means that (afaik) #InitBinder won't work anymore, hence losing access to the session object.
How (if possible) can I access the session from within the custom constraint?
public class EmailIsUniqueStringValidator implements ConstraintValidator<EmailIsUnique, String> {
#Autowired
private UserDAO userDAO;
HttpSession session; //Somehow initialized
#Override
public void boolean isValid(String email, ConstraintValidatorContext context) {
User user = (User) session.getAttribute("user");
if(user.getEmail().equals(email)){
return true; // No need to validate
}
else if(userDAO.emailInUse(email)) {
return false;
}
}
Non-ideal approach:
What I'm doing now is performing the session-dependant validations in the controller manually, which means I have 2 points where validation is performed.
There are some other interesting options in this post too, but if there was a way to access the session...
Thanks in advance
This can be achieved using RequestContextHolder like so:
public class EmailIsUniqueStringValidator implements ConstraintValidator<EmailIsUnique, String> {
#Autowired
private UserDAO userDAO;
HttpSession session;
#Override
public void boolean isValid(String email, ConstraintValidatorContext context) {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
session = attr.getRequest().getSession();
User user = (User) session.getAttribute("user");
if(user.getEmail().equals(email)){
return true; // No need to validate
}
else if(userDAO.emailInUse(email)) {
return false;
}
}
Related
I have two web applications (A and B).
The first web application (A) is used as a reverse proxy using spring-cloud.
I'm using spring-session to store the sessions in a redis database for both applications.
The problem
When I modify a field (e.g name) of the current (logged in) user, the current logged in user object is not updated immediately and as a result, when I'm trying to retrieve current logged in user in a next call (via #AuthenticationPrincipal) I get a non-updated user object.
My custom user details object:
public class CustomUserDetails extends my.package.User implements org.springframework.security.core.userdetails.UserDetails, java.io.Serializable {
// ...
}
How can I update the current user object immediately?
Recently I've had the similar issue and I resolved it in the following manner:
1.Created a custom Authentication class
public class MyCustomAuthentication implements Authentication {
private UserDetails userDetails;
public MyCustomAuthentication(UserDetails userDetails) {
this.userDetails = userDetails;
}
...
#Override
public Object getDetails() { return userDetails; }
#Override
public Object getPrincipal() { return userDetails; }
#Override
public boolean isAuthenticated() { return true; }
...
}
update userDetails object with some fresh data (I guess, 'name' in your case)
Set new authentication created from userDetails in SecurityContextHolder
SecurityContextHolder.getContext().setAuthentication(new MyCustomAuthentication(userDetails));
Hope you will find that helpful.
I've Spring cache implemented as below
#Component
public class KPCacheExample {
private static final Logger LOG = LoggerFactory.getLogger(KPCacheExample.class);
#CachePut(value="kpCache")
public String saveCache(String userName, String password){
LOG.info("Called saveCache");
return userName;
}
#Cacheable(value="kpCache")
public String getCache(String userName, String password){
LOG.info("Called getCache");
return "kp";
}
}
And Java Config file
#Configuration
#ComponentScan(basePackages={"com.kp"})
public class GuavaCacheConfiguration {
#Bean
public CacheManager cacheManager() {
GuavaCacheManager guavaCacheManager = new GuavaCacheManager("kpCache");
guavaCacheManager.setCacheBuilder(CacheBuilder.newBuilder().expireAfterAccess(2000, TimeUnit.MILLISECONDS).removalListener(new KPRemovalListener()));
return guavaCacheManager;
}
}
By default the spring uses put method in the cache interface to update/put values in the cache. How can I force the spring to use putifabsent method to be invoked, such that I can get null value if cache is missed or in other wards first request to the method with unique username and password should return null and subsequent request to that username and password should return username.
Well, looking through Spring's Cache Abstraction source, there does not appear to be a configuration setting (switch) to default the #CachePut to use the "atomic" putIfAbsent operation.
You might be able to simulate the "putIfAbsent" using the unless (or condition) attribute(s) of the #CachePut annotation, something like (based on the Guava impl)...
#CachePut(value="Users", key="#user.name" unless="#root.caches[0].getIfPresent(#user.name) != null")
public User save(User user){
return userRepo.save(user);
}
Also note, I did not test this expression, and it would not be "atomic" or portable using a different Cache impl. The expression ("#root.caches[0].get(#user.name) != null") maybe more portable.
Giving up the "atomic" property may not be desirable so you could also extend the (Guava)CacheManager to return a "custom" Cache (based on GuavaCache) that overrides the put operation to delegate to "putIfAbsent" instead...
class CustomGuavaCache extends GuavaCache {
CustomGuavaCache(String name, com.google.common.cache.Cache<Object, Object> cache, boolean allowNullValues) {
super(name, cache, allowNullValues);
}
#Override
public void put(Object key, Object value) {
putIfAbsent(key, value);
}
}
See the GuavaCache class for more details.
Then...
class CustomGuavaCacheManager extends GuavaCacheManager {
#Override
protected Cache createGuavaCache(String name) {
return new CustomGuavaCache(name, createNativeGuavaCache(name), isAllowNullValues());
}
}
See GuavaCacheManager for further details, and specifically, have a look at line 93 and createGuavaCache(String name).
Hope this helps, or at least gives you some more ideas.
For a login page, I have an authentication method as:
#Component(value = "customSpringAuthentication")
public class CustomSpringAuthentication implements AuthenticationProvider {
#SuppressWarnings({ "serial", "deprecation" })
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
...
return authUser;
}
}
Also I have a bean, which is called after authentication:
#Component(value = "loggedinUserBean")
#Scope("session")
public class LoggedinUserBean {
private AuthUser authUser;
private boolean isAdminUser = false;
#PostConstruct
public void initModel() {
....
authUser = (AuthUser) SecurityContextHolder.getContext().getAuthentication();
....
}
}
My question is when I am trying to access "authUser" in initmodel()method, it is null.
I know that authenticate method did not return null. But somewhat I realized that initmodel() works few miliseconds before authenticate returns. So that it can't get authetication object properly. How can I ensure/define ordering that without authenticate() returns loggedinuser is not initalized?
To actually answer your question, the initModel() method will be run immediately after your LoggedinUserBean object is created. This doesn't mean that its initialised. Initialisation and creation are two separate things. When an object is created, the JVM allocates memory for that object and all its fields. All the fields are null because they don't have values but the memory is set aside for you to populate the fields. Initialisation means that authUser will not be null because you have given it a value and you will only give it a value when authenticate() is called. As long as you aren't setting authUser anywhere else, then you are fine.
TL;DR: Its already guaranteed by Spring Security that your authUser is null until the user successfully authenticates using one of your authentication providers. If authUser remains null after then entire springSecurityFilterChain has been tried then the login attempt has failed.
I'm adding a user validator using the initBinder method:
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new UserValidator());
}
Here is the UserValidator
public class UserValidator implements Validator {
public boolean supports(Class clazz) {
return User.class.equals(clazz);
}
public void validate(Object target, Errors errors) {
User u = (User) target;
// more code here
}
}
The validate method is getting properly called during the controller method call.
#RequestMapping(value = "/makePayment", method = RequestMethod.POST)
public String saveUserInformation(#Valid User user, BindingResult result, Model model){
// saving User here
// Preparing CustomerPayment object for the payment page.
CustomerPayment customerPayment = new CustomerPayment();
customerPayment.setPackageTb(packageTb);
model.addAttribute(customerPayment);
logger.debug("Redirecting to Payment page.");
return "registration/payment";
}
But while returning to the payment screen I'm getting this error:
java.lang.IllegalStateException: Invalid target for Validator [com.validator.UserValidator#710db357]: com.domain.CustomerPayment[ customerPaymentId=null ]
org.springframework.validation.DataBinder.setValidator(DataBinder.java:476)
com.web.UserRegistrationController.initBinder(UserRegistrationController.java:43)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
java.lang.reflect.Method.invoke(Method.java:597)
org.springframework.web.bind.annotation.support.HandlerMethodInvoker.initBinder(HandlerMethodInvoker.java:393)
org.springframework.web.bind.annotation.support.HandlerMethodInvoker.updateModelAttributes(HandlerMethodInvoker.java:222)
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:429)
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:414)
This might be because I'm returning a CustomerPayment and there is not validator defined for that.
I'm also not able to add multiple validators in initBinder method.
How can I fix this?
You need to set the value of the #InitBinder annotation to the name of the command you want it to validate. This tells Spring what to apply the binder to; without it, Spring will try to apply it to everything. This is why you're seeing that exception: Spring is trying to apply the binder - with your UserValidator - to a parameter of type CustomerPayment.
In your specific case, it looks like you need something like:
#InitBinder("user")
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new UserValidator());
}
To your second question, as Rigg802 explained, Spring does not support attaching multiple validators to a single command. You can, however, define multiple #InitBinder methods for different commands. So, for example, you could put the following in a single controller and validate your user and payment parameters:
#InitBinder("user")
protected void initUserBinder(WebDataBinder binder) {
binder.setValidator(new UserValidator());
}
#InitBinder("customerPayment")
protected void initPaymentBinder(WebDataBinder binder) {
binder.setValidator(new CustomerPaymentValidator());
}
It's a bit tricky to do, 1 controller has only 1 validator on 1 command object.
you need to create a "Composite Validator" that will get all the validators and run them seperately.
Here is a tutorial that explains how to do it: using multiple validators
You can add multiple validators by iterating over all org.springframework.validation.Validator in an ApplicationContext and set up suitable ones in #InitBinder for each request.
#InitBinder
public void setUpValidators(WebDataBinder webDataBinder) {
for (Validator validator : validators) {
if (validator.supports(webDataBinder.getTarget().getClass())
&& !validator.getClass().getName().contains("org.springframework"))
webDataBinder.addValidators(validator);
}
}
See my project for examples and simple benchmarks. https://github.com/LyashenkoGS/spring-mvc-and-jms-validation-POC/tree/benchamark
I do not see a reason why Spring does not filter out all validators which are not applicable to the current entity by default which forces to use things like CompoundValidator described by #Rigg802.
InitBinder allows you to specify name only which give you some control but not full control over how and when to apply your custom validator. Which from my perspective is not enough.
Another thing you can do is to perform check yourself and add validator to binder only if it is actually necessary, since binder itself has binding context information.
For example if you want to add a new validator which will work with your User object in addition to built-in validators you can write something like this:
#InitBinder
protected void initBinder(WebDataBinder binder) {
Optional.ofNullable(binder.getTarget())
.filter((notNullBinder) -> User.class.equals(notNullBinder.getClass()))
.ifPresent(o -> binder.addValidators(new UserValidator()));
}
There is a simple hack, always return true in supports method, and delegate the class checking to validate. Then basically you can add multiple validator in the initBinder without issue.
#Component
public class MerchantRegisterValidator implements Validator {
#Autowired
private MerchantUserService merchantUserService;
#Autowired
private MerchantCompanyService merchantCompanyService;
#Override
public boolean supports(Class<?> clazz) {
return true; // always true
}
#Override
public void validate(Object target, Errors errors) {
if (!RegisterForm.getClass().equals(target.getClass()))
return; // do checking here.
RegisterForm registerForm = (RegisterForm) target;
MerchantUser merchantUser = merchantUserService.getUserByEmail(registerForm.getUserEmail());
if (merchantUser != null) {
errors.reject("xxx");
}
MerchantCompany merchantCompany = merchantCompanyService.getByRegno(registerForm.getRegno());
if (merchantCompany != null) {
errors.reject("xxx");
}
}
}
Multiple validator on one command is supported with Spring MVC 4.x now. You could use this snippet code:
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new UserValidator(), new CustomerPaymentValidator());
}
The safest way is to add a generic validator handling that Controller:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setValidator(new GenericControllerOneValidator());
}
Then, in the generic validator you can support multiple request body models and based of the instance of the object, you can invoke the appropriate validator:
public class GenericValidator implements Validator {
#Override
public boolean supports(Class<?> aClass) {
return ModelRequestOne.class.equals(aClass)
|| ModelRequestTwo.class.equals(aClass);
}
#Override
public void validate(Object body, Errors errors) {
if (body instanceof ModelRequestOne) {
ValidationUtils.invokeValidator(new ModelRequestOneValidator(), body, errors);
}
if (body instanceof ModelRequestTwo) {
ValidationUtils.invokeValidator(new ModelRequestTwoValidator(), body, errors);
}
}
}
Then you add your custom validations inside for each model validator implementatios. ModeRequestOneValidator and ModeRequestTwoValidator still need to implement the Validator interface of org.springframework.validation
Also, do not forget to use #Valid ModeRequestOne and #Valid ModeRequestTwo inside the controllers method call.
One addition to Annabelle's answer:
If controller has this method parameter and you want to validate that one specifically
#RequestMapping(value = "/users", method = RequestMethod.POST)
public String findUsers(UserRequest request){..}
Then the binding should be lower case of the class name (but just the first letter, and not everything else)
#InitBinder("userRequest")
protected void initUserBinder(WebDataBinder binder) {
binder.setValidator(new YourValidator());
}
Declare request as
(... , Model model,HttpServletRequest request)
and change
model.addAttribute(customerPayment);
to
request.setAttribute("customerPayment",customerPayment);
I am using spring security for the authentication purposes in my project wherein after successful authentication, I get the principal object inside which the various details are stored.
This principal object is passed to various methods which allow the entries to be reflected in the database against the current user. In short, principal helps me in giving principal.getName() everywhere i need it.
But now when I login through spring social then I do not have principal object of Principal in hand, instead I have implemented MyPrincipal class --->
public class MyPrincipal implements Principal {
public String name;
public boolean flag;
public boolean isflag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public void setName(String name) {
this.name = name;
}
#Override
public String getName() {
return name;
}
}
Then in the social login handler, I am adding the current username and flag value to myPrincipal object, and forwarding the user to the same home page where the spring security forwards in case of normal login.
MyPrincipal myPrincipal = new MyPrincipal();
myPrincipal.name = username;
myPrincipal.socialFlag = true;
modelMap.addAttribute("myPrincipal", myPrincipal);
return new ModelAndView("forward:/home");
Adding this object in session by annotating class with
#SessionAttributes({"myPrincipal"})
Now from here on-wards I want the flow to be handed over to the home page with all the functionality working for the user correctly. But each method is taking Principal principal as argument, just like this -->
#RequestMapping(value = {"/home"}, method = RequestMethod.POST)
#ResponseBody
public ModelAndView test(ModelMap modelMap, Principal principal) {
String name = principal.getName();
}
There are two different things going around in both cases-
Normal login is giving me principal directly but social login is giving me it in session attributes.
I do not want to pass principal as parameters even in case of normal spring security login, instead here also I want to put it in session attribute.
How can I do this and where to make the changes when I have implemented my own authentication provider.
I don't think I fully understand...However, in general it shouldn't be necessary to pass principal instances around. Use org.springframework.security.core.context.SecurityContextHolder.getContext() to get a hold of the context then call SecurityContext.getAuthentication().getPrincipal() or SecurityContext.getAuthentication().getDetails().