使用JWT实现Spring Boot令牌认证

如何可靠实现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;
}

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

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;

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);
}
}

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

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

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


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

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

Spring Boot token authentication using JWT