使用JWT实现Spring Boot令牌认证

17-02-21 banq
              

如何可靠实现Rest服务和客户端之间的用户身份验证/授权的方式?

最原始的方式是为每个请求发送基本的HTTP验证头部凭证信息(用户名/密码),但这需要将这些凭证保存在内存中,服务必须每次检查这些凭证(口令哈希操作是很费CPU的昂贵操作)。所以这不是最好的主意。

还有一种方式,REST服务使用令牌系统实现安全验证。标准令牌系统在成功登录时返回“令牌”(只是一个长的唯一字符串的随机字符,例如GUID)。客户端然后在每次请求的HTTP授权报头中发送此令牌。在处理请求的每个服务中,然后在后端服务器中查找上下文分解分析该令牌。上下文可以存储在DB中,也可从Redis缓存中检索,或简单地存储在内存的哈希表中。这种方法的缺点是,对于每个REST方法,您都将需要在数据库或缓存中执行查找。

JSON Web Tokens(或简称JWT)也是一种令牌,它不仅是用户唯一的令牌,而且还包含该用户所需的任何信息,即所谓的声明。最基本的声明是“Subject主题”(基本上是唯一的用户ID),但是令牌可以扩展为包括任何你想要的信息,可以是api访问权限或用户角色; 您可以在用户登录时简单地向用户添加“角色Role”数组,其中包含“用户”权限和“管理”权限。只要客户端将JWT发送到服务器,就可以从JWT检索这些声明。

显然,这个令牌是纯文本,添加设置很方便;JWT使用安全密钥加密(仅服务器已知)或签名。JWT使用的最常见的方法是通过签名方式使用。这个JWT的“签名”位称为JWS,JSON Web Signature。当您希望客户端能够读取的令牌中信息时,可以使用此方法。base-64编码用于签名但不会加密。

另一种方法是使用JWE,JSON Web Encryption。使用JSON Web加密,您可以使用行业标准加密方法来加密令牌的内容。只有服务器可以创建和解密令牌,因此这意味着客户端无法读取或更改内容,因为它不知道怎么加密的。

让我们来看一些实际的代码。开源小例子项目按这里,使用spring boot实现JWT的案例。

首先,我们从登陆开始。它由/ user / login路由处理:

@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(@RequestBody User login) throws ServletException {

    String jwtToken = "";

    if (login.getEmail() == null || login.getPassword() == null) {
        throw new ServletException("Please fill in username and password");
    }

    String email = login.getEmail();
    String password = login.getPassword();

    User user = userService.findByEmail(email);

    if (user == null) {
        throw new ServletException("User email not found.");
    }

    String pwd = user.getPassword();

    if (!password.equals(pwd)) {
        throw new ServletException("Invalid login. Please check your name and password.");
    }

    jwtToken = Jwts.builder().setSubject(email).claim("roles", "user").setIssuedAt(new Date())
            .signWith(SignatureAlgorithm.HS256, "secretkey").compact();

    return jwtToken;
}

<p>

当用户使用正确的密码登录时,他会收到以下令牌:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtQGFib3VsbGFpdGUubWUiLCJyb2xlcyI6InVzZXIiLCJpYXQiOjE0ODYyMDYwNjd9.nbppPf6DIl3f3d79EGouJ1cN599R0JELjAiGHXUqSD0

这个“令牌”之后会用于客户端对服务器的后续API调用系列请求。这里的标准方法是发送具有“Bearer”令牌的授权报头。HTTP头部将是:

authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtQGFib3VsbGFpdGUubWUiLCJyb2xlcyI6InVzZXIiLCJpYXQiOjE0ODYyMDU3MTh9.N3quHsQvaqzpCLIPhm7-5_gvmK9TxVrKygCEiis27h

SpringBootJwtApplication会配置一个过滤器。Servlet过滤器可以使用HttpRequests做各种事情,我们将使用这个过滤器来保护我们的“安全”端点。你可以看到SpringBootJwtApplication类将我们的JwtFilter配置为只对“/ secure / *”端点执行操作:

    final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
    registrationBean.setFilter(new JwtFilter());
    registrationBean.addUrlPatterns("/secure/*");

    return registrationBean;
<p>

2

这样,当我们调用/user/login没有授权头时,它不会出错。过滤器负责检查正确的授权头是否存在以及Http头部中的令牌是否有效:

public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain)
        throws IOException, ServletException {

    final HttpServletRequest request = (HttpServletRequest) req;
    final HttpServletResponse response = (HttpServletResponse) res;
    final String authHeader = request.getHeader("authorization");

    if ("OPTIONS".equals(request.getMethod())) {
        response.setStatus(HttpServletResponse.SC_OK);

        chain.doFilter(req, res);
    } else {

        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            throw new ServletException("Missing or invalid Authorization header");
        }

        final String token = authHeader.substring(7);

        try {
            final Claims claims = Jwts.parser().setSigningKey("secretkey").parseClaimsJws(token).getBody();
            request.setAttribute("claims", claims);
        } catch (final SignatureException e) {
            throw new ServletException("Invalid token");
        }

        chain.doFilter(req, res);
    }
}
<p>

Jwt解析器使用与签名相同的密钥来检查令牌签名。如果密钥有效,我们就在请求对象中存储包含一些用户信息(电子邮件,角色)的“Claims”,以便API端点可以使用它。

最后但并非最不重要的是,我们调用chain.doFilter这样,没有它,请求不会传递到我们的控制器。

现在我们有了过滤器,我们可以定义一些漂亮的超级安全的API方法,比如:

@RequestMapping("/user/users")
public String loginSuccess() {
    return "Login Successful!";
}
<p>

您可以使用像Postman这样的REST客户端演示这个例子。

Spring Boot token authentication using JWT

              

6