获取 401 Unauthorized 即使用户已通过身份验证(Spring 安全)

Getting 401 Unauthorized Even when the user is authenticated (Spring Security)

我正在做一个简单的项目,它有 2 个定义的角色 Admin 和 User,admin 角色有权通过他们的用户名查看用户,但是当我以管理员身份登录并点击 /admin/users/usernames/{username} 我得到 401 Unauthenticated 你可以在这里看到请求 postman request

以下实现有什么问题?

**登录方式**

    @Override
    public ResponseEntity<User> login(String username, String password) {
        authenticate(username,password);
        User loginUser = findUserByUsername(username);
        UserPrinciple userPrinciple = new UserPrinciple(loginUser);
        HttpHeaders tokenHeader = getJwtToken(userPrinciple);
        return new ResponseEntity<>(loginUser,tokenHeader, HttpStatus.OK);
    }
    private void authenticate(String username, String password) {
        authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username,password));
    }

    private HttpHeaders getJwtToken(UserPrinciple userPrinciple) {
        String token= jwtTokenProvider.generateJWTToken(userPrinciple);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add(SecurityConsts.JWT_TOKEN_HEADER,token);
        return httpHeaders;
    }

用户 ReST 控件

@RestController
@RequestMapping("/admin/users")
public class UserController {
    private UserService userService;
    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/")
    public List<User> getUsers() {
        return userService.getUsers();
    }

    @GetMapping("/emails/{email}")
    public User findUserByEmail(@PathVariable String email) {
        return userService.findUserByEmail(email);
    }

    @GetMapping("/usernames/{username}")
    public User findUserByUsername(@PathVariable String username) {
        return userService.findUserByUsername(username);
    }

    @JsonView(BodyView.BasicUser.class)
    @PostMapping("/register")
    public User addUser(@RequestBody User user) throws UsernameExistsException {
        return userService.addUser(user);
    }
    @JsonView(BodyView.BasicUser.class)
    @PostMapping("/login")
    public ResponseEntity<User> login(@RequestBody User user) {
        return userService.login(user.getUsername(), user.getPassword());
    }
}

POM.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>FM6-App</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>FM6-App</name>
    <description>FM6-App</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.19.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.17</version>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.17</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

网络安全配置器适配器
public 网址:

public static final String[] PUBLIC_URLS ={"/users/admin/login/**","/admin/users/register/**","/demandes/**","/criteres/**"};
@Configuration
@EnableWebSecurity
@ComponentScan
@EnableGlobalMethodSecurity(
        prePostEnabled = true,
        securedEnabled = true,
        jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JWTAuthorizationFilter jwtAuthorizationFilter;
    @Autowired
    private JWTAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    @Autowired
    private JWTAccessDenielHandler jwtAccessDenielHandler;
    @Autowired
    private UserService userService;
    @Autowired
    private PasswordEncoder passwordEncoder;


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .cors()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/admin/users/login/**").permitAll()
                .antMatchers(SecurityConsts.PUBLIC_URLS).permitAll()
                .antMatchers("/admin/**").hasAnyAuthority("ROLE_ADMIN")
                .antMatchers("/utilisateur/**").hasAnyAuthority("ROLE_USER")

//                .anyRequest().authenticated()
                .and()
                .exceptionHandling().accessDeniedHandler(jwtAccessDenielHandler)
                .authenticationEntryPoint(jwtAuthenticationEntryPoint)
                .and()
                .addFilterBefore(jwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class);
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder);
    }

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

JWT 授权过滤器

@Component
public class JWTAuthorizationFilter extends OncePerRequestFilter {
    @Autowired
    private JWTTokenProvider jwtTokenProvider;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if(request.getMethod().equalsIgnoreCase(SecurityConsts.OPTION_HTTP_METHOD)){
            response.setStatus(SecurityConsts.OK.value());
        }else if(request.getRequestURI().equals("/users/login")){
            filterChain.doFilter(request,response);
        }
        else {
            String autorizationHeader =  request.getHeader(HttpHeaders.AUTHORIZATION);
            if(autorizationHeader == null || !autorizationHeader.startsWith(SecurityConsts.TOKEN_PREFIX)){
                filterChain.doFilter(request,response);
                return;
            }
            String token = autorizationHeader.substring(SecurityConsts.TOKEN_PREFIX.length());
            String username = jwtTokenProvider.getSubject(token);
            if(jwtTokenProvider.isTokenValid(username,token) && SecurityContextHolder.getContext().getAuthentication() == null){

                List<GrantedAuthority> authorities = jwtTokenProvider.getAuthorities(token);
                Authentication authentication = jwtTokenProvider.getAuthentication(username,authorities,request);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }else {
                SecurityContextHolder.clearContext();
            }

        }
        filterChain.doFilter(request,response);
    }
}

跨域配置class

@Configuration
public class CorsConfig {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH")
                        .allowedHeaders("*")
                        .allowedOrigins("*");
            }
        };
    }
}

你能调试应用程序吗?你在评论javax.management.InstanceNotFoundException: org.springframework.boot:type=Admin,name=SpringApplication中提到的错误是IDE-related,有关于它的Whosebug post,所以这不是问题。

尝试调试代码并检查:

  1. 如果你真的到达 JWTAuthorizationFilter
  2. 如果您的用户在此处被识别为管理员用户:List<GrantedAuthority> authorities = jwtTokenProvider.getAuthorities(token);
  3. 检查您是否在 Authorization header 中向 Postman 传递了正确且最新的令牌;检查 url(在您提供的屏幕截图中 url 不正确,它是 useErnames
  4. SecurityConsts.TOKEN_PREFIX 的值是多少?它应该包括 space,即 'Bearer '

更新 基于下面关于我的第二个问题的评论(虽然我打算在运行时以调试模式检查值,但无论如何..)

String[] ADMIN_AUTHORITIES = {"ADMIN_ROLE"};

在安全配置中您正在寻找 hasAnyAuthority("ROLE_ADMIN"),但为您的管理员分配了 "ADMIN_ROLE"。措辞不同:ROLE_ADMIN 与 ADMIN_ROLE。 当您更新其中之一时,请注意 Spring 在某些情况下会自行将单词“ROLE”添加到权限名称中。所以尝试使用和不使用这个前缀。