java - 如何在其他服务实现中从 Spring Security 接收经过身份验证的用户,而不是匿名用户。(Spring Security+JWT)

标签 java spring-boot spring-security jwt

开发者好。 我试图在我的应用程序中为经过身份验证的用户检索一些数据,以便在其他方法中实现它。这个应用程序使用 Spring Security 和 JWT,因此要做的第一件事是在类 UserDetailsImpl 上设置 Java 接口(interface) UserDetails 的实现如下:

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


}

有了 Userdetails 接口(interface)的实现,是时候也实现 UserDailsS​​ervice 接口(interface)了,以便让他们获得 UserDetails 对象,因此它的实现将是:

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.

因此下一步将设置所有逻辑来过滤在每个用户登录请求中创建的 token , 并从那里生成该 token 并触发 SecurityContextHolder 我可能能够访问经过身份验证的用户详细信息,因此:

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


}

然后创建登录有效负载,从用户交互中获取身份验证所需的所有数据 在类 LoginRequest 上处理。 服务的登录实现及其实现将以这种方式设置

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


然后 Controller 及其端点调用所有这些:

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


到此为止,用户登录完成,所有引用它的数据都正常。 在调试过程中,我检查实现带来了什么并且很好 enter image description here .

问题从这里开始

然后考虑到用户登录我想从其他服务实现访问它的数据 为了提升其他应用程序功能,因此可以说我有一项服务及其实现称为 ProductServiceImpl, 在这个类中,我初始化了为我带来所有产品的方法,但我也想知道哪个用户正在执行该请求,因此如果它被记录我需要所有数据,否则应用程序会做其他事情。 请记住,一旦用户使用 token 进行身份验证,就已经创建了一个 SecurityContextHolder 来设置该请求的用户详细信息,我想一切都会如此简单,只要我需要访问用户记录的数据就可以调用 SecurityContextHolder,对吗?

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


但是上下文持有者给我带来了一个匿名用户!!!不让我继续错误的原因是:

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]...

老实说,我完全不知道还能做什么! 在我的调试过程中为了检查:

选项 1 enter image description here

选项 2 enter image description here

最后但并非最不重要的是,在我的安全包中,它的类是这样设置的:

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

任何帮助都会很棒。拜托!!

最佳答案

我的解决方案是:

Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); UserDetailsImpl currentUser = (UserDetailsImpl) authentication.getPrincipal();

这样我就可以在任何类或方法中访问当前用户。

问候。

关于java - 如何在其他服务实现中从 Spring Security 接收经过身份验证的用户,而不是匿名用户。(Spring Security+JWT),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64927997/

相关文章:

java - 逐行将文本文件的整行存储在数组中(不是逐字;包括空格)

spring - LocalDate反序列化&序列化错误-jackson.databind.exc.InvalidDefinitionException : Cannot construct instance of java. time.LocalDate

spring - 具有多个实体查找器的通用 spring jpa 存储库

java - Spring Security - j_spring_security_check - HTTP 状态 403

java - Camel 聚合器如何工作

java - EXI 获取 JAXB 解码器

java - Spring boot - 在启动时禁用 Liquibase

java - Spring Security 忽略多个 HTTP 配置

java - Spring安全@PreAuthorize NullPointerException。为什么?

Java:当我实例化抽象类的子类时,它无法识别其父类(super class)的构造函数