Apache Shiro 和 JWT 实施问题,当每个用户使用不同的秘密时

Apache Shiro And JWT Implementation Problems When Using Different Secret Per User

我正在开发一个应用程序,我在其中实现了 java Restful 使用 Apache Shiro 身份验证的后端服务。我现在可以让用户使用我的数据库支持的密码和盐成功注册和登录。现在我想通过添加 JWT 身份验证来改进这一点。

场景是:

  1. 用户尝试使用用户名和密码登录
  2. 成功发送凭据并且shiro登录用户后,后端生成一个jwt令牌并将其发送回客户端
  3. 在每个新请求中,客户端将在上一步中收到的 jwt 令牌发送到服务器以进行身份​​验证。
  4. Shiro Filter 检查请求中包含的令牌。如果有效,则显示 return 错误消息。

为了实现这个功能,我遵循了: JSON Web Token with Apache Shiro

整个工作由 JWTVerifyingFilter 执行:

public class JWTVerifyingFilter extends AccessControlFilter {

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse arg1, Object arg2) throws Exception {
        boolean accessAllowed = false;
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String jwt = httpRequest.getHeader("Authorization");
        if (jwt == null || !jwt.startsWith("Bearer ")) {
            return accessAllowed;
        }
        jwt = jwt.substring(jwt.indexOf(" "));
        String username = Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary("secret"))
                .parseClaimsJws(jwt).getBody().getSubject();
        String subjectName = (String) SecurityUtils.getSubject().getPrincipal();
        if (username.equals(subjectName)) {
            accessAllowed = true;
        }
        return accessAllowed;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest arg0, ServletResponse arg1) throws Exception {
        HttpServletResponse response = (HttpServletResponse) arg1;
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        return false;
    }

}

但是,我面临以下问题:

如您所见,用于生成 jwt 签名的秘密对于所有用户都是相同的 'secret'。在我发现的关于 jwt 令牌签名生成和身份验证的每个示例中,它们在生成 jwt 签名时对所有用户使用相同的秘密。

在我的实现中,我想使用数据库中每个用户存储的盐来生成 jwt 签名,然后对其进行验证。因此,当为每个用户生成 jwt 时,我使用以下函数,该函数使用用户 salt 创建 jwt 签名。

    public class JWTProvider {

    private JWTProvider() {

    }

    public static String getJWTToken(User user) {

        System.out.println("JWT Provider FIRED");
        SignatureAlgorithm sigAlg = SignatureAlgorithm.HS512;

        byte[] apiKeySecretBytes = Base64.decode(user.getSalt());

        Key signingKey = new SecretKeySpec(apiKeySecretBytes, sigAlg.getJcaName());
        Date date = new Date();
        JwtBuilder builder = Jwts.builder()
                .setSubject(user.getUsername())
                .claim("FirstName", user.getFirstName())
                .claim("LastName", user.getLastName())
                .setIssuedAt(date)
                .setExpiration(new Date(date.getTime() +  24 * 60 * 60 * 1000)) //Expires in One Days
                .signWith(sigAlg, signingKey);

        System.out.println("Generated JWT: " + builder.compact());
        return builder.compact();
    }

    }

在我的例子中,为了验证 jwt 签名,我需要为每个用户获取 salt。因此,我将 JWTVerifyingFilter 的实现更改为以下内容:

public class JWTVerifyingFilter extends AccessControlFilter {

    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) {
        System.out.println("JWT Verifier Fired.....");
        HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String jwt = httpRequest.getHeader("Authorization");
        if (jwt == null || !jwt.startsWith("Bearer ")) {
            System.out.println("DEn e brika prama: ");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }
        System.out.println("Found Something");
        jwt = jwt.substring(jwt.indexOf(" "));
        System.out.println("JWT: " + jwt);

        User user = (User) SecurityUtils.getSubject().getPrincipal();
        System.out.println("MMMMMMMMMMMMMM " + user.getUsername() + "jhhhhhhhhhhhhhh" + user.getSalt());

        String subjectName = ((User) SecurityUtils.getSubject()).getPrincipal();
        System.out.println("Subject: " + subjectName);
        String username = Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary("secret"))
                .parseClaimsJws(jwt).getBody().getSubject();
        System.out.println("UserNAeme: " + username);
        System.out.println("Subject: " + subjectName);
        if (username.equals(subjectName)) {
            response.setStatus(HttpServletResponse.SC_OK);
            return true;
        }
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        return false;
    }
}

然而,Apache Shiro 的 SecurityUtils.getSubject() return 是一个 null 对象。我试图实现一个 Singleton Class 以始终从 SecurityUtils 获取相同的主题(如果有一个主题 return 则调用 SecurityUtils.getSubject())但没有成功。在后来的实现中,只有一个用户可以登录系统。使用另一个浏览器,后端报告的用户已经使用先前由其他浏览器登录的用户的凭据登录。

问题:

  1. 在所有用户的 jwt 身份验证中始终使用相同的秘密是否可以?
  2. apache shiro 在哪里保存登录用户的信息,我如何从 restful 后端 java 服务访问它们??
  3. 每个用户使用不同的 jwt 密码的 Apache Shiro 的任何完整示例?实现这个的最佳方法是什么。
  4. 我是否需要像 redis 这样的内存存储来让经过身份验证的用户在登录时保留他们的 salt 并在后续请求中从那里获取他们的 salt?

提前感谢您的回答。

问题出在我的 shiro.ini

我启用了选项

securityManager.sessionManager.sessionIdCookieEnabled = false

我把它注释掉了,一切正常。现在我可以通过 shiro 的 SecurityUtils.getSubject();

访问主题