There are 3 classes in my Spring MVC app: a UserDetailsInterceptor class, an MyAdvice class and a UserDetails class (session scoped).
What I want to accomplish is simple:
UserDetailsInterceptor intercepts requests and set user's id in a session scoped UserDetails bean.
Later on, when the method in AOP advice class is called, retrieve user's id from the session scoped UserDetails bean.
Problem (also marked in the code below):
UserDetails object is null in MyAdvice class.
In UserDetailsInterceptor, userDetails.setUserID(request.getRemoteUser()); does nothing.
Code:
UserDetailsInterceptor class:
public class UserDetailsInterceptor extends HandlerInterceptorAdapter {
#Autowired
private UserDetails userDetails;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//set user ID, but next line doesn't do anything for some reason (e.g. `userID` is still null)
userDetails.setUserID(request.getRemoteUser());
return true;
}
}
MyAdvice class:
public class MyAdvice implements MethodInterceptor {
#Autowired
private UserDetails userDetails; //It's null
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
//Print user ID
System.out.println(userDetails.getID());
return invocation.proceed();
}
}
UserDetails class:
public class UserDetails {
private String userID;
public void setUserID(String userID) {
this.userID= userID;
}
public String getUserID() {
return this.userID;
}
}
In dispatcher-servlet.xml:
<bean id="userDetails " class="package.UserDetails " scope="session">
<aop:scoped-proxy/>
</bean>
MyPointcutAdvisor class:
public class MyPointcutAdvisor implements PointcutAdvisor {
private MyPointcut pointcut = new MyPointcut();
private MyAdvice advice = new MyAdvice();
#Override
public Pointcut getPointcut() {
return this.pointcut;
}
#Override
public Advice getAdvice() {
return this.advice;
}
#Override
public boolean isPerInstance() {
return false;
}
}
Any ideas please? Thanks in advance.
Update:
By registering MyAdvice class, userDetails object in it is no longer null. However it is not the same object as the one in UserDetailsInterceptor. So the bean is not actually "session scoped"?
Answer:
The problem lies in following code:
private MyPointcut pointcut = new MyPointcut();
private MyAdvice advice = new MyAdvice();
Neither of them are managed by spring. As a result, things are being wired and not working the way we expected.
This
UserDetails object is null in MyAdvice class.
is not possible if the MyAdvice instance is managed by Spring. You must be instantiating it yourself instead of getting it from the context.
If Spring doesn't manage the object, it can't inject anything into #Autowired targets, so your field remains null.
If Spring was managing your object, a bean, and couldn't resolve the dependency, it would throw exceptions.
Related
I have a standard Spring MVC web application, and I want to add a custom annotation that takes a parameter and the HttpRequest.
Now I know that annotation parameters are resolved at compile time, but how does Spring security get access to the session and user and stuff with #PreAuth and stuff...
I am specifically looking to get the HttpRequest.
Ideas how to work around this?
U should implement PermissionEvaluator.
public class CustomPermissionEvaluator implements PermissionEvaluator {
public boolean hasPermission(Authentication authentication, Object target,
Object permission) {
return // you logic;
}
public boolean hasPermission(Authentication authentication, Serializable targetId,String targetType, Object permission) {
return // you logic;
}
}
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(new CustomPermissionEvaluator());
return expressionHandler;
}
#PreAuthorize("hasPermission(#foo, 'YOU_CUSTOM_PARAM')")
#PostMapping(value = "/pay")
public Foo create(Foo foo) {
return foo;
}
I've just created an aspect to validate an input parameter in my login method. I have a service implementing UserDetailsService interface in spring security (Spring 4.2.1.RELEASE). My aspect is very simple for now, just calling jointPoint.proceed(), not validating input parameter yet:
#Aspect
public class LoginAspect {
#Around(value="#annotation(LoginAnnotation)")
public void loadByUserNameLoginValidation(ProceedingJoinPoint joinPoint) throws Throwable {
joinPoint.proceed();
}
}
My service:
#LoginAnnotation
public class MyUserDetailService implements UserDetailsService {
//Retrieve User Details from database
#LoginAnnotation
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
//Retrieve user details code
//.....
//.....
return userDetails;
}
}
Debugging the code, I've found that loadedUser is null in Spring's DaoAuthenticationProvider.retrieveUser method:
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
The moment I remove my custom annotation #LoginAnnotation from my service, everything works perfectly. What I am missing here?
Yes, that was it:
#Around(value="#annotation(LoginAnnotation)")
public Object loadByUserNameLoginValidation(ProceedingJoinPoint joinPoint) throws Throwable {
return joinPoint.proceed();
}
the result has to be returned from the aspect or it will be null.
I have a Wicket Session class as follows
public class IASession extends AuthenticatedWebSession {
private static final long serialVersionUID = 3529263965780210677L;
#SpringBean
private UserService userService;
public IASession(Request request) {
super(request);
}
#Override
public boolean authenticate(String username, String password) {
// Get the user
UserDetailsDTO user = userService.findByEmail(username);
if(null != user && user.getPassword().equals(password))
return true;
else
return false;
}
#Override
public Roles getRoles() {
Roles roles = new Roles();
roles.add("SIGNED_IN");
return roles;
}
}
In this class, I am trying to autowire Spring service using wicket-spring annnotation #SpringBean. But when I am trying to login, it giving me error.
Last cause: null
WicketMessage: Method onFormSubmitted of interface org.apache.wicket.markup.html.form.IFormSubmitListener targeted at [StatelessForm [Component id = login-form]] on component [StatelessForm [Component id = login-form]] threw an exception
Wicket is unable to autowire the userService spring bean and that is why it's null.
What can I do to fix this?
Since the Session is not a Component or Behavior you'll have to overwrite the constructor and call Injector.get.inject(this). See the SpringComponentInjector doc.
public IASession(Request request) {
super(request);
Injector.get().inject(this);
}
I'm working on spring mvc application, where I should aplly validation based on Spring MVC validator. I first step for that I added annotation for class and setup controller and it works fine. And now I need to implement custom validator for perform complex logic, but i want to use existing annotation and just add additional checking.
My User class:
public class User
{
#NotEmpty
private String name;
#NotEmpty
private String login; // should be unique
}
My validator:
#Component
public class UserValidator implements Validator
{
#Autowired
private UserDAO userDAO;
#Override
public boolean supports(Class<?> clazz)
{
return User.class.equals(clazz) || UsersForm.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors)
{
/*
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "NotEmpty.user");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "login", "NotEmpty.user");
*/
User user = (User) target;
if (userDAO.getUserByLogin(user.getLogin()) != null) {
errors.rejectValue("login", "NonUniq.user");
}
}
}
My controller:
#Controller
public class UserController
{
#Autowired
private UserValidator validator;
#InitBinder
protected void initBinder(final WebDataBinder binder)
{
binder.setValidator(validator);
}
#RequestMapping(value = "/save")
public ModelAndView save(#Valid #ModelAttribute("user") final User user,
BindingResult result) throws Exception
{
if (result.hasErrors())
{
// handle error
} else
{
//save user
}
}
}
So, Is it possible to use custom validator and annotation together? And if yes how?
I know this is a kind of old question but, for googlers...
you should use addValidators instead of setValidator. Like following:
#InitBinder
protected void initBinder(final WebDataBinder binder) {
binder.addValidators(yourCustomValidator, anotherValidatorOfYours);
}
PS: addValidators accepts multiple parameters (ellipsis)
if you checkout the source of org.springframework.validation.DataBinder you will see:
public class DataBinder implements PropertyEditorRegistry, TypeConverter {
....
public void setValidator(Validator validator) {
assertValidators(validator);
this.validators.clear();
this.validators.add(validator);
}
public void addValidators(Validator... validators) {
assertValidators(validators);
this.validators.addAll(Arrays.asList(validators));
}
....
}
as you see setValidator clears existing (default) validator so #Valid annotation won't work as expected.
If I correctly understand your problem, as soon as you use you custom validator, default validation for #NotEmpty annotation no longer occurs. That is common when using spring : if you override a functionnality given by default, you have to call it explicitely.
You have to generate a LocalValidatorFactoryBean and inject it with your message source (if any). Then you inject that basic validator in you custom validator and delegate annotation validation to it.
Using java configuration it could look like :
#Configuration
public class ValidatorConfig {
#Autowired
private MessageSource messageSource;
#Bean
public Validator basicValidator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(messageSource);
return validator;
}
}
Then you modify UserValidator to use it :
#Component
public class UserValidator implements Validator
{
#Autowired
#Qualifier("basicValidator")
private Validator basicValidator;
#Autowired
private UserDAO userDAO;
// ...
#Override
public void validate(Object target, Errors errors)
{
basicValidator.validate(target, errors);
// eventually stop if any errors
// if (errors.hasErrors()) { return; }
User user = (User) target;
if (userDAO.getUserByLogin(user.getLogin()) != null) {
errors.rejectValue("login", "NonUniq.user");
}
}
}
Well for me you have to delete the
#InitBinder
protected void initBinder(final WebDataBinder binder)
{
binder.setValidator(validator);
}
Leave the
#Valid #ModelAttribute("user") final User user,
BindingResult result
And after in the function make
validator.validate(user,result)
This way you will use the validation basic with the #Valid and after you will put make the more complex validation.
Because with the initBinder you are setting the validation with your complex logic and putting a way the basic logic.
Maybe is wrong, i use always the #Valid without any validator.
Dao
#Repository
public interface LoginDao extends JpaRepository<Login, Integer> {
Login findByLogin(String login);
}
Validator
#Component
public class PasswordChangeValidator implements Validator {
private LoginDao loginDao;
#Override
public boolean supports(Class<?> aClass) {
return PasswordChange.class.equals(aClass);
}
#Override
public void validate(Object o, Errors errors) {
PasswordChange passwordChange = (PasswordChange) o;
**// There is a null pointer here because loginDao is null**
Login login = loginDao.findByLogin(passwordChange.getLoginKey());
}
public LoginDao getLoginDao() {
return loginDao;
}
#Autowired
public void setLoginDao(LoginDao loginDao) {
**// There is a debug point on the next line and it's hit on server startup and I can
// see the parameter us non-null**
this.loginDao = loginDao;
}
}
Controller
#Controller
#RequestMapping("api")
public class PasswordController {
#Autowired
PasswordService passwordService;
#InitBinder("passwordChange")
public void initBinder(WebDataBinder webDataBinder, WebRequest webRequest) {
webDataBinder.setValidator(new PasswordChangeValidator());
}
#RequestMapping(value = "/passwordChange", method = RequestMethod.POST)
public #ResponseBody PasswordInfo passwordInfo(#RequestBody #Valid PasswordChange passwordChange)
throws PasswordChangeException {
return passwordService.changePassword(passwordChange.getLoginKey(), passwordChange.getOldPassword(), passwordChange.getNewPassword());
}
}
I have the Dao listed above. This same dao bean gets injected in an #Service annotated class but not in #Component annotated Validator class. Well, not exactly the upon server startup I can see that the setter method gets called, but when I try to use this variable in a method the variable shows as null.
Does anybody see a problem with my configuration ? Please note that the loginDao bean gets injected into a service class, so the Context configuration is good.
Well there's your problem
webDataBinder.setValidator(new PasswordChangeValidator());
Spring can only manage beans it created. Here, you're creating the instance. Instead inject your bean into the #Controller and use it.
#Inject
private PasswordChangeValidator passwordChangeValidator;
...
webDataBinder.setValidator(passwordChangeValidator);