JWT 身份验证导致与常规身份验证发生冲突。我无法从登录页面登录

Posted

技术标签:

【中文标题】JWT 身份验证导致与常规身份验证发生冲突。我无法从登录页面登录【英文标题】:JWT Authentication causes conflicts with regular authentication. I can't log in from the login page 【发布时间】:2021-05-16 23:19:54 【问题描述】:

在谈到 Spring Security 时,我是一个新手,特别是 JWT 和 CORS,所以如果我没有把这件事说清楚,我提前道歉。

我们被要求制作一个模拟私人诊所网站的应用程序,患者可以在该网站上预约医生并从药房购买产品。医生可以在数据库中介绍有关其患者的信息。我们的项目也有一个 Restful API,可以通过移动应用程序(或 Postman)访问。 API 的作用是显示我们存储在数据库中的产品列表。

所有用户都可以通过使用 Spring Security 的登录表单登录。另一方面,如果我们想要检索我们的 API 的信息,除了 Spring Security 之外,还使用了 CORS 和 JWT。

当我设置一个自定义授权过滤器时,问题就出现了,我们的老师让我们这样做(我已经评论了这样做的行)。我们可以完美地使用 Postman 访问我们的 API:我们使用管理员用户登录并将授权令牌传递给我们的 API 路由,作为回报,我们得到产品列表。但是当过滤器工作时,我们不能再使用我们网站的登录表单进行身份验证。整个过程是这样的:

    应用程序从主页开始 (localhost:8080/inicio)。 在主页上有一个“登录”按钮,当用户未通过身份验证时会出现。单击它会将我们带到登录表单。 在登录表单 (localhost:8080/auth/login) 中,我们填写所有必要的字段,以便我们以数据库用户身份登录(在本例中,用户名:admin,密码:admin)。 我们提交表单,该表单将我们带到负责身份验证过程的请愿书 (localhost:8080/login/login-post)。 在进程结束时,我们被重定向回主页。当用户通过身份验证时,“登录”按钮应显示为“注销”。但事实并非如此。我们无法导航到经过身份验证的用户不应访问的其他页面。

控制台不提供任何错误消息,它所做的只是将我带回主页,而无需对用户进行身份验证。

这是我的 Spring Security 配置类:

@Autowired
@Qualifier("userService")
private UserService userService;

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


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


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


@Override
protected void configure(HttpSecurity http) throws Exception 
    http
    .csrf().disable()
    // .addFilterAfter(new JWTAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class)
    .authorizeRequests()
        .antMatchers("/", "/css/**",  "/img/**", "/js/**", "/vendor/**", "/inicio/**", "/pacientes/altaPaciente/**", "/pacientes/addPaciente/**", "/auth/**").permitAll()
        .antMatchers(HttpMethod.GET, "/authRest/**").permitAll()
        .antMatchers(HttpMethod.POST, "/authRest/**").permitAll()
        .anyRequest().authenticated()
        .and()
    .formLogin()
        .loginPage("/auth/login")
        .defaultSuccessUrl("/inicio/", true)
        .loginProcessingUrl("/auth/login-post")
        .permitAll()
        .and()
    .logout()
        .logoutUrl("/logout")
        .logoutSuccessUrl("/auth/login?logout")
        .permitAll();

还有我的 JWT 授权过滤器:

private final String HEADER = "Authorization";
private final String PREFIX = "Bearer ";
private final String SECRET = "mySecretKey";

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
        throws ServletException, IOException 
    try 
        if (checkJWTToken(request, response)) 
            Claims claims = validateToken(request);
            if (claims.get("authorities") != null) 
                setUpSpringAuthentication(claims);
             else 
                SecurityContextHolder.clearContext();
            
         else 
            SecurityContextHolder.clearContext();
        
        chain.doFilter(request, response);
     catch(ExpiredJwtException | UnsupportedJwtException | MalformedJwtException e) 
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        ((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
        return;
    


private Claims validateToken(HttpServletRequest request) 
    String jwtToken = request.getHeader(HEADER).replace(PREFIX, "");
    return Jwts.parser().setSigningKey(SECRET.getBytes()).parseClaimsJws(jwtToken).getBody();


private void setUpSpringAuthentication(Claims claims) 
    @SuppressWarnings("unchecked")
    List<String> authorities = (List<String>) claims.get("authorities");
    
    UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
            claims.getSubject(), 
            null, 
            authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList())
    );
    SecurityContextHolder.getContext().setAuthentication(auth);


private boolean checkJWTToken(HttpServletRequest request, HttpServletResponse res) 
    String authenticationHeader = request.getHeader(HEADER);
    if (authenticationHeader == null || !authenticationHeader.startsWith(PREFIX)) 
        return false;
    
    return true;

编辑:根据要求,这是我尝试使用 Web 表单作为数据库中的现有用户登录时得到的日志:https://pastebin.com/7SYX2MZF

【问题讨论】:

为什么在 Spring Security 中已经内置了一个自定义 jwt 过滤器? docs.spring.io/spring-security/site/docs/current/reference/… 这是我们老师向我们展示的用于使 JWT 身份验证工作的一个,我们甚至不知道 Spring Security 一开始就有一个。但是,如果它已经在 Spring Security 配置中定义,那么它对我的 Resful API 的安全性没有任何作用,我可以使用我想要访问它的任何授权令牌。 在目前的状态下,这个问题需要更清楚the only one working is the JWT Authentication 可能意味着任何事情,EXACTLY 不起作用,明确的重现步骤。可以登录吗?发布 spring 安全调试日志,并发布您的请求是什么样的。没有日志或重现步骤,没有人能够回答。 我会尽力重做我的问题,所以它会变得更清楚,但正如我所说,这是一个我不太熟悉的话题,我很难理解。 您可以通过激活调试日志开始***.com/a/47729991/1840146 重新启动服务器,执行您的请求,然后使用调试日志更新问题 【参考方案1】:

故障可能是(经过讨论)

这里的某个地方:

if (checkJWTToken(request, response)) 
    Claims claims = validateToken(request);
    if (claims.get("authorities") != null) 
         setUpSpringAuthentication(claims);
     else 
        SecurityContextHolder.clearContext();
    
 else 
        SecurityContextHolder.clearContext();

检查checkJWTToken 中是否存在Authorization 标头,如果没有,则清除当前的SecurityContext,这意味着它将删除存在的任何主体。

这将删除以前登录的任何人,这反过来又是最初登录时构造的主体。

所以你登录,安全上下文由主体填充,然后在下一个过滤器中突然被删除。

【讨论】:

以上是关于JWT 身份验证导致与常规身份验证发生冲突。我无法从登录页面登录的主要内容,如果未能解决你的问题,请参考以下文章

无法让 JWT 身份验证与 socket.io 一起使用

Django 通道 JWT 身份验证

常用中间件 vs 身份验证 api

使用JWT身份验证缓存API请求

使用 Jwt 的基于令牌的身份验证无法授权

.Net Core JWT 身份验证与自定义 API 密钥中间件