Spring - Call a Service method in JSTL - spring

I'm using Spring Security to handle user authentication for my Spring MVC web app. I'm able to get the username from the Authentication object, but my username is the email address, and I want to be able to show the user's actual name in my header.
So I have my custom User class:
class Users{
String name;
String email;
String password;
// getters and setters
}
I thought about using an aop scoped proxy to set the User in the session, as explained in this blog: http://richardchesterwood.blogspot.co.uk/2011/03/using-sessions-in-spring-mvc-including.html . The problem I faced using this approach is that the AuthenticationSuccessHandler is actually a Service and should be stateless. So Spring doesn't autowire a Users object for me in the Service.
So I created a Service method that would get the username (or email) from the Authentication object and return my Users object. This I can use in my Controllers.
#Service
#Transactional
public class UserServiceImpl implements UserService {
#Override
public Users getCurrentUser() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User userD = (User)auth.getPrincipal();
Users currentUser = getUserByEmail(userD.getUsername());
return currentUser;
}
}
So is there a way that I can call this Service method from JSTL to get the user's full name, which I can display in my header?
Am also open to suggestions for a better way to implement this.
EDIT:
In my earlier approach using the AuthenticationSuccessHandler, my code goes like this:
#Service("userDetailsService")
#Transactional
public class UserAuthenticationServiceImpl implements AuthenticationSuccessHandler {
#Autowired
Users currentUser;
#Override
public void onAuthenticationSuccess(HttpServletRequest hsr, HttpServletResponse hsr1, Authentication a) throws IOException, ServletException {
User user = (User) a.getPrincipal();
Users user1 = userDao.getUserByEmail(user.getUsername());
currentUser.setName(user1.getName());
currentUser.setUserRoles(user1.getUserRoles());
//currentUser = user1;
}
}
And in my spring-servlet.xml file, I have this:
<bean id="currentUser" class="com.foo.bean.Users" scope="session">
<!-- this next element effects the proxying of the surrounding bean -->
<aop:scoped-proxy/>
</bean>
The problem I'm facing here is that Spring isn't autowiring my currentUser object because the Service isn't in the session scope.

If the only thing you need is the full name just use an AuthenticationSuccessHandler to retrieve the user and add the name to the session (or the full user if you need more then that).
#Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse res, Authentication auth) throws IOException, ServletException {
User user = (User) auth.getPrincipal();
Users user1 = userDao.getUserByEmail(user.getUsername());
WebUtils.setSessionAttribute(req, "currentUser" user1);
}
Then in your JSP the only thing you need is ${currentUser.username}.
Although I wouldn't suggest stuffing the full user in the session I would suggest just adding the information needed.
WebUtils.setSessionAttribute(req, "currentUsername" user1.getUsername());
Then in your JSP ${currentUsername} saves you a lot of serialization overhead of the session.

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

How to configure Spring Boot Security so that a user is only allowed to update their own profile

I have implemented the basic Spring Boot Security stuff in order to secure my web services. I know that you can grant access to some services only to some user Roles, but is it also possible to grant access to a specified user (user can be dynamic)?
Let's say we have a social app, where every user has their own profile. With the following rest-service, they should be the only one able to edit the profile:
#RestController
public class UserController {
#RequestMapping(method = RequestMethod.PUT, path = "/user/{userId}", ...)
public UserDetails updateUserDetails(#PathVariable("userId") String userId) {
// code for updating the description for the specified user
}}
}
How can i ensure with spring security, that only the user itself can update his personal profile? Any other user should be rejected. Is there an elegant way, how you can configure this behaviour?
I have tried to find a method for that inside my WebSecurityConfig, but with no success.
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// configure authorization for urls
.authorizeRequests()
// grant access to all users for root path and /home
//.antMatchers("/", "/home").permitAll()
// here i would like to grant access in the way, that only the user is allowed to perform this request by calling url with his userId
.antMatchers(HttpMethod.PUT,"/user/<userId>").and().httpBasic();
}
What is a good approach to implement this behaviour?
I think that the best way to implement something like this would be to inject the Principal (Object containing the user that is logged in for this request) into the controller and then check if the user id or username is matching.
#RestController
public class UserController {
#RequestMapping(method = RequestMethod.PUT, path = "/user/{userId}", ...)
public UserDetails updateUserDetails(#PathVariable("userId") String userId, Principal principal) {
CustomUserDetails userDetails = (CustomUserDetails) principal;
if (userDetails.getUserId().equals(userId)) {
// Update the user
}
}}
}
Note that you will need a custom UserDetails interface if you want to add the user id, because it only provided the username by default. Check this question if you want to know how.
Use #PreAuthorize annotation:
#PreAuthorize("#userId == principal.userId")
#RequestMapping(method = RequestMethod.PUT, path = "/user/{userId}", ...)
public UserDetails updateUserDetails(#PathVariable("userId") String userId) {
// code for updating the description for the specified user
}
This assumes that the class that implements UserDetails interface has a userId property.

