How to pass attribute to Spring Controller in a stateless application? - spring

I am currently implementing a SAML SSO solution in my application where in my SAMLUserDetailsService, I am loading my user
#Service
public class SAMLUserDetailsServiceImpl implements SAMLUserDetailsService {
#Override
public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException {
return new User(credential.getNameID().getValue());
}
}
I am then using a SavedRequestAwareAuthenticationSuccessHandler to redirect user to a landing controller upon successful authentication.
#Bean
public AuthenticationSuccessHandler successRedirectHandler() {
SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler =
new SavedRequestAwareAuthenticationSuccessHandler();
successRedirectHandler.setDefaultTargetUrl("/landing");
return successRedirectHandler;
}
Controller:
#RequestMapping("/landing")
public ResponseEntity landing(User user) {
return ResponseEntity.ok(user.getLoginName());
}
Is there a way to pass the User object to my controller. I noticed that this is usually done using a HandlerMethodArgumentResolver but since my application is stateless and does not use sessions, is there a way to achieve this using another way please?

You don't need injection for this. Use following instead:
SecurityContextHolder.getContext().getAuthentication().getName()
or
SecurityContextHolder.getContext().getAuthentication().getPrincipal()
In the latter case check the type of what getPrincipal() returned. It can be String, it can be UserDetails. If latter, cast it to UserDetails and call getUsername().

Related

Idiomatic way to register custom Authentication in Spring Security

My use case is that I'm using Spring Security 5.2's Oauth2 login, but would like my database user class to be available alongside the Oauth2AuthenticationToken within the Authentication. This is so that I have my database user class cached by the SecurityContextHolder.
In Pseudocode:
A user logs in using Google or Github Oauth2
My app finds (or creates) the database user with the information returned
My app saves to the SecurityContextHolder a custom Authentication wrapper that wraps both the Oauth2AuthenticationToken and the database User class
On subsequent requests, the custom Authentication wrapper is available to controller methods
Here are my attempts at a wrapper:
class MyAuthenticationWrapper implements Authentication {
public MyAuthenticationWrapper(User user, Authentication underlyingAuth1) {
this.user = user;
this.underlyingAuth = underlyingAuth1;
}
private final User user;
private final Authentication underlyingAuth;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return underlyingAuth.getAuthorities();
}
#Override
public void setAuthenticated(boolean isAuthenticated) {
underlyingAuth.setAuthenticated(isAuthenticated);
}
#Override
public String getName() {
return underlyingAuth.getName();
}
#Override
public Object getCredentials() {
return underlyingAuth.getCredentials();
}
#Override
public Object getPrincipal() {
return underlyingAuth.getPrincipal();
}
#Override
public boolean isAuthenticated() {
return underlyingAuth.isAuthenticated();
}
#Override
public Object getDetails() {
return underlyingAuth.getDetails();
}
public User getUser() {
return user;
}
}
And a custom Oauth2AuthenticationFilter:
#Component
class CustomLoginAuthenticationFilter extends OAuth2LoginAuthenticationFilter {
#Autowired
private UserDAO userDAO;
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
Authentication auth = super.attemptAuthentication(request, response);
if (auth instanceof OAuth2AuthenticationToken) {
switch (((OAuth2AuthenticationToken) auth).authorizedClientRegistrationId) {
case "google":
Optional<User> user = userDAO.findByEmail(username);
if (!user.isPresent()) {
throw new NotFoundException("!");
}
return MyAuthenticationWrapper(auth, user.get());
}
}
return auth;
}
}
I haven't had success getting this approach to work, and I'm left wondering if this is the right approach at all.
Is there another, more idiomatic approach to combining database user data with Oauth2 user data in Spring security?
Perhaps looking into OAuth2UserService might help. It gets invoked after successfully obtaining the OAuth token. This is how it would work:
A user logs in using Google or Github Oauth2
No need to add anything. Let the default filters take care of that.
My app finds (or creates) the database user with the information returned
Create your own OAuth2UserService as a bean (it'll get picked up automatically) that takes care of dealing with the database:
#Component
public class CustomService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
#Override
public OAuth2User loadUser(OAuth2UserRequest userRequest)
throws OAuth2AuthenticationException {
// ... DB logic goes here
}
}
In loadUser(...), the OAuth2UserRequest gives you access to the corresponding ClientRegistration and the OAuth2AccessToken, which you can then use to query or update the database.
My app saves to the SecurityContextHolder a custom Authentication wrapper that wraps both the Oauth2AuthenticationToken and the database User class
No need to deal with a wrapper! The custom OAuth2User you construct from information from the database will be the Principal in the OAuth2LoginAuthenticationToken, which ends up being the Authentication, so it'll be available to your application. Since you're not dealing with the Authentication yourself, you wouldn't have to worry about saving it in the SecurityContextHolder.
On subsequent requests, the custom Authentication wrapper is available to controller methods
Your Authentication will be of type OAuth2LoginAuthenticationToken. You can get your custom OAuth2User like this:
OAuth2LoginAuthenticationToken auth = //...
OAuth2User user = auth.getPrincipal();
For more info on the core classes you're dealing with, these might be helpful:
OAuth2LoginAuthenticationFilter
OAuth2LoginAuthenticationProvider
OAuth2UserRequest
OAuth2LoginAuthenticationToken
For out-of-the-box implementations of OAuth2UserService, check out:
DefaultOAuth2UserService
CustomUserTypesOAuth2UserService

