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.
Related
I have a entity model called User. I have implemented UserDetailsService and used it in WebSecurityConfig like this. Removed unnecessary code for brevity.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Now at any place during a request-response, I can use this code to get the currently logged in user
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
// now use some jpa logic to retrive User entity using above username
Now my question is, is there any way to create a custom AOP or annotation that gives me access to logged in user so that I can use it anywhere like in my controller as shown below?
public class LoginController {
public String response(#CurrentUser User user) {
// do logic once you have access to logged in user with help of #CurrentUser annotation
}
}
So like in the above example, can I create an annotation #CurrentUser that gives me currently logged in user(if not throws an exception, which I can catch in controller advice)?
Note: How I get the user is up to me. In the above example I am using authentcation.getName() and then querying my db to build User entity. But I can use any other way to do so. I just want to create an annotation like CurrentUser and then add the code (of retrieving the user) behind it.
Add the following bean:
(change UserDetail if you are using a different entity for the principal).
#Bean
public Function<UserDetails, User> fetchUser() {
return (principal -> {
String name = principal.getUsername()
//do JPA logic
return ...
});
}
Then set-up the #CurrentUser annotation as followed:
#Retention(RetentionPolicy.RUNTIME)
#Inherited
#Documented
#AuthenticationPrincipal(expression = "#fetchUser.apply(#this)", errorOnInvalidType=true)
public #interface CurrentUser {}
I'm building a Spring Boot application to provide a stateless REST API. For security, we're using OAuth 2. My app receives a bearer-only token.
The user's information is stored in our database. I can look it up using the injected Principal in the controller:
#RequestMapping(...)
public void endpoint(Principal p) {
MyUser user = this.myUserRepository.findById(p.getName());
...
}
To avoid this extra line of boilerplate, I would like to be able to inject the MyUser object directly into my controller method. How can I achieve this? (The best I've come up with so far is to create a Lazy, Request-scoped #Bean...but I haven't been able to get it working...)
The Idiomatic Way
The idiomatic way in Spring Security is to use a UserDetailsService or implement your own:
public class MyUserDetailsService implements UserDetailsService {
#Autowired
MyUserRepository myUserRepository;
public UserDetails loadUserByUsername(String username) {
return this.myUserRepository.findById(username);
}
}
And then there are several spots in the Spring Security DSL where this can be deposited, depending on your needs.
Once integrated with the authentication method you are using (in this case OAuth 2.0), then you'd be able to do:
public void endpoint(#AuthenticationPrincipal MyUser myuser) {
}
The Quick, but Less-Flexible Way
It's generally better to do this at authentication time (when the Principal is being ascertained) instead of at method-resolution time (using an argument resolver) as it makes it possible to use it in more authentication scenarios.
That said, you could also use the #AuthenticationPrincipal argument resolver with any bean that you have registered, e.g.
public void endpoint(
#AuthenticationPrincipal(expression="#myBean.convert(#this)") MyUser user)
{
}
...
#Bean
public Converter<Principal, MyUser> myBean() {
return principal -> this.myUserRepository.findById(p.getName())
}
The tradeoff is that this conversion will be performed each time this method is invoked. Since your app is stateless, this might not be an issue (since the lookup needs to be performed on each request anyway), but it would mean that this controller could likely not be reused in other application profiles.
You can achieve this by implementing HandlerMethodArgumentResolver.
For example:
Custom annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface Version {
}
Implementation:
public class HeaderVersionArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterAnnotation(Version.class) != null;
}
#Override
public Object resolveArgument(
MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
HttpServletRequest request
= (HttpServletRequest) nativeWebRequest.getNativeRequest();
return request.getHeader("Version");
}
}
When you implement this you should add this as argument resolver:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addArgumentResolvers(
List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new HeaderVersionArgumentResolver());
}
}
Now we can use it as argument
public ResponseEntity findByVersion(#PathVariable Long id, #Version String version)
I want to check if user exists in ControllerAdvice and treat user as #ModelAttribute if user exists. On the other hand, I also want to access user object in #Controller directly. So I add #ModelAttribute annotation on the parameter of #RequestMapping method.
I'm using #ControllerAdvice like:
#ControllerAdvice
public class UserAdvice {
#Autowired
private UserService userService;
#ModelAttribute("user")
public User user(#PathVariable("username") String username) {
User user = userService.findByUsername(username);
if (user != null) {
return user;
}
user = userService.findById(username);
if (user == null) {
throw new ResourceNotFoundException("user not found");
}
return user;
}
}
And UserController Like:
#RestController
#RequestMapping("/users/{username}")
public class UserController {
public static final Logger logger = LoggerFactory.getLogger(UserCourseListController.class);
#Autowired
private CourseService courseService;
#RequestMapping(value = "", method = RequestMethod.GET)
public void getUser(#ModelAttribute("user") User user, Model model) {
logger.info("{}", user);//user is null
logger.info("{}", model.asMap().get("user"));// not null
}
}
But now, the parameter user that annotated with #ModelAttribute is null while there is a "user" obj in Model Map.
Is there any mistakes I've made in this scenario? Or any misunderstanding of the concepts of #ModelAttribute and #ControllerAdvice?
Thanks very much!
Update
From Docs of Springframework:
Once present in the model, the argument’s fields should be populated from all request parameters that have matching names.
So We cannot add #ModelAttribute to method parameters annotated by #RequestMapping directly because Spring will do data binding from request(not Model)。
Finally I found a solution——HandlerMethodArgumentResolver. It can resolve method arguments on each #RequestMapping method and do some work on resolving arguments. An example of Java Config is below:
public class Config extends WebMvcConfigurerAdapter {
#Bean(name = "auditorBean")
public AuditorAware<User> auditorAwareBean() {
return () -> null;
}
#Bean
public HttpMessageConverters customConverters() {
return new HttpMessageConverters(new MappingJackson2HttpMessageConverter());
}
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new HandlerMethodArgumentResolver() {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(User.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return mavContainer.getDefaultModel().get(parameter.getParameterName());
}
});
}
}
We resolve method arguments from model via parameter.getParameterName(). It mean that the name of method argument(user) must be equal to the value of #ModelAttrubute defined in #ControllerAdvice. You can also use any other naming conventions to implement the binding.
I am newbie to spring and I face problem in Transaction.
I have created two Model as below:
UserDto - Stored user information
RoleDto - Stored user's role information
Service respected to both models are as below (both are annotated with #Transactional):
UserService - void saveUser(UserDto userDto) throws Exception;
RoleService - void saveRole(RoleDto roleDto) throws Exception;
now when user create account in the application at that time I called "add" method of the controller, which has the code snippet as below:
userService.saveUser(userDto);
roleService.saveRole(roleDto);
now in this code if exception occurred in Roleservice than it still insert user data into database table. but I want to rollback that too if roleService throws any exception. I tried to find the solution but could not get any good tutorial. any help will be appreciated.
The way you're calling the method makes each of it has its own transaction. Your code can be understood like this:
Transaction t1 = Spring.createTransaction();
t1.begin();
try {
//your first service method marked as #Transactional
userService.saveUser(userDto);
t1.commit();
} catch (Throwable t) {
t1.rollback();
} finally {
t1.close();
}
Transaction t2 = Spring.createTransaction();
t2.begin();
try {
//your second service method marked as #Transactional
roleService.saveRole(roleDto);
t2.commit();
} catch (Throwable t) {
t2.rollback();
} finally {
t2.close();
}
An option to solve this would be to create another service class where its implementation has RoleService and UserService injected, is marked as #Transactional and calls these two methods. In this way, both methods will share the same transaction used in this class:
public interface UserRoleService {
void saveUser(UserDto userDto, RoleDto roleDto);
}
#Service
#Transactional
public class UserRoleServiceImpl implements UserRoleService {
#Autowired
UserService userService;
#Autowired
RoleService roleService;
#Override
public void saveUser(UserDto userDto, RoleDto roleDto) {
userService.saveUser(userDto);
roleService.saveRole(roleDto);
}
}
A better design would be to make RoleDto a field of USerDto and that implementation of USerService has a RoleService field injected and perform the necessary calls to save each. Note that service classes must provide methods that contains business logic, this also means business logic rules. Service classes are not just wrappers for Dao classes.
This could be an implementation of the above explanation:
#Service
public class UserServiceImpl implements UserService {
#Autowired
RoleService roleService;
public void saveUSer(UserDto userDto) {
//code to save your userDto...
roleService.saveRole(userDto.getRoleDto());
}
}
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.