简体   繁体   中英

How to receive the authenticated user from SpringSecurity in other service implementation , instead of an anonymousUser.(Spring Security+JWT)

Good day developers. I'm trying to retrieve some data for a authenticated user in my application in order to implement it in other method.This app uses Spring Security and JWT, thus the first thing to do was setting the implemetation of Java interface UserDetails on the class UserDetailsImpl as following:

package com.example.demo.services;

import com.example.demo.entiities.Renter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.*;
import java.util.stream.Collectors;

public class UserDetailsImpl implements UserDetails {

    public static final long serialVersionUID=1L;

    private Long id;
    private String username;
    private String email;
    @JsonIgnore
    private String password;
   
    private Collection<? extends GrantedAuthority> authorities;
   
    public UserDetailsImpl(Long id, String username, String email, String password,
                           Collection<? extends GrantedAuthority> authorities) {
        this.id = id;
        this.username = username;
        this.email = email;
        this.password = password;
        this.authorities = authorities;
    }
    
    public static UserDetailsImpl build(Renter renter) {
        List<GrantedAuthority> authorities = renter.getRoles().stream()
                .map(role -> new SimpleGrantedAuthority(renter.getRenterName()))
                .collect(Collectors.toList());

        return new UserDetailsImpl(
                renter.getId(),
                renter.getRenterName(),
                renter.getRenbterEmail(),
                renter.getRenterPassword(),
                authorities);
    }//this method would return the new user logged details accessed through the entity Renter and each 
     //method i need neccesary for my app comsumption , like getting the name , email, password,etc...
 
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    public Long getId() {
        return id;
    }

    public String getEmail() {
        return email;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        UserDetailsImpl user = (UserDetailsImpl) o;
        return Objects.equals(id, user.id);
    }


}

With that implementation of the Userdetails interface , is time to also implement the UserDeailsService interface too in order to get them the UserDetails object, thus its implementation would be :

package com.example.demo.services;

import com.example.demo.entiities.Renter;
import com.example.demo.jwt.JwtUtils;
import com.example.demo.repositories.RenterRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    RenterRepository renterRepository;

    @Override
    @Transactional
    public UserDetails loadUserByUsername(String renterName) throws UsernameNotFoundException {
       Renter renter = renterRepository.findByRenterName(renterName)
                .orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + renterName));

        return UserDetailsImpl.build(renter);
    }
}
thus i get full custom Renter object using RenterRepository, then i build a UserDetails object using static build() method.

Thus next step would be setting all logic to filter the token created in every user login request, and from there with that token generated and triggering the SecurityContextHolder i might be able to access the user authenticated details, thus:

package com.example.demo.jwt;

import com.example.demo.services.UserDetailsServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class AuthTokenFilter extends OncePerRequestFilter {
    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        try {
            String jwt = parseJwt(request);
          
  if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
                
                String username = jwtUtils.getUserNameFromJwtToken(jwt);

                UserDetails userDetails = userDetailsService.loadUserByUsername(username)

                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());

                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                
                SecurityContextHolder.getContext().setAuthentication(authentication);
                
            }
        } catch (Exception e) {
            logger.error("Cannot set user authentication: {}", e);
        }

        filterChain.doFilter(request, response);
    }
Thus in this case basically inside the try statement under the condition of the generated token being valid , first i extract the user name from the token . Then from  that username i got the UserDetails to create an Authentication object accessing the method of its service(loadUserByname); and after that 
got settled  the current UserDetails in SecurityContext using setAuthentication(authentication) method, that would be used in further implemenbtations to access the user data(didn't work)


    private String parseJwt(HttpServletRequest request) {
        String headerAuth = request.getHeader("Authorization");

        if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
            return headerAuth.substring(7, headerAuth.length());
        }

        return null;
    }


}

Then login payload is created achieving from user interaction all data neccesary for the authetication process on a class LoginRequest. An the login implementation with the service and its implementation would be set in this way

RENTER SERVICE
package com.example.demo.services;

import com.example.demo.entiities.Renter;
import com.example.demo.exceptions.GeneralException;
import com.example.demo.payload.LoginRequest;
import org.springframework.http.ResponseEntity;
import java.util.List;
import java.util.Map;