Pass parameter to controller

I have some app with JWT authentication. And currently, I have such controller:
#RestController
#RequestMapping("users")
public class UserController {
#PostMapping(value = "{userId}/rate/inc")
public Double incRate(#PathVariable Long userId) {
return service.incUserRate(userId);
}
}
But, I want to get user by the token in the filter and pass it as a method's param. For example:
#PostMapping(value = "/rate/inc")
public Double incRate(User user) {
returnservice.incUserRate(user);
}
Is this possible?
Implement argument resolver and inject into your controller everything you need.
By default Spring allowes you to inject Principal object that by default contains users email (it is default realization in Spring Security). But you can implement injection of your business login users account by implementing Interface HandlerMethodArgumentResolver<User>.
I advice you to create an annotation like #AuthorizedUser in make mark your User param with this annotation. And according to this annotation presence in controller method, inject your user via HandlerMethodArgumentResolver.
#Component
public class UserArgumentHandlerResovler implements HandlerMethodArgumentResolver {
#Autowired
private UserRepository userRepository;
public boolean supportsParameter(MethodParameter parameter) {
return parameter.isAnnotationPresent(AuthorizedUser.class);
}
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String email = (String) auth.getPrincipal(); // <- it is a pseudocode, check your Authentication implementation to get email for example.
return userRepository.findByEmail(email);
}
}
If you use Spring Security, you can resolve the current user and then have it provided to your controller method. However – if I'm not mistaken – you must declare it as Principal:
#PostMapping(value = "/rate/inc")
public Double incRate(Principal principal) {
returnservice.incUserRate((User)principal);
}
A more extensive example can be found at Baeldung.

Spring alternative for Factory

May be its a duplicate, Please feel free to tag... I am a newbie to Spring.
I am implementing a UserService for getting user details from different vendors,
So My class Structure is
Interface UserService ->> UserServiceA, UserServiceB
Which user service to use depends upon a field called provider. my code will look something like
public interface ExternalUserService {
ExternalUserDTO getUserDetail(String username);
}
Implementations -
public class GoogleUserService implements ExternalUserService{
#Override
public ExternalUserDTO getUserDetail(String username) {
return user;
}
}
public class FacebookUserService implements ExternalUserService{
#Override
public ExternalUserDTO getUserDetail(String username) {
return user;
}
}
I want to use it in my code in this fashion, I dont know if this is possible, but giving a try to see if its possible
public class ExternalUserManager(String provider) {
String provider;
#Autowired
ExternalUserService service; //This is supposed to come from some factory, dont know how to get it in spring context.
public void doSomething(String username) {
System.out.println(service.getUserDetail(username));
}
}
Had it been in conventional java programming, I would have created a Factory called UserServiceFactory, which would have made the things straight.
Can someone please help me on how much it is possible with spring, and if its possible, then how can I achieve it? We use Spring boot, so no xml config.
You can use a #Bean annotated method with scope 'prototype' as a factory.
Spring will call this method anytime this bean is injected somewhere.
import org.springframework.beans.factory.config.BeanDefinition;
...
#Bean
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public ExternalUserService externalUserService(UserServiceFactory factory,UserProviderResolver resolver) {
.. create the user service depending on resolver.getProvider()
}
The UserServiceFactory is used to create the specific service depending on the provider name, as you already described.
Create a class UserProviderResolver whith a method getProvider() that returns the provider name for the current request or user.
You can #Autowire the HttpRequest in the UserProviderResolver to get access to the current request.

Sending message to specific user using spring

My Goal - To send message to single user if possible without using spring security
I want to input a username from user and set it as username in spring security so that I can use method convertAndSendToUser. I searched on the net and found two approaches
Using DefaultHandshakeHandler to set username but this way I am unable to retrieve user input from the page and use it in determineUser method
I have tried using following piece of code
Authentication request = new UsernamePasswordAuthenticationToken("xyz", null);
SecurityContextHolder.getContext().setAuthentication(request);
But it is not working as it is just changing the username for that method and then it resets the username.
If possible is there any approach with which I can send message to single user without using spring security. Thanks in advance
P.S. I am a newbee.
You can use your first approach to set the Username. First you need add the interceptor to your StompEndpointRegistry class and after that you can determine User from the attributes Map and return the Principal.
Below is the Code:
HttpSessionHandshakeInterceptor is Used for Intercepting the Http attributes and provide them in the DefaultHandshakeHandler class
#Configuration
#EnableWebSocketMessageBroker
#EnableWebMvc
#Controller
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app","/user");
}
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat")
//Method .addInterceptors for enabling interceptor
.addInterceptors(new HttpSessionHandshakeInterceptor())
.setHandshakeHandler(new MyHandler())
.withSockJS();
}
class MyHandler extends DefaultHandshakeHandler{
#Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler,
Map<String, Object> attributes) {
//Get the Username object which you have saved as session objects
String name = (String)attributes.get("name");
//Return the User
return new UsernamePasswordAuthenticationToken(name, null);
}
}
}

