如何可靠实现 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路由处理:
@ request Mapping(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客户端演示这个例子。
海报
0 条评论
118
本站已关闭游客评论,请登录或者注册后再评论吧~