Spring Security: how to pass additional user info to JSP? - java

In JSP I can get username by ${pageContext.request.remoteUser}. But there is also additional info (rating of user) I need to display on every page of my site. How can I access it, considering there is a #Service to get it by username?
For what it's worth I use custom authentication provider:
#Service
public class MyUserDetailsService implements UserDetailsService {
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
return new User(s, "password", Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")));
}
}
<security:authentication-manager>
<security:authentication-provider user-service-ref='myUserDetailsService'/>
</security:authentication-manager>

You could create implementation of AuthenticationSuccessHandler and set an attribute there:
#Component
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
request.getSession().setAttribute("someDetail", "detailsValue");
response.sendRedirect("/to-whatever-url-you-want")
}
}
Upon successful login, someDetail attribute will be set. Note that you can also obtain currently logged in user from Authentication instance and perform some logic.

You can create a custom UserDetails class (e.g. MyUserDetails) and save the extra information there. In your UserDetailsService, just return this MyUserDetails instead of the normal UserDetail.
public class MyUserDetails extends UserDetail {
private int rating;
... // other properties
... // getter setter
}
#Service
public class MyUserDetailsService implements UserDetailsService {
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
return new MyUserDetails(...);
}
}
In every controller, you can call
(MyUserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
to the get the current principal/UserDetails, which contains your extra info(e.g. rating of the user).
P.s. If this extra info is related to users, sessions are not the right place to store it, because sessions may expire after closing the browser. If this extra info is just some temporary data, then #Branislav Lazic's answer is correct. Since I can't add a comment, so I have to write the comments to #Branislav Lazic's answer here.

Related

#AuthenticationPrincipal UserModel user is always Null. I tried many solution methods

There is a controller whose task is to return the user profile to me using the REST API. Code further:
#PostMapping("/me")
public UserProfileResponse getUserProfile(#AuthenticationPrincipal UserAuthenticationPrincipalModel user ) {
return userProfileService.getUserProfile(user.getUserId());
}
I created a model for the User entity. The entity class is created as:
public class User implements UserDetails { ... }
The model has the following structure:
public class UserAuthenticationPrincipalModel extends User {
private String userId;
private String avatarUrl;
public UserAuthenticationPrincipalModel(***.********.entity.User user) {
super(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(),
user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
this.userId = user.getUserId();
this.avatarUrl = user.getUserPic();
}
// + equals and hashCode
}
In the model, the data that I will ever (or so far plan so) to pull from the AuthPrincipal an authorized user. According to the statement of work, I can’t use the default Principal, I haven’t even tried it. Implementation of UserDetailsService:
#Service
public class UserDetailsServiceImpl extends AbstractMySQLService<User, String, UserRepository> implements UserDetailsService {
private final UserRepository userRepository;
public UserDetailsServiceImpl(final UserRepository userRepository, final UserRepository repository) {
super(repository);
this.userRepository = userRepository;
}
#Override
public UserAuthenticationPrincipalModel loadUserByUsername(final String email) {
User user = userRepository.findByEmail(email);
if (user == null) {
throw new UsernameNotFoundException("Invalid username or user not e: " + email);
}
return new UserAuthenticationPrincipalModel(user);
}
}
Error: Null always flies into methods. Made a lot of additions, which are recommended on the Baeldang and this stack - nothing :(
Please, write a comment, if I should add some more information.
UPD 1:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/ca/**").hasRole("COMPANY_ADMIN")
.antMatchers("/d/**").hasRole("DRIVER")
.antMatchers("/u/**").authenticated()
.antMatchers("/sign_up", "/oauth/token", "/swagger-ui.html", "/resources").permitAll();
}
I can give you a few pointers of how to approach this issue.
Ensure you are using org.springframework.security.core.annotation.AuthenticationPrincipal instead of #org.springframework.security.web.bind.annotation.AuthenticationPrincipal (Both should work but just pre-caution because the later is deprecated)
Now the issue is to isolate the problem to ONE of the following areas so you can concentrate there:
Your UserDetailsServiceImpl is not used
Something wrong with getUserProfile method with #AuthenticationPrincipal
user is not associated with a logged in session.
To identify that, replace your public UserProfileResponse getUserProfile method with the following:
[Do not change anything else]
#Autowired
private UserDetailsService userDetailsService;
#PostMapping("/me")
public void getUserProfile(#AuthenticationPrincipal UserDetails user ) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println("principal : " + authentication.getPrincipal());
System.out.println("Implementing class of UserDetails: " + authentication.getPrincipal().getClass());
System.out.println("Implementing class of UserDetailsService: " + userDetailsService.getClass());
}
Check the logs and it will tell you where the problem is and if you can't figure out from it, you can post the outcome here for more help
Update: Answers for point 4 given as below in comments.
principal : anonymousUser
Implementing class of UserDetails : class java.lang.String
Implementing class of UserDetailsService : class
Conclusion : endpoint is not protected and user accessing without logging in
Solution : Protect the endpoint by replacing .antMatchers("/u/**").authenticated() with .antMatchers("/api/u/**").authenticated()

How to configure Spring-Data-Rest in way to only return data owned by the logged in user

I am working on a Spring Boot RESTful application which will be exposing a bunch of APIs for the web app to perform CRUD operations on the resources.
I am using spring-data-rest (along with spring-data-jpa of course) to expose the entities/repositories with the help of Spring Magic.
Even though I have secured (role-based) the endpoints with spring-security, it is not completely secure.
For example:
I have a User entity with has one-to-many relationship with Car. So the endpoint (auto exposed by spring-data-rest) for getting a user's cars is localhost:8080/users/{userId}/cars
However, any user with the required role can just pass the userId of another user and still access the endpoint.
The behavior I want is to secure these endpoints in a way that if I a logged-in user's ID is 1, then we can only hit localhost:8080/users/1/cars. Any other request with any other userId should end up in 403 or something.
Note: I know if write my own controllers then I can get a handle of the principal and do what I desire. I just want to know is there a way or pattern in spring-data-rest to achieve this?
Since you have already secured the application with Spring Security , here is another alternative with Method Security Expressions
Please review the #Pre and #Post Annotations for your requirement.
You may store the logged-in user's userId to the Authentication object.Details here.
Secure the required method with the #PreAuthorize annotation as follows
#PreAuthorize("#user.userId == authentication.principal.userId")
public List<Car> getCars(User user){..}
Do remember to enable method security
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {..}
To achieve that you need to write an Interceptor.It will be used under following situation:
Before sending the request to the controller
Before sending the response to the client
Before writing any Interceptor it should implement the HandlerInterceptor interface.
Three methods Interceptor supports are :
preHandle() method − Perform operations before sending the request
to the controller. This method should return true to return the
response to the client.
postHandle() method − Used to perform operations before sending
the response to the client.
afterCompletion() method − This is used to perform operations
after completing the request and response.
Code :
#Component
public class MyInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String pathVariablesMap = request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
//From this pathVariablesMap extract UserId and match with a loggedinUserId
}
#Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler,
) throws Exception {}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception exception) throws Exception {}
}
By using a InterceptorRegistry you can register your Interceptors like below :
#Component
public class MyRegistoryConfig extends WebMvcConfigurer{
#Autowired
MyInterceptor myInterceptor ;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor );
}
}
For more Info follow this link Interceptors
EDIT : As #Ritesh suggested added that point.
You're using spring security(great :D), so it's better to create a simple filter, register it, then simply do your custom authorize in that filter.
In brief
Create a Custom filter
Get userId from the URL path
Get userId from SecurityContextHolder (Authenticated user principal)
Compare fetched userIds
Register filter in spring security config (After BasicAuthenticationFilter)
1- Create a custom filter (Pseudo-code)
public class CustomFilter extends GenericFilterBean {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
//Fetch userId from path
HttpServletRequest req = (HttpServletRequest) request;
String path = req.getContextPath();
//..
//Fetch userId from SecurityContextHolder (User Principal)
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
User user = (User) authentication.getPrincipal();
Long userId = user.getId();
//Compare userId (fethced from path) with authenticated userId (fetched from principal)
//If comparison is ok
chain.doFilter(request, response);
//else
//throw Unauthorize
}
2- Register a filter in spring security config (After BasicAuthenticationFilter.class)
#Configuration
public class Configuration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(
new CustomFilter(), BasicAuthenticationFilter.class);
}
}
With this structure when an authenticated user sends a request, the request will be first checked (Comparison between userIds) and then sent.
More information for creating a filter in spring security:
A Custom Filter in the Spring Security Filter Chain

Complex authentication in Spring Boot

I'm trying to get authentication done from a Spring Boot app with an external provider I will need to code for a 3rd party software equipment . The app issues commands on that external software and thus a user credential is needed to connect and operate.
The authentication needs to be performed using username and password provided in a form against an Active Directory database (Checks if the user exists in the company), and then an internal database which tells the app if the user is allowed to use the app and whether he's an admin or not (For customizing the menu bar later on).
Afterwards, the user is authenticated with this external software by means of a binary executable present on the server (Using ProcessBuilder). It's a bit complex but that's the way it has to be because of external contraints.
Furthermore, once the user is authenticated in this 3rd party software, he must pick a role out of a list which contains all roles available to that user. Only after this, the connection is finally set up and we have to redirect the user to the main page from where he can use the app.
The login page shows a form with username and password fields, and a button which will trigger the auth process and present the user with the list of roles, and after picking one and clicking another button the role will be selected and the user will be redirected to the home page.
The problem is that I don't have any clues to implement this in Spring Boot.
My LoginController contains:
#Inject
public LoginController(final LoginService loginService) {
this.loginService = loginService;
}
#RequestMapping("/login.html")
public ModelAndView getLoginView() {
LOGGER.debug("Received request to get login view");
ModelMap model = new ModelMap();
model.addAttribute("authenticationTypes",loginService.getAuthenticationTypes());
model.addAttribute(loginService);
return new ModelAndView("login", model);
}
I had working code in a LoginServiceImpl module I was using in a older JSF application which would like to reuse but don't know how.
Like a similar answer here, you need to create your own CustomAuthenticationProvider, which must implements AuthenticationProvider.
For example:
#Component
public class CustomAuthenticationProvider
implements AuthenticationProvider {
#Autowired
private ThirdPartyClient thirdPartyClient;
public void setAtpClient(ThirdPartyClient atpClient) {
this.thirdPartyClient = atpClient;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
Request3rd requestTO = new AtpAuthenticateRequestDTO();
requestTO.setPassword(password);
requestTO.setUsername(username);
Response3rd authenticate = this.thirdPartyClient.authenticate(requestTO);
if (authenticate != null) {
List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
Authentication auth = new UsernamePasswordAuthenticationToken(authenticate.getUsername(), password, grantedAuths);
return auth;
} else {
return null;
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Then in the SecurityConfig class, which extends WebSecurityConfigurerAdapter override in this configure method:
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(this.authenticationProvider);
}
Where you can autowire the customAuthenticationProvider created before:
#Autowired
private CustomAuthenticationProvider authenticationProvider;

Spring Secuiry Database Authentication casting Spring User to Domain User

I've been having some trouble figuring this one out.
I've got an multitenant system where users are organized into organizations. Within that organization the usernames must be unique. Otherwise, two organizations can have the same user name.
I've got spring security hooked up with the jdbc-user-service and all that works fine. My problems start when I'm trying to get the current user.
I took a look at a link that leverages spring 3 and the Principal object as a method parameter. This works great, except a Principal doesn't have enough information! First, usernames aren't unique in my usecase, also having easy access to the organization that the user belongs to would be great.
A little more searching dug up this awesome answer. The problem with this is the same problem as before. It relies on the Principal object, which just doesn't have enough info.
(here's the magic)
#Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
if (this.supportsParameter(methodParameter)) {
Principal principal = webRequest.getUserPrincipal();
return (User) ((Authentication) principal).getPrincipal();
} else {
return WebArgumentResolver.UNRESOLVED;
}
}
Is there a good way to override the User object to use my own? Am i stuck writing a custom UserDetailService? Is there a better approach than continuing to walk down this path?
Thanks
Make your User object inherit UserDetails(or use a wrapper) and use it as principal.
e.g.
public class MyCustomUser implements UserDetails {
// ..
}
Make a custom UserDetailsService that returns your User object:
#Service
public class MyCustomUserDetailsServiceImpl implements UserDetailsService {
#Autowired
private MyCustomUserDAO userDAO;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userDAO.getByUsername(username);
}
}
Now you can extract your user:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Object principal = auth.getPrincipal();
MyCustomUser user = (MyCustomUser)principal;
user.myCustomMethod();

Spring Security - Authentication's username is null in AuthenticationSuccessHandler

I have a Customer class which has all the account info.(it does NOT extend Spring's userdetails.User class)
I'm trying to do some stuff after a successful login (e.g. set new last login time). To achieve this I set up a custom AuthenticationSuccessHandler.
In the onAuthenticationSuccess method I try to get the username from the Authentication object. This object however is a User object. If I try to get the username from it I get null.
Can I somehow manage to make the Authority object a Customer object? Or does my Customer class have to extend the User class?
Update
Some more details:
I have my User class. It is completely self written and doesn't implement or extend any interface/class. I do not have a class that implements a UserDetailsService. The <form-login> part of my applicationContext-security.xml looks like this:
<form-login login-page="/index.htm"
authentication-success-handler-ref='authSuccHandler'
authentication-failure-handler-ref='authFailureHandler'
default-target-url='/library/login.htm'
always-use-default-target='true'/>
Theh authSuccHandler looks like this: (The necessary bean definition is in place)
public class PostSuccessfulAuthenticationHandler extends SimpleUrlAuthenticationSuccessHandler
{
#Autowired
private UserService userService;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws ServletException, IOException
{
userService.trackUserLogin(authentication.getName()); //NullPointerException
super.onAuthenticationSuccess(request, response, authentication);
}
}
The form redirects to j_spring_security_check
Authentication cannot be User, since they don't inherit each other.
If your UserDetailsService produces a custom UserDetails, you should be able to obtain it by calling getDetails() on Authentication.
When the request comes into the authentication success handler, it expects you to redirect to the desired URL. Use the following to redirect to the desired page like home.htm.
This will work definitely!
The modified code is given below. Check it and let me know if you have any issues.
public class PostSuccessfulAuthenticationHandler extends SimpleUrlAuthenticationSuccessHandler
{
#Autowired
private UserService userService;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws ServletException, IOException
{
userService.trackUserLogin(authentication.getName()); //NullPointerException
response.sendRedirect("home.htm");
//super.onAuthenticationSuccess(request, response, authentication);
}
}
I think the method you are looking for is getPrincipal on Authentication. Then you have to case the object that comes back to your custom class.
User user = (User)authentication.getPrincipal();

Categories