Java 带有JWT令牌的Spring安全性在每个请求上加载Spring UserDetails对象

Java 带有JWT令牌的Spring安全性在每个请求上加载Spring UserDetails对象,java,spring,spring-boot,spring-security,jwt,Java,Spring,Spring Boot,Spring Security,Jwt,我正在学习使用JWT令牌和spring引导的spring安全性。我已经正确地执行了它,它工作得很好。但我对JwtRequestFilter的工作原理有一个疑问。为了了解SpringBoot的SpringSecurity,我浏览了几个网站,发现了同样的东西。让我来谈谈主要的疑问。 我在下面添加JwtRequestFilter文件 JwtRequestFilter.java @Component public class JwtRequestFilter extends OncePerRequest

我正在学习使用JWT令牌和spring引导的spring安全性。我已经正确地执行了它,它工作得很好。但我对JwtRequestFilter的工作原理有一个疑问。为了了解SpringBoot的SpringSecurity,我浏览了几个网站,发现了同样的东西。让我来谈谈主要的疑问。 我在下面添加JwtRequestFilter文件

JwtRequestFilter.java

@Component
public class JwtRequestFilter extends OncePerRequestFilter {

@Autowired
private JwtUserDetailsService jwtUserDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {

    final String requestTokenHeader = request.getHeader("Authorization");

    String username = null;
    String jwtToken = null;

    // JWT Token is in the form "Bearer token". Remove Bearer word and get
    // only the Token
    if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
        jwtToken = requestTokenHeader.substring(7);
        try {
            username = jwtTokenUtil.getUsernameFromToken(jwtToken);
        } catch (IllegalArgumentException e) {
            System.out.println("Unable to get JWT Token");
        } catch (ExpiredJwtException e) {
            System.out.println("JWT Token has expired");
        }
    } else {
        logger.warn("JWT Token does not begin with Bearer String");
    }
    // Once we get the token validate it.
    if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
         // This below line is calling on every request
        UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
        // if token is valid configure Spring Security to manually set
        // authentication
        if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
            userDetails, null, userDetails.getAuthorities());
            usernamePasswordAuthenticationToken
            .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            // After setting the Authentication in the context, we specify
            // that the current user is authenticated. So it passes the
            // Spring Security Configurations successfully.
            SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        }
    }
    filterChain.doFilter(request, response);
}
}

为了验证令牌,我们必须提供SpringUserDetails对象,我们从jwtUserDetailsService获取SpringUserDetails对象。所以这个过滤器将调用的每个请求,然后令牌验证将执行,我们必须在每个请求上调用jwtUserDetailsService。 我的疑问是在我的JWTUserDetails服务中,我添加了一些验证和用户权限。因此,在jwtUserDetailsService中,对每个请求重复以下步骤

  • 使用数据库中的用户名获取用户
  • 获取用户角色
  • 从数据库获取用户权限
  • 为userDetails分配权限
  • JwtUserDetailsService.java

    @Service("jwtUserDetailsService")
    @Transactional
    public class JwtUserDetailsService implements UserDetailsService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private IUserService service;
    
    @Autowired
    private MessageSource messages;
    
    @Autowired
    private RoleRepository roleRepository;
    
    @Override
    public UserDetails loadUserByUsername(String email)
      throws UsernameNotFoundException {
    
        User user = userRepository.findByEmail(email);
        if (user == null) {
            return new org.springframework.security.core.userdetails.User(
              " ", " ", true, true, true, true, 
              getAuthorities(Arrays.asList(
                roleRepository.findByName("ROLE_USER"))));
        }
    
        return new org.springframework.security.core.userdetails.User(
          user.getEmail(), user.getPassword(), user.isEnabled(), true, true, 
          true, getAuthorities(user.getRoles()));
    }
    
    private Collection<? extends GrantedAuthority> getAuthorities(
      Collection<Role> roles) {
    
        return getGrantedAuthorities(getPrivileges(roles));
    }
    
    private List<String> getPrivileges(Collection<Role> roles) {
    
        List<String> privileges = new ArrayList<>();
        List<Privilege> collection = new ArrayList<>();
        for (Role role : roles) {
            collection.addAll(role.getPrivileges());
        }
        for (Privilege item : collection) {
            privileges.add(item.getName());
        }
        return privileges;
    }
    
    private List<GrantedAuthority> getGrantedAuthorities(List<String> privileges) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        for (String privilege : privileges) {
            authorities.add(new SimpleGrantedAuthority(privilege));
        }
        return authorities;
    }
    }
    
    @Service(“jwtUserDetailsService”)
    @交易的
    公共类JwtUserDetailsService实现UserDetailsService{
    @自动连线
    私有用户存储库用户存储库;
    @自动连线
    私人服务;
    @自动连线
    私有消息源消息;
    @自动连线
    私人角色扮演者角色扮演者;
    @凌驾
    公共用户详细信息loadUserByUsername(字符串电子邮件)
    抛出UsernameNotFoundException{
    User=userRepository.findByEmail(电子邮件);
    if(user==null){
    返回新的org.springframework.security.core.userdetails.User(
    “,”,真的,真的,真的,真的,
    getAuthorities(Arrays.asList)(
    roleRepository.findByName(“角色用户”);
    }
    返回新的org.springframework.security.core.userdetails.User(
    user.getEmail(),user.getPassword(),user.isEnabled(),true,true,
    true,getAuthories(user.getRoles());
    }
    私人收藏
    因此,此筛选器将调用的每个请求都将进行令牌验证
    执行时,我们必须在每次请求时调用jwtUserDetailsService

    这是不正确的,因为您有一个条件
    if(SecurityContextHolder.getContext().getAuthentication()==null)

    因此,在第一次验证令牌时,您需要查询您的用户详细信息服务,获取所有授权并将其设置为安全上下文(您已经在这样做:
    SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);


    此外,使用JWT auth,您通常甚至不需要访问任何用户详细信息服务,因为理想情况下,所有授权都应该包含在令牌本身中。因此,您需要做的唯一一件事就是验证令牌的签名。

    一旦用户登录了他的身份验证,您就不需要在登录后再次进行db调用在身份验证过程中,应仅在令牌中设置角色的情况下检查y请求用户的授权,您需要验证令牌在每个请求中是否未被篡改 而不是通过从db调用加载用户详细信息来创建用户详细信息

    UserDetails UserDetails=this.jwtUserDetailsService.loadUserByUsername(用户名)

    您还可以在JWT声明中对用户的用户名和角色进行编码
    并通过解析JWT中的声明来创建UserDetails对象。

    既然spring security中已经有JWT筛选器,为什么还要实现自己的筛选器5@ThomasAndolf,很抱歉,我刚刚开始学习这个。所以我不知道spring security 5提供了这样的功能。你能提供任何链接让我通过吗很难理解这个概念。谢谢。太多的人在谷歌上搜索和阅读旧的教程。总是先看他们自己的文档。它真的很好@ThomasAndolf,谢谢我会研究它。@ThomasAndolf至少他们在谷歌上搜索:-)。我在看文档,但我在列表中没有看到JWT过滤器(我见过有人扩展UsernamePasswordAuthenticationFilter或OncePerFilter)。我在OAuth下看到一个令牌过滤器,但他没有在上面实现OAuth。我同意你的观点,在每个请求上它不应该调用jwtUserDetailsService。在这里,我的前端是有角度的,所以每次我得到请求SecurityContextHolder.getContext()时.getAuthentication()始终为空。从angular开始,我在头中传递令牌。我需要传递任何其他参数吗?据我所知,将在spring security之前调用筛选器,因此我们无法从SecurityContextHolder获取用户。因为我们将如何从spring security context holder中标识哪个用户身份验证对象。请告诉我如果我错了。您可能注册了错误的筛选器。是否可以添加
    SecurityConfig
    code(您实际将此筛选器添加到筛选器链的位置)?