【问题标题】:Spring Security JWT Authorization returning 401 on endpointsSpring Security JWT Authorization 在端点上返回 401
【发布时间】:2021-11-11 21:14:33
【问题描述】:

我可以注册一个用户,然后使用这些凭据登录并接收一个包含用户 ID、用户名和用户角色的有效 JWT 令牌。

这是具有 2 个角色(USER 和 ADMIN)的令牌的屏幕截图 jwt.io result

我将 2 个角色(USER 和 ADMIN)手动插入到我的角色表中,以便创建的每个用户在创建后都会自动分配一个 ROLE_USER。

问题是,现在我想根据用户拥有的角色访问 URL,我在尝试授予访问权限的端点上收到 401 未授权。

这是来自具有用户和管理员角色的用户的邮递员响应,但我仍然收到此 401。

postman response

这里是重要的类 我的角色实体有名称和描述字段 我的存储库接口有一个 findByXXX 方法


用户.java


@Entity
@Table(name="user")
public class User implements UserDetails{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true)
    @NotBlank(message = "Username is required")
    private String username;

    @NotBlank(message = "Password is required")
    private String password;
    @Transient
    private String confirmPassword;

    private boolean enabled = true;

    @JsonFormat(pattern = "yyyy-mm-dd")
    private Date createdAt;

    // I want to load all the roles of users + the user itself once requested for
    @ManyToMany(fetch=FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinTable(name="user_roles",
        joinColumns = {
            @JoinColumn(name="user_id")
        },
        inverseJoinColumns = {
            @JoinColumn(name="role_id")
    })
    private Set<Role> roles = new HashSet<>();

    
    // Constructor
    // Getters and setteres


    @Override
    @JsonIgnore
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

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

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

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



}


SecurityConfig.java

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

    @Autowired
    private JwtAuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter();
    };

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

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/api/users/register", "/api/users/login", "/api/users/all").permitAll()
                .antMatchers("/api/users/user/**").hasRole("USER")
                .antMatchers("/api/users/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                // handles the json exception response if details are incorrect with an appropriate message
                .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);

        httpSecurity.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

    }
}

用户服务.java


@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Autowired
    private RoleService roleService;


    public User saveUser(User user) {

        Role userRole = roleService.findByRoleName("USER");
//        Role adminRole = roleService.findByRoleName("ADMIN");
        Set<Role> roles = new HashSet<>();
        try {
            user.setPassword(passwordEncoder.encode(user.getPassword()));
            user.setUsername(user.getUsername());
            user.setConfirmPassword("");
            roles.add(userRole);
            user.setRoles(roles);

            return userRepository.save(user);
        } catch(Exception e) {
            throw new UsernameAlreadyExistsException("User with username " + user.getUsername() + " already exists!");
        }
    }


    public List<User> findAll() {
        List<User> list = new ArrayList<>();
        userRepository.findAll().iterator().forEachRemaining(list::add);
        return list;
    }

}

UserDetailsS​​erviceImpl.java

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);

        if(user == null) {
            throw new UsernameNotFoundException("User not found");
        }
        return user;
    }
    
    private Set getAuthority(User user) {
        Set<SimpleGrantedAuthority> authorities = new HashSet<>();
        user.getRoles().forEach(role -> {
            authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
        });
        return authorities;
    }
}


JwtTokenProvider.java


@Component
public class JwtTokenProvider {

    private final String SECRET = "SECRET";
    // Generate the token
    public String generateToken(Authentication authentication) {
        User user = (User) authentication.getPrincipal();
        Date now = new Date(System.currentTimeMillis());

        Date expiryDate = new Date(now.getTime() + 300_000);

        String userId = Long.toString(user.getId());

        // this is what holds the token
        // add roles in claims
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", (Long.toString(user.getId())));
        claims.put("username", user.getUsername());
        claims.put("roles", user.getRoles());

        return Jwts.builder().setSubject(userId).setClaims(claims).setIssuedAt(now).setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, SECRET).compact();
    }

    // Validate the token
    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
            return true;
        } catch (SignatureException ex) {
            System.out.println("Invalid JWT Signature");
        } catch (MalformedJwtException ex) {
            System.out.println("Invalid JWT Token");
        } catch (ExpiredJwtException ex) {
            System.out.println("Expired JWT Token");
        } catch (UnsupportedJwtException ex) {
            System.out.println("Unsupported JWT token");
        } catch (IllegalArgumentException ex) {
            System.out.println("JWT claims string is empty");
        }
        return false;
    }

    public String getUsernameFromJWT(String token) {
        Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
        return claims.getSubject();
    }
}