public interface RenterService  {
   
    ResponseEntity<?> loginUser(LoginRequest loginRequest)throws GeneralException;
}

RENTER SERVICE IMPLEMENTATION

package com.example.demo.services;

import com.example.demo.dto.RenterDtos;
import com.example.demo.entiities.EnumRoles;
import com.example.demo.entiities.Renter;
import com.example.demo.entiities.Role;
import com.example.demo.exceptions.GeneralException;
import com.example.demo.exceptions.NotFoundException;
import com.example.demo.jsons.RenterJson;
import com.example.demo.jwt.JwtUtils;
import com.example.demo.payload.LoginRequest;
import com.example.demo.payload.SignUprequest;
import com.example.demo.repositories.RenterRepository;
import com.example.demo.repositories.RoleRepository;
import com.example.demo.responses.JwtResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.client.HttpServerErrorException;

import java.util.*;
import java.util.stream.Collectors;


import org.modelmapper.ModelMapper;

import javax.validation.Valid;

@Service
public class RenterServiceImpl implements RenterService {

    @Autowired
    private RenterRepository renterRepository;

    @Autowired
    private RoleRepository roleRepository;

    @Autowired
    PasswordEncoder passwordEncoder;

    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    JwtUtils jwtUtils;
    
    private static final Logger LOGGER = LoggerFactory.getLogger(RenterServiceImpl.class);

    @Override
    public ResponseEntity<?> loginUser(LoginRequest loginRequest) throws GeneralException {
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(loginRequest.getRenterName(), loginRequest.getRenterPassword()));

        SecurityContextHolder.getContext().setAuthentication(authentication);

        String jwt = jwtUtils.generateJwtToken(authentication);

        UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();

        List<String> roles = userDetails.getAuthorities().stream()
                .map(item -> item.getAuthority())
                .collect(Collectors.toList());

        return ResponseEntity.ok(new JwtResponse(jwt,
                userDetails.getId(),
                userDetails.getUsername(),
                userDetails.getEmail(),
                roles));
    }

    private static List<GrantedAuthority> mapRoleUser(List<String> roles){
        List<GrantedAuthority>authorities=new ArrayList<>();
        for (String role : roles){
            authorities.add(new SimpleGrantedAuthority(role));
        }
        return authorities;
    }
}


Then the controller with its endpoint calling all this :

package com.example.demo.controller;


import com.example.demo.dto.RenterRegisterDto;
import com.example.demo.entiities.EnumRoles;
import com.example.demo.jsons.CreateRenterJson;
import com.example.demo.jsons.RenterJson;
import com.example.demo.jwt.JwtUtils;
import com.example.demo.payload.LoginRequest;
import com.example.demo.payload.SignUprequest;
import com.example.demo.repositories.RenterRepository;
import com.example.demo.repositories.RoleRepository;
import com.example.demo.responses.AppResponse;
import com.example.demo.entiities.Renter;
import com.example.demo.entiities.Role;
import com.example.demo.exceptions.GeneralException;
import com.example.demo.responses.JwtResponse;
import com.example.demo.services.RenterService;
import com.example.demo.services.UserDetailsImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.*;
import java.util.stream.Collectors;

@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/cubancoder/multirenter")
public class RegistrationController {

    @Autowired
    RenterService renterService;

    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    RenterRepository renterRepository;

    @Autowired
    RoleRepository roleRepository;

    @Autowired
    PasswordEncoder passwordEncoder;

    @Autowired
    JwtUtils jwtUtils;

    @ResponseStatus(HttpStatus.OK)
    @PostMapping(value = "/login/renter")
    public AppResponse<ResponseEntity<?>>  logInUser(@Valid @RequestBody LoginRequest loginRequest) throws GeneralException {
               
        return new AppResponse<>("Success",String.valueOf(HttpStatus.CREATED),
                "Ok",renterService.loginUser(loginRequest));
    }
}


Until this point the user logged is achieved and all data referring to it is ok. On a debug process i check what the implementation brings and is fine在此处输入图像描述 .

THE PROBLEM STARTS HERE

