如何使用后端的 JWT 实用程序从前端验证 JWT? (反应+ Springboot)

Posted

技术标签:

【中文标题】如何使用后端的 JWT 实用程序从前端验证 JWT? (反应+ Springboot)【英文标题】:How to validate JWT from frontend using a JWT Utility on the backend? (React + Springboot) 【发布时间】:2021-11-23 02:42:04 【问题描述】:

好的,所以我有一个 React 组件,如果本地存储中的 JWT 有效(意味着令牌未过期且它是正确的令牌),我只想加载该组件。目前,我只能检查其中一项是否已过期。

这是该组件中的componentDidMount 函数:

componentDidMount() 
    this._isMounted = true;

    // validate token
    const token = localStorage.getItem("token");
    AuthService.validateToken()
      .then((res) => 
        console.log("step 1");
        if (res == undefined || res == null || !token) 
          this.props.history.push("/login");
        
      )
      .then(() => 
        console.log("step 2");

        TicketService.getTickets().then((res) => 
          if (this._isMounted) 
            this.setState( tickets: res.data );
          
        );
      );
  

AuthService.validateToken() 看起来像这样:

  // IS token expired?
  validateToken() 
    console.log(" about to try validating");
    return AxiosInstance.get("authenticate")
      .then("did it")
      .catch((err) => console.log(err));
  

AxiosInstance 就是这样。我也会将代码分享给我的 Axios 实例:

const AxiosInstance = axios.create(
  baseURL: API_BASE_URL,
  headers:  Authorization: `Bearer $getTokenFromLocalStorage()` ,
);

function getTokenFromLocalStorage() 
  const token = localStorage.getItem("token");
  console.log("the token is -> " + token);
  if (token === null) 
    return undefined;
  
  return token;

最后,终点是这样的:

    // returns the username
    @GetMapping("/authenticate")
    public ResponseEntity<?> validateHeaderToken() throws Exception 

        String username = null;
        try 
            System.out.println("Attempting to validate token");
            String token = request.getHeader("Authorization");
            token = token.split("Bearer ")[1];
            username = jwtTokenUtil.extractUsername(token);

            // TODO : this should eventually use the .validateToken method once I figure out
            // how to pass in UserDetails
            if (!jwtTokenUtil.isTokenExpired(token)) 
                return ResponseEntity.ok(username);
             else 
                throw new Exception("Expired or Invalid Token");
            

         catch (Exception e) 
            return ResponseEntity.status(400).build();
        

    

如您所见,我只是在上面的函数中检查令牌是否过期^^。我想使用我的 JWT 实用程序来使用已经存在的 validateToken。这是我的 JWT 实用程序:

  public String extractUsername(String token) 
        // we set the username as the subject when we created the token so we can
        // extract the username from the claim like this
        return extractClaim(token, Claims::getSubject);
    

    public Date extractExpiration(String token) 
        return extractClaim(token, Claims::getExpiration);
    

    public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) 
        final Claims claims = extractAllClaims(token);
        // extract the claim that is attached to the token, if any
        return claimsResolver.apply(claims);
    

    private Claims extractAllClaims(String token) 
        return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
    

    public Boolean isTokenExpired(String token) 
        return extractExpiration(token).before(new Date());
    

    public String generateToken(UserDetails userDetails) 
        Map<String, Object> claims = new HashMap<>();
        // create token using the username as subject
        return createToken(claims, userDetails.getUsername());
    

    private String createToken(Map<String, Object> claims, String subject) 
        // Expiration is 10 hours from creation
        // subject is username
        return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
    

    public Boolean validateToken(String token, UserDetails userDetails) 
        final String username = extractUsername(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    

所以,所有有用的功能都在我的 JWT 实用程序中,但 validateToken() 需要 UserDetailstoken。我在哪里以及如何让UserDetails 传递给validateToken 函数??

编辑:用户详细信息已经加载,当我刷新页面时,我可以看到为登录的人打印的用户详细信息,但不知道如何从其他地方访问它。

编辑:MyUserDetailsService 看起来像这样:

@Service
public class MyUserDetailsService implements UserDetailsService 

    @Autowired
    private EmployeeServices employeeServices;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException 

        System.out.println("S is -> " + s);
        Employee employee = employeeServices.getEmployeeByUsername(s);
        System.out.println(employee);
        // if (employee == null) 
        // throw new UsernameNotFoundException("Username was not found. Could not log
        // in.");
        // 

        return new User(employee.getUsername(), employee.getPassword(), new ArrayList<>());
    

当我刷新页面时,每次都会调用它。我可以在每次页面刷新时看到S is -&gt; 打印到我的终端。这是我应该在 Cookie 中存储用户详细信息的地方吗?

【问题讨论】:

Spring 已内置 jwt 支持 3 年,为什么要构建自定义安全性?自定义自制安全是不好的做法。如果您要忽略框架提供的内容,为什么要使用安全框架。将 JWT 存储在本地存储中也是非常糟糕的做法。请阅读 OWASP 我是新手,正在学习教程。我现在计划将事情转化为最佳实践。您能否解释一下或者分享一个我可以查看的链接,该链接将解释需要更改哪些部分才能使用内置的 JWT 安全性? 使用 google 并找到 Spring Security 的文档并阅读 JWT 章节。 如果遵循教程,您应该在问题中发布教程。本教程正在教你一些不好的做法,如果你想学习 Spring Security,我建议你阅读官方文档,不要相信提供错误信息的编写不佳的教程。 【参考方案1】:

听起来您想要做的是将cookie中的数据与UserDetails中的数据进行比较。

大概您将UserDetails 存储在会话cookie 中,或者至少是用户的ID。在不确切知道会话 cookie 是如何形成的情况下,我无法给出确切的答案,但这里是它的要点。

在您可以访问传入请求对象的 validateHeaderToken 方法中,您应该:

    从传入请求的 cookie 中提取用户详细信息,或 从传入请求的 cookie 中提取 ID,并从您的数据库中查找相应的用户详细信息。

然后

    将用户详细信息与令牌一起传递给您的validateToken 方法。

关于标头中 cookie 的 MDN 文章:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie

【讨论】:

我打印了我的 cookie,但它是空的。我没有。我对我的问题进行了更新,请您查看一下吗? 好吧,您的用户详细信息以某种方式显示在页面上。他们如何到达那里?我不太清楚为什么你想将有效令牌中的内容与登录的用户名进行比较,因为这违背了不记名令牌的目的,但你说你想比较它们,所以您需要以某种方式将用户详细信息添加到身份验证请求中。

以上是关于如何使用后端的 JWT 实用程序从前端验证 JWT? (反应+ Springboot)的主要内容,如果未能解决你的问题,请参考以下文章

身份验证后请求其他服务时如何从 JWT 令牌获取用户名?

JWT身份验证及其替代方案,可实现Web应用程序后端的RESTfulness

如何验证请求标头、JWT 令牌

如何在 CodeIgniter 3.x 中为来自客户端的每个请求验证 JWT 令牌

如何使用 JWT 从前端(角度 4)将密钥传递到后端(节点 js)

如何将 JWT 令牌从 PHP 发送到 Angular?