JwtAuthenticationFilter.java


public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private JwtTokenProvider tokenProvider;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

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

                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userDetails, null, Collections.emptyList());

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

    }

    private String getJWTFromRequest(HttpServletRequest request) {
        // Header Authorization: Bearer token
        String bearerToken = request.getHeader("Authorization");

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

        return null;
    }
}

UserController.java


@RestController
@RequestMapping("/api/users")
@CrossOrigin
public class UserController {

    private UserService userService;
    private UserDetailsServiceImpl userDetailsService;
    private UserValidator userValidator;
    private ErrorValidationService errorValidationService;
    private JwtTokenProvider tokenProvider;
    private AuthenticationManager authenticationManager;

    @Autowired
    public UserController(UserService userService, UserDetailsServiceImpl userDetailsService, UserValidator userValidator, ErrorValidationService errorValidationService, JwtTokenProvider tokenProvider, AuthenticationManager authenticationManager) {
        this.userService = userService;
        this.userDetailsService = userDetailsService;
        this.userValidator = userValidator;
        this.errorValidationService = errorValidationService;
        this.tokenProvider = tokenProvider;
        this.authenticationManager = authenticationManager;
    }

    // I want to allow role based access to these URLs
    @GetMapping("/all")
    public String welcomeAll() {
        return "Anyone can view this!";
    }

    @GetMapping("/admin")
    public String adminPing(){
        return "Only Admins Can view This";
    }

    @GetMapping("/user")
    public String userPing(){
        return "Any User Can view This";
    }


    // Get all users
    @GetMapping("/userList")
    public ResponseEntity<?> getAllUsers() {
          return ResponseEntity.ok(userService.findAll());
    }

    
    @PostMapping("/register")
    public ResponseEntity<?> register(@Valid @RequestBody User user, BindingResult result) {

        userValidator.validate(user, result);
        ResponseEntity<?> errorMap = errorValidationService.validationService(result);
        if(errorMap != null) return errorMap;

        User newUser = userService.saveUser(user);

        return new ResponseEntity<User>(newUser, HttpStatus.CREATED);
    }

    @PostMapping("/login")
    public ResponseEntity<?> login(@Valid @RequestBody LoginRequest loginRequest, BindingResult result) throws Exception {

        System.out.println("Entering /login");
        ResponseEntity<?> errorMap = errorValidationService.validationService(result);
        if(errorMap != null) return errorMap;

        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));

        SecurityContextHolder.getContext().setAuthentication(authentication);

        String jwt = "Bearer " + tokenProvider.generateToken(authentication);
        return ResponseEntity.ok(new JwtLoginSuccessResponse(true, jwt));
    }

}



如果有人可以让我知道为什么会这样,以及如何以更好的方式改变和抽象事物,我将不胜感激。由于网上有很多不同的实现,我不确定我做错了什么。

【问题讨论】:

  • 在 Spring Security 中启用 DEBUG 日志记录并查看哪个过滤器阻止了请求。这会给你一个提示。您可能必须调试过滤器中的相关方法以查看发生了什么问题。如果您可以通过自动化测试涵盖此场景,它也将有助于调查问题。将使重现和调试变得更加容易

标签: java spring spring-boot jwt authorization


【解决方案1】:

使用hasAnyAuthority 代替hasRole 并从SimpleGrantedAuthority 中删除"ROLE_"

  • 您的代码
.antMatchers("/api/users/user/**").hasRole("USER")
//...and...
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
  • 试试这个
.antMatchers("/api/users/user/**").hasAnyAuthority("USER")
//...and...
authorities.add(new SimpleGrantedAuthority(role.getName()));
  • 但是我没有看到你在哪里使用方法UserDetailsServiceImpl.getAuthority?您将authorities 的空列表放入UsernamePasswordAuthenticationToken 中的JwtAuthenticationFilter
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userDetails, null, Collections.emptyList());

你应该输入authorities

【讨论】:

  • 我现在知道了,谢谢。春天真的很混乱,尤其是对于像我这样的新人
猜你喜欢
  • 2018-11-06
  • 2018-04-26
  • 2017-04-26
  • 2020-11-05
  • 2020-07-24
  • 2020-07-25
  • 2019-02-21
  • 2021-07-24
  • 2017-10-11
相关资源
最近更新 更多