Injecting Custom Principal to Controllers by Spring Security

servletApi() support of Spring Security is great.
I want to inject custom Principal as this:
public interface UserPrincipal extends Principal {
public Integer getId();
}
#RequestMapping(value = "/")
public ResponseEntity<List<Conversation>> listAfter(UserPrincipal user){
// implementation
}
or
#RequestMapping(value = "/")
public ResponseEntity<List<Conversation>> listAfter(UserPrincipalImpl user){
// implementation
}
Spring has support for injecting Principal instances with the help of ServletRequestMethodArgumentResolver.
It is injecting principal as this:
else if (Principal.class.isAssignableFrom(paramType)) {
return request.getUserPrincipal();
}
Here is the place where the problem begins. request is here an instance of SecurityContextHolderAwareRequestWrapper. It has an implementation of:
#Override
public Principal getUserPrincipal() {
Authentication auth = getAuthentication();
if ((auth == null) || (auth.getPrincipal() == null)) {
return null;
}
return auth;
}
Because an Authentication is also an Principal. (The only part of spring security I did not like so far. I will ask this a separate question as well.)
This is causing a problem. Because Authentication is a Principal not a UserPrincipal.
How can I resolve this problem? Do I need to implement an authentication which is a UserPrincipal as well? Or should I change HandlerMethodArgumentResolver order a create a custom resolver? (This is not easy for Spring MVC because internal handlers has higher priority.)
As a extra information:
I am using Spring Security M2 and my configuration for AuthenticationManagerBuilder is simply:
#Override
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(detailsService);
}
Any help?
Fundamentally this seems like trouble integrating with Spring MVC and not a Spring Security issue. Spring Security has no way of knowing that Authentication#getPrinicpal() implements Principal since the API returns an Object.
I see a few options for you. Each has some pros and cons, but I think the best is using #ModelAttribute and #ControllerAdvice
#ModelAttribute and #ControllerAdvice
The easiest option is annotate a method with #ModelAttribute on custom #ControllerAdvice. You can find details in the Spring Reference.
#ControllerAdvice
public class SecurityControllerAdvice {
#ModelAttribute
public UserPrincipal customPrincipal(Authentication a) {
return (UserPrincipal) a == null ? null : a.getPrincipal();
}
}
Now in your controller you can do something like this:
#RequestMapping(value = "/")
public ResponseEntity<List<Conversation>> listAfter(#ModelAttribute UserPrincipal user){
// implementation
}
Note that the #ModelAttribute is necessary only to ensure the #ModelAttribute is used over the HttpServletRequest#getPrincipal(). If it did not implement Principal, #ModelAttribute is not required.
#Value and ExpressionValueMethodArgumentResolver
You can also do something like this:
#RequestMapping(value = "/")
public ResponseEntity<List<Conversation>> listAfter(
#Value("#{request.userPrincipal.principal}") UserPrincipal user){
// implementation
}
This works because the HttpServletRequest is available as an attribute to the ExpressionValueMethodArgumentResolver (added by default by Spring MVC) which allows accessing things via SpEL. I find this less attractive than #ModelAttribute due to the constant that must be in the #Value annotation. It will be nicer when SPR-10760 is resolved which would allow for your own custom annotation to be used like:
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Value("#{request.userPrincipal.principal}")
public #interface CurrentUser { }
#Autowire RequestMappingHandlerAdapter
This is a bit sloppy because the RequestMappingHandlerAdapter has already been initialized, but you can change the ordering of the HandlerMethodArgumentResolvers as shown here:
#EnableWebMvc
#Configuration
public class WebMvcConfiguration
extends WebMvcConfigurerAdapter {
...
#Autowired
public void setArgumentResolvers(RequestMappingHandlerAdapter adapter) {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
resolvers.add(new CustomPrincipalArgumentResolver());
resolvers.addAll(adapter.getArgumentResolvers().getResolvers());
adapter.setArgumentResolvers(resolvers);
}
}
Subclass WebMvcConfigurationSupport
You can also extend WebMvcConfigurationSupport instead of using #EnableWebMvc to ensure your HandlerMethodArgumentResolver is used first. For example:
#Configuration
public class WebConfiguration extends WebMvcConfigurationSupport {
...
#Bean
#Override
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter()();
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
resolvers.add(new CustomPrincipalArgumentResolver());
resolvers.addAll(adapter.getArgumentResolvers().getResolvers());
adapter.setArgumentResolvers(resolvers);
return adapter;
}
}
I know this is an old question, but as it does come up on top on Google when searching for injecting a Principal, I'll post a 2020 update:
Since Spring Security 4.0 you can just simply inject an #AuthenticationPrincipal into your controller methods:
#RequestMapping(value = "/")
public ResponseEntity<List<Conversation>> listAfter(#AuthenticationPrincipal UserPrincipal user){
// implementation
}
This will work out of the box, no additional config required.

Resources