Cache user specific application data with Spring Security

In our microservice each authenticated Spring Security user has an associated application-specific data structure.
when thinking on how can we easily cache this data together with the user, we thought it would be good if it could have been done similar to this:
add the cached data to the in-memory-authentication when creating the users:
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth.inMemoryAuthentication().withUser("user").password("123").roles("ROLE");
auth.inMemoryAuthentication().withUser("user").cache(appDate);
...
}
pull the data in #RestController methods:
#RequestMapping("/foo")
public void foo() {
User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Object details = SecurityContextHolder.getContext().getAuthentication().getDetails();
Object cachedAppDate= SecurityContextHolder.getContext().getAuthentication().getCachedData();
}
Obviously the method in bold are a wish-list and do not exist.
Any advice on how to do this easily with the existing Spring Security framework?
thanks!
If you can get your cached data using only user name (no password required) you can use implement org.springframework.security.core.userdetails.UserDetailsService, it has only one method - UserDetails loadUserByUsername(String username), and return required data as custom UserDetail object. After implement interface, just pass it as UserDetailService for AuthenticationManagerBuilder.
If you need password to get that cached data, things got a bit complicated.
You should create your own AuthenticationProvider and put cached data in Principal or UserDetails. For example code for set additional data in Principal:
public class MyProvider implements org.springframework.security.authentication.AuthenticationProvider {
#Override
public boolean supports(Class<? extends Object> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
... // check login/password
Object cachedAppDate = "i'm cached data!";
MyUser user = new MyUser(token, cachedData);
UsernamePasswordAuthenticationToken output = new UsernamePasswordAuthenticationToken(user, authentication.getCredentials(), user.getAuthorities());
output.setDetails(authentication.getDetails());
return output;
}
}
public static class MyUser extends org.springframework.security.core.userdetails.User {
private final Object cachedData;
public User(UsernamePasswordAuthenticationToken token, Object cachedData) {
super(token.getName(), "", token.getAuthorities());
this.cachedData = cachedData;
}
public Object getCachedData() {
return this.cachedData;
}
}
and access cached data as ((MyUser)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getCachedData();

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);
}
}
}

Not understanding Spring SessionAttribute and Autowiring

I'm not a Spring expert and I'm facing a behavior I don't understand...
I have a SessionAttribute "user" in my Controller, that is autowired to my bean User.
When I log in, my User is populated with some values etc.
When I log out, I am expecting that my session attribute "user" would be reset, but it keeps its values.
So where is the problem? Is my log out not working properly? Or is it normal and so, could someone explain me what is happening inside Spring please?
Here is a code Sample to understand my question:
#Controller
#SessionAttributes("user")
public class HomeController
{
#Autowired
private User user;
// Session Attribute
#ModelAttribute("user")
public User setSessionAttribute()
{
LOGGER.debug("Adding user to session...");
return user;
}
...
}
Edit: logout sample code and user declaration
My User is declared like this:
#Component
public class User
{
...
}
To log out I have a link pointing to /myapp/j_spring_security_logout and I have implemented a logout handler:
#Component
public class MyLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler
{
#Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException
{
//to check if user is in session, but it's not
Enumeration<String> e = request.getSession().getAttributeNames();
//some code needed to log out from my custom security manager
//kill the session (not spring session) and redirect to the specified url
agent.logout("/myapp/login");
super.onLogoutSuccess(request, response, authentication);
}
}
Now that you've posted User
#Component
public class User
{
...
}
you will notice that it is has Singleton scope. The bean autowired here
#Autowired
private User user;
is that singleton instance. It will always be the same regardless of what Session or request you're processing and regardless of you logging out. So up to now, all your users have been sharing the same User instance.
You can change it to have Session scope.
#Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
#Component
public class User
{
...
}
Now each Session will have its own instance to work with.
I believe SimpleUrlLogoutSuccessHandler is not clearing the content of the session.
SimpleUrlLogoutSuccessHandler only invokes the handle() method in AbstractAuthenticationTargetUrlRequestHandler and it's Javadoc says:
Invokes the configured RedirectStrategy with the URL returned by the determineTargetUrl method.
The redirect will not be performed if the response has already been committed.
Simplest solution would be to remove this attribute from the session by:
request.getSession().removeAttribute("user");

Resources