Then having in mind that user logged i would like to access its data from other service implementation in order to boost other app features, thus lets say i have a service and its implementation called ProductServiceImpl, and in this classes i initialize methods that brings me all products, but i also want to know which user is doing that request,thus if it is logged i need all the data , otherwise the app would do other stuff. Having in mind once the user is autheticated with token a SecurityContextHolder is already created setting the user details for that request, i guess all would be such simple as calling that SecurityContextHolder wherever i need to access the data the user logged brings right?

package com.example.demo.services;

import com.example.demo.dto.ProductDtos;
import com.example.demo.dto.RenterDtos;
import com.example.demo.entiities.*;
import com.example.demo.exceptions.GeneralException;
import com.example.demo.exceptions.NotFoundException;
import com.example.demo.jwt.AuthEntryPointJwt;
import com.example.demo.jwt.JwtUtils;
import com.example.demo.payload.LoginRequest;
import com.example.demo.repositories.ProductRepository;
import com.example.demo.repositories.RenterRepository;
import com.example.demo.security.AuthenticationValidation;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class ProductServiceImpl implements ProductService {

    @Autowired
    ProductRepository productRepository;

    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    UserDetailsService userDetailsService;

    @Autowired
    ProductDtos productDtos;

     @Autowired
    AuthEntryPointJwt authEntryPointJwt;

    @Autowired
    RenterDtos renterDtos;

    @Autowired
    RenterRepository renterRepository;

    @Autowired
    JwtUtils jwtUtils;

    @Autowired
    LoginRequest loginRequest;

    public Map<String, Object> getAllProducts() throws GeneralException {
        Map<String, Object> dto = new HashMap<>();
       
        List<Product> listProducts = productRepository.findAll();
        
Option1:
        Object auth = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
                                    

        .....doing something here .........
Option2:
        
         Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        
        if (auth.isAuthenticated()) {
            Object Authenticated = auth.getPrincipal();
            String renterLogged = ((UserDetails) Authenticated).getUsername();
            .....doing something........
        }
       
 return dto;
    }
}


BUT THE CONTEXT HOLDER BRINGS ME AN ANONYMOUS USER!!! not letting me proceed cause of a error saying that :

ava.lang.ClassCastException: java.lang.String cannot be cast to org.springframework.security.core.userdetails.UserDetails
    at com.example.demo.services.ProductServiceImpl.getAllProducts(ProductServiceImpl.java:83) ~[classes/:na]...

And honestly i quite strayed about what else to do!. In my debug process in order to check :

Option 1 在此处输入图像描述

Option 2 在此处输入图像描述

Last but not the least in my security package its class is set in this way :

package com.example.demo.security;

import com.example.demo.entiities.Renter;
import com.example.demo.exceptions.NotFoundException;
import com.example.demo.jwt.AuthEntryPointJwt;
import com.example.demo.jwt.AuthTokenFilter;
import com.example.demo.repositories.RenterRepository;
import com.example.demo.services.ProductServiceImpl;
import com.example.demo.services.RenterService;
import com.example.demo.services.UserDetailsImpl;
import com.example.demo.services.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
        // securedEnabled = true,
        // jsr250Enabled = true,
        prePostEnabled = true)public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    RenterService renterService;

    @Autowired
    UserDetailsServiceImpl userDetailsService;

    @Autowired
    ProductServiceImpl productService;

    @Autowired
    private AuthEntryPointJwt unauthorizedHandler;

    @Bean
    public AuthTokenFilter authenticationJwtTokenFilter() {
        return new AuthTokenFilter();
    }

    @Override
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests().antMatchers("/cubancoder/multirenter/**","/v2/api-docs","/configuration/ui",
                "/swagger-resources/**",
                "/configuration/security",
                "/swagger-ui.html",
                "/webjars/**").permitAll()
                .antMatchers("/api/test/**").permitAll()
                .anyRequest().authenticated();
        http.logout().logoutUrl("/cubancoder/multirenter/logout");
        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

Any help would be amazing .Please!!

My solution was:

UserDetailsImpl currentUser = (UserDetailsImpl) authentication.getPrincipal();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM