SpringBoot采用jwt作为REST API安全机制的应用示例

网友投稿 268 2022-11-04


SpringBoot采用jwt作为REST API安全机制的应用示例

对外提供数据接口,安全性是必须考虑的问题。JWT无需存储客户端状态,也无须预先分配api_key、secrity_key,仅需校验数据签名,检查时间戳,就能保证请求的身份信息的完整性和防止重放攻击;而对于客户端来说,也没有跨域问题,不失为一种轻量的REST API安全机制。

一、概述

WebService有两种方案:基于SOAP的RPC和基于HTTP的REST API。REST API轻便,无状态,伸缩性更好,得到广泛使用,但缺少对安全性的直接支持,需要自己解决安全性问题。

安全性设计是个宏大的命题。这里说的REST API的安全性,只包括身份验证和授权策略2个方面,再有就是数据传输。数据传输现在一般都采用API来说,主要工作在服务器端,不会有什么大问题。所以REST API的安全性设计主要是身份验证。

REST API的身份验证方案,大约有这么几种:HTTP Basic、HTTP Digest、API KEY、Oauth 和 JWT。

1、HTTP Basic 每次请求,简单的将用户名和密码 base64 编码放到header中,传送给服务器。所以安全性较低。一定要配合ssl进行数据传输,否则无异于裸奔。

2.API KEY Client 端向服务端注册,获得api_key以及security_key。请求的时候,客户端根据 api_key、secrity_key、timestrap、rest_uri 采用 hmacsha256 算法得到一个 hash 值 sign,发送给服务端。

服务端收到该请求后,首先验证 api_key 和 security_key,接着验证 timestrap 是否超过时间限制,最后计算 sign 值,和传过来的sign 值做校验。这样的设计就防止了数据被篡改和重放攻击。

3.Oauth1.0a 或者 Oauth2 OAuth 协议适用于为外部应用授权访问本站资源的情况。可见拙作:​​​oAuth​​

4.JWT JWT(JSON Web Token)。第一次身份认证后,服务器生成一个token给客户端,客户端之后每次请求,都带上这个token。听上去与第一点的basic类似,但这个token有时间戳和签名,同样可以保证token的完整性和防止重放攻击。

不过,token里的信息,除了签名,其余部分也只是使用base64简单处理,跟明文无异,靠签名校验来保证安全性,因此也应该使用SSL进行数据传输,同时有效时间也不宜设置过久,30分钟足矣。(当然,原始token产生以后,我们自行进行加密也是可以的)

二、JWT的工作原理

1、工作过程

2、JWT的数据结构

大约类似这样:

它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行。

JWT 的三个部分依次如下。

Header(头部) Payload(负载) Signature(签名)

写成一行,就是下面的样子

Header.Payload.Signature

3、Signature Signature 部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

三、示例

1、服务器端代码结构(JAVA)

2、代码概述

控制器与外部交互,提供接口给客户端,包括身份认证接口、数据获取接口等;

身份认证时,系统验证客户端提交的用户名和密码,通过后生成JWT返回客户端;

客户端请求数据时,将JWT附在header中。拦截器会检查JWT,有效则返回数据。

3、代码明细1)TokenUser.java

public class TokenUser { private String name; private String password; private String ip; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; }}

2)JWT静态类及相关pom.xml

import com.auth0.jwt.JWT;import com.auth0.jwt.JWTCreator;import com.auth0.jwt.algorithms.Algorithm;import com.auth0.jwt.interfaces.DecodedJWT;import java.util.Calendar;import java.util.Map;public class JwtUtils { private static String SECRET = "略去"; //密钥 /* 为了保证令牌的安全性,jwt令牌由三个部分组成,分别是: header:令牌头部,记录了整个令牌的类型和签名算法 payload:令牌负荷,记录了保存的主体信息,比如你要保存的用户信息就可以放到这里 signature:令牌签名,按照头部固定的签名算法对整个令牌进行签名,该签名的作用是:保证令牌不被伪造和篡改 它们组合而成的完整格式是:header.payload.signature */ /** * 生成token * * @param map //传入payload * payload:令牌负荷,记录了保存的主体信息,比如你要保存的用户信息就可以放到这里 * @return 返回token */ public static String getToken(Map map) { JWTCreator.Builder builder = JWT.create(); map.forEach((k, v) -> { builder.withClaim(k, v); }); Calendar instance = Calendar.getInstance(); instance.add(Calendar.MINUTE, 30);//有效期30分钟? builder.withExpiresAt(instance.getTime()); return builder.sign(Algorithm.HMAC256(SECRET)).toString(); } /** * 验证token * * @param token */ public static void verify(String token) { JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token); } /** * 获取token明文 * * @param token * @return */ public static DecodedJWT getTokenPlainText(String token) { return JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token); }}

pom.xml中要引入jwt类库

com.auth0 java-jwt 3.10.0

3)拦截器及拦截器使能

(1)拦截器

mport com.auth0.jwt.exceptions.AlgorithmMismatchException;import com.auth0.jwt.exceptions.SignatureVerificationException;import com.auth0.jwt.exceptions.TokenExpiredException;import com.fasterxml.jackson.databind.ObjectMapper;import com.gzdd.rainapi.utils.JwtUtils;import org.springframework.org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.javax.servlet.java.util.HashMap;import java.util.Map;/** * JWT验证拦截器 */@Componentpublic class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { /** * 前后端分离有时候会有两次请求,第一次为OPTIONS请求,默认会拦截所有请求,但是第一次请求又获取不到jwt,所以会出错。 **/ if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) { return true; } Map map = new HashMap<>(); //令牌建议是放在请求头中,获取请求头中令牌 String token = request.getHeader("token"); try { JwtUtils.verify(token);//验证令牌 return true;//放行请求 } catch (SignatureVerificationException e) { map.put("msg", "无效签名"); map.put("status", 401); } catch (TokenExpiredException e) { map.put("msg", "token过期"); map.put("status", 401); } catch (AlgorithmMismatchException e) { map.put("msg", "token算法不一致"); map.put("status", 401); } catch (Exception e) { map.put("msg", "token失效"); map.put("status", 401); } map.put("state", false);//设置状态 //将map转化成json,response使用的是Jackson System.out.println(map); Map res = new HashMap(); res.put("data", map); String json = new ObjectMapper().writeValueAsString(res); response.setContentType("application/json;charset=UTF-8"); response.getWriter().print(json); return false; }}

(2)使用拦截器

import com.gzdd.rainapi.interceptor.JwtInterceptor;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configurationpublic class SecurityConfiguration implements WebMvcConfigurer { @Autowired private JwtInterceptor jwtI; public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtI) .addPathPatterns("/data/*");//取数据的接口,必须检查JWT }}

4)控制器 (1)身份认证

import com.gzdd.rainapi.config.AppConfig;import com.gzdd.rainapi.pojo.TokenUser;import com.gzdd.rainapi.utils.IPUtils;import com.gzdd.rainapi.utils.JwtUtils;import com.gzdd.rainapi.utils.Result;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import javax.servlet.java.util.HashMap;import java.util.Map;@RestControllerpublic class IndexController { @Autowired AppConfig appConfig; @RequestMapping(value = {"/test"}) public String noCheck(Model model) { return "Hello world!"; } //登录时生成token @PostMapping(value = "/token") public Result getToken(HttpServletRequest request, @RequestBody TokenUser user) { if (!appConfig.isValidUser(user.getName(), user.getPassword())) { return Result.error("非法的用户"); } Map payload = new HashMap<>(); payload.put("ip", IPUtils.getIpAddr(request)); payload.put("name", user.getName()); payload.put("time", System.currentTimeMillis() + ""); String token = JwtUtils.getToken(payload);//生成JWT令牌 return Result.ok().put("token", token); }}

(2)数据接口

import org.springframework.ui.Model;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;//注意这些接口以/data/开头,符合拦截器使能条件@RestController@RequestMapping(value = "/data")public class DataController { @RequestMapping(value = {"/test"}) public String test(Model model) { return "welcome to data api!"; }}

5)测试结果

(1)身份认证

(2)数据获取

参考文章:​​​REST API 安全设计指南​​​​JSON Web Token 入门教程​​


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:星宿关系查询表API(星宿关系查询表业胎)
下一篇:Springboot JPA 枚举Enum类型存入到数据库的操作
相关文章

 发表评论

暂时没有评论,来抢沙发吧~