You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot JWT登录返回401 Unauthorized及用户角色分配实现咨询

Hey there, let's tackle your two main issues step by step: fixing the 401 on login, and implementing role assignment for new users.

1. Fixing the 401 Unauthorized on /api/users/login

The root cause of your login failure is mostly related to how Spring Security interacts with your User entity, plus a small mismatch in JWT handling. Here's how to fix it:

1.1 Your User class doesn't implement UserDetails

Spring Security depends on the UserDetails interface to fetch critical authentication data (like password, enabled status, and user authorities). Right now your User entity doesn't implement this interface, so the authentication manager can't validate user credentials properly.

Fix: Update your User.java to implement UserDetails and override all required methods:

@Entity
@Table(name="user")
public class User implements UserDetails { // Add this interface
    // ... existing fields ...

    // Implement UserDetails methods
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // Convert roles to Spring Security's GrantedAuthority format
        return roles.stream()
                .map(role -> new SimpleGrantedAuthority(role.getName()))
                .collect(Collectors.toList());
    }

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

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

    @Override
    public boolean isAccountNonExpired() {
        return true; // Adjust based on your business rules
    }

    @Override
    public boolean isAccountNonLocked() {
        return true; // Adjust based on your business rules
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true; // Adjust based on your business rules
    }

    @Override
    public boolean isEnabled() {
        return this.enabled;
    }

    // ... existing constructors, getters, setters ...
}

1.2 Mismatch in JWT Subject and Username Retrieval

In JwtTokenProvider.generateToken(), you're setting the JWT subject to the user ID (setSubject(userId)), but in getUsernameFromJwt() you're fetching the subject as if it's a username. This will break token validation later, and contributes to authentication confusion.

Fix: Set the JWT subject to the username (or retrieve the username from the claims map):

// Option 1: Set subject to username in generateToken()
public String generateToken(Authentication authentication) {
    User user = (User) authentication.getPrincipal();
    Date now = new Date(System.currentTimeMillis());
    Date expiryDate = new Date(now.getTime() + 300_000);

    Map<String, Object> claims = new HashMap<>();
    claims.put("id", Long.toString(user.getId()));
    claims.put("username", user.getUsername());
    
    // Set subject to username instead of user ID
    return Jwts.builder()
            .setSubject(user.getUsername()) 
            .setClaims(claims)
            .setIssuedAt(now)
            .setExpiration(expiryDate)
            .signWith(SignatureAlgorithm.HS512, "SECRETSECRETSECRET")
            .compact();
}

// Keep getUsernameFromJwt as is - it now correctly fetches the username from the subject
public String getUsernameFromJwt(String token) {
    Claims claims = Jwts.parser()
            .setSigningKey("SECRETSECRETSECRET")
            .parseClaimsJws(token)
            .getBody();
    return claims.getSubject();
}

1.3 Quick Sanity Checks

  • Ensure your LoginRequest class has properly named fields (username and password) that match the JSON keys you're sending in Postman.
  • Confirm the password stored in the database is properly BCrypt-encoded (your UserService.saveUser already does this, which is correct).

2. Implementing Role Assignment for New Users

Here's a straightforward way to add role logic to your registration flow, and include roles in JWT tokens for authorization:

2.1 Assign Default Role on Registration

First, make sure you have a default role (like ROLE_USER) in your role database table (seed it manually or use a data loader if needed). Then update your UserService.saveUser() to assign this role to new users:

Modify UserService.java:

public User saveUser(User user) {
    try {
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        user.setUsername(user.getUsername());
        user.setConfirmPassword("");
        user.setEnabled(true);
        
        // Assign default role (e.g., ROLE_USER)
        Role defaultRole = roleService.findByRoleName("ROLE_USER");
        if (defaultRole != null) {
            user.getRoles().add(defaultRole);
        } else {
            throw new RuntimeException("Default role ROLE_USER not found in database");
        }
        
        return userRepository.save(user);
    } catch(Exception e) {
        throw new UsernameAlreadyExistsException("User with username " + user.getUsername() + " already exists!");
    }
}

2.2 Add Roles to JWT Claims

Update JwtTokenProvider.generateToken() to include the user's roles in the JWT claims, so they can be used for authorization later:

public String generateToken(Authentication authentication) {
    User user = (User) authentication.getPrincipal();
    Date now = new Date(System.currentTimeMillis());
    Date expiryDate = new Date(now.getTime() + 300_000);

    // Extract role names as strings
    List<String> roles = user.getRoles().stream()
            .map(Role::getName)
            .collect(Collectors.toList());

    Map<String, Object> claims = new HashMap<>();
    claims.put("id", Long.toString(user.getId()));
    claims.put("username", user.getUsername());
    claims.put("roles", roles); // Add roles to JWT claims

    return Jwts.builder()
            .setSubject(user.getUsername())
            .setClaims(claims)
            .setIssuedAt(now)
            .setExpiration(expiryDate)
            .signWith(SignatureAlgorithm.HS512, "SECRETSECRETSECRET")
            .compact();
}

2.3 Populate Authorities in JwtAuthenticationFilter

Right now your filter sets an empty list of authorities, which means even after login, users won't have their roles recognized for authorization. Fix this by using the user's actual authorities:

@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
    try {
        String jwt = getJWTFromRequest(httpServletRequest);
        if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
            String username = tokenProvider.getUsernameFromJwt(jwt);
            User userDetails = (User) userDetailsService.loadUserByUsername(username);
            
            // Use the user's actual authorities instead of an empty list
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                    userDetails, null, userDetails.getAuthorities()); 
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
    } catch (Exception ex) {
        logger.error("Could not set user authentication in security context", ex);
    }
    filterChain.doFilter(httpServletRequest, httpServletResponse);
}

2.4 Enable Role-Based Access Control

Now you can use Spring Security annotations to restrict access to your endpoints:

@GetMapping("/all")
public String welcomeAll() {
    return "Anyone can view this!";
}

@GetMapping("/admin")
@PreAuthorize("hasRole('ADMIN')") // Restrict to admin users only
public String adminPing(){
    return "Only Admins Can view This";
}

@GetMapping("/user")
@PreAuthorize("hasRole('USER')") // Restrict to regular users
public String userPing(){
    return "Any User Can view This";
}

After making these changes:

  1. Test the registration endpoint again - new users should have the ROLE_USER assigned automatically.
  2. Test the login endpoint - it should return a valid JWT token instead of 401.
  3. Test the role-protected endpoints by including the JWT token in the Authorization: Bearer <token> header.

内容的提问来源于stack exchange,提问作者ServletException

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.01 01:57:45