不点蓝字,我们哪来故事?
概述
与前端对接的API接口,如果被第三方抓包并进行恶意篡改参数,可能会导致数据泄露,甚至会被篡改数据,我主要围绕时间戳,token,签名三个部分来保证API接口的安全性
1.用户成功登陆站点后,服务器会返回一个token,用户的任何操作都必须带了这个参数,可以将这个参数直接放到header里。
2.客户端用需要发送的参数和token生成一个签名sign,作为参数一起发送给服务端,服务端在用同样的方法生成sign进行检查是否被篡改。
3.但这依然存在问题,可能会被进行恶意无限制访问,这时我们需要引入一个时间戳参数,如果超时即是无效的。
4.服务端需要对token,签名,时间戳进行验证,只有token有效,时间戳未超时,签名有效才能被放行。
开放接口
没有进行任何限制,简单粗暴的访问方式,这样的接口方式一般在开放的应用平台,查天气,查快递,只要你输入正确对应的参数调用,即可获取到自己需要的信息,我们可以任意修改参数值。
/*
* Description: 开放的接口
* @author huangweicheng
* @date 2020/12/21
*/
@RestController
@RequestMapping("/token")
public class TokenSignController {
@Autowired
private TokenSignService tokenSignService;
@RequestMapping(value = "openDemo",method = RequestMethod.GET)
public List openDemo(int personId) {
return tokenSignService.getPersonList(personId);
}
}
Token认证获取
用户登录成功后,会获取一个ticket值,接下去任何接口的访问都需要这个参数。我们把它放置在redis内,有效期为10分钟,在ticket即将超时,无感知续命。延长使用时间,如果用户在一段时间内没进行任何操作,就需要重新登录系统。扩展:
@RequestMapping(value = "login",method = RequestMethod.POST)
public JSONObject login(@NotNull String username, @NotNull String password){
return tokenSignService.login(username,password);
}
登录操作,查看是否有这个用户,用户名和密码匹配即可成功登录。
/**
*
* Description:验证登录,ticket成功后放置缓存中,
* @param
* @author huangweicheng
* @date 2020/12/31
*/
public JSONObject login(String username,String password){
JSONObject result = new JSONObject();
PersonEntity personEntity = personDao.findByLoginName(username);
if (personEntity == null || (personEntity != null && !personEntity.getPassword().equals(password))){
result.put("success",false);
result.put("ticket","");
result.put("code","999");
result.put("message","用户名和密码不匹配");
return result;
}
if (personEntity.getLoginName().equals(username) && personEntity.getPassword().equals(password)){
String ticket = UUID.randomUUID().toString();
ticket = ticket.replace("-","");
redisTemplate.opsForValue().set(ticket,personEntity.getLoginName(),10L, TimeUnit.MINUTES);
result.put("success",true);
result.put("ticket",ticket);
result.put("code",200);
result.put("message","登录成功");
return result;
}
result.put("success",false);
result.put("ticket","");
result.put("code","1000");
result.put("message","未知异常,请重试");
return result;
}
Sign签名
把所有的参数拼接一起,在加入系统秘钥,进行MD5计算生成一个sign签名,防止参数被人恶意篡改,后台按同样的方法生成秘钥,进行签名对比。
/**
* @param request
* @return
*/
public static Boolean checkSign(HttpServletRequest request,String sign){
Boolean flag= false;
//检查sigin是否过期
Enumeration> pNames = request.getParameterNames();
Map params = new HashMap();
while (pNames.hasMoreElements()) {
String pName = (String) pNames.nextElement();
if("sign".equals(pName)) continue;
String pValue = (String)request.getParameter(pName);
params.put(pName, pValue);
}
System.out.println("现在的sign-->>" + sign);
System.out.println("验证的sign-->>" + getSign(params,secretKeyOfWxh));
if(sign.equals(getSign(params, secretKeyOfWxh))){
flag = true;
}
return flag;
}
重复访问
引入一个时间戳参数,保证接口仅在一分钟内有效,需要和客户端时间保持一致。
public static long getTimestamp(){
long timestampLong = System.currentTimeMillis();
long timestampsStr = timestampLong / 1000;
return timestampsStr;
}
需要跟当前服务器时间进行对比,如果超过一分钟,就拒绝本次请求,节省服务器查询数据的消耗
拦截器
每次请求都带有这三个参数,我们都需要进行验证,只有在三个参数都满足我们的要求,才允许数据返回或被操作。
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws IOException {
JSONObject jsonObject = new JSONObject();
String ticket = request.getParameter("ticket");
String sign = request.getParameter("sign");
String ts = request.getParameter("ts");
if (StringUtils.isEmpty(ticket) || StringUtils.isEmpty(sign) || StringUtils.isEmpty(ts)){
jsonObject.put("success",false);
jsonObject.put("message","args is isEmpty");
jsonObject.put("code","1001");
PrintWriter printWriter = response.getWriter();
printWriter.write(jsonObject.toJSONString());
return false;
}
//如果redis存在ticket就认为是合法的请求
if (redisTemplate.hasKey(ticket)){
System.out.println(redisTemplate.opsForValue().getOperations().getExpire(ticket));
String values = (String) redisTemplate.opsForValue().get(ticket);
//判断ticket是否即将过期,进行续命操作
if (redisTemplate.opsForValue().getOperations().getExpire(ticket) != -2 && redisTemplate.opsForValue().getOperations().getExpire(ticket) < 20){
redisTemplate.opsForValue().set(ticket,values,10L, TimeUnit.MINUTES);
}
System.out.println(SignUtils.getTimestamp());
//判断是否重复访问,存在重放攻击的时间窗口期
if (SignUtils.getTimestamp() - Long.valueOf(ts) > 600){
jsonObject.put("success",false);
jsonObject.put("message","Overtime to connect to server");
jsonObject.put("code","1002");
PrintWriter printWriter = response.getWriter();
printWriter.write(jsonObject.toJSONString());
return false;
}
//验证签名
if (!SignUtils.checkSign(request,sign)){
jsonObject.put("success",false);
jsonObject.put("message","sign is invalid");
jsonObject.put("code","1003");
PrintWriter printWriter = response.getWriter();
printWriter.write(jsonObject.toJSONString());
return false;
}
return true;
}else {
jsonObject.put("success",false);
jsonObject.put("message","ticket is invalid,Relogin.");
jsonObject.put("code","1004");
PrintWriter printWriter = response.getWriter();
printWriter.write(jsonObject.toJSONString());
}
return false;
}
}
访问
先登录系统,获取合法的ticket
生成一个合法的sign验证,获取测试ts,访问,即可正常访问。还可以将参数加密,将http换成https,就不一 一展开了。
demo代码
往期推荐
多点控制单元 选择转发单元 tutorial 1 documentation
选择性转发单元 SFU( Unit)在各个端点之间交换音频和视频流。 每个接收器方可以选择它所要接收的流和层(空间/时间上)。 与 MCU(多点控制单元)相比,这种设计可以带来更好的性能、更高的吞吐量和更少的延迟。 鉴于它不做转码或合成媒体,所以它具有高度可扩展性,并且需要的资源少得多。
由于各个端点分别获取其他端点的媒体,因此它们可以具有个性化的布局,并选择自己所要呈现的媒体流,以及决定如何显示它们。
SFU 可以看作一个多媒体流的路由器,实践中可以应用发布订阅模式( publish/ pattern)
libuv
refer to
room-<unique room ID>: { description = This is my awesome room is_private = true|false (private rooms don't appear when you do a 'list' request, default=false) secret = <optional password needed for manipulating (e.g. destroying) the room> pin = <optional password needed for joining the room> require_pvtid = true|false (whether subscriptions are required to provide a valid private_id to associate with a publisher, default=false) publishers = <max number of concurrent senders> (e.g., 6 for a video conference or 1 for a webinar, default=3) bitrate = <max video bitrate for senders> (e.g., 128000) bitrate_cap = <true|false, whether the above cap should act as a limit to dynamic bitrate changes by publishers, default=false>, fir_freq = <send a FIR to publishers every fir_freq seconds> (0=disable)audiocodec = opus|g722|pcmu|pcma|isac32|isac16 (audio codec to force on publishers, default=opus can be a comma separated list in order of preference, e.g., opus,pcmu) videocodec = vp8|vp9|h264|av1|h265 (video codec to force on publishers, default=vp8 can be a comma separated list in order of preference, e.g., vp9,vp8,h264) vp9_profile = VP9-specific profile to prefer (e.g., "2" for "profile-id=2") h264_profile = H.264-specific profile to prefer (e.g., "42e01f" for "profile-level-id=42e01f") opus_fec = true|false (whether inband FEC must be negotiated; only works for Opus, default=false) video_svc = true|false (whether SVC support must be enabled; only works for VP9, default=false) audiolevel_ext = true|false (whether the ssrc-audio-level RTP extension must be negotiated/used or not for new publishers, default=true) audiolevel_event = true|false (whether to emit event to other users or not, default=false) audio_active_packets = 100 (number of packets with audio level, default=100, 2 seconds) audio_level_average = 25 (average value of audio level, 127=muted, 0='too loud', default=25) videoorient_ext = true|false (whether the video-orientation RTP extension must be negotiated/used or not for new publishers, default=true) playoutdelay_ext = true|false (whether the playout-delay RTP extension must be negotiated/used or not for new publishers, default=true) transport_wide_cc_ext = true|false (whether the transport wide CC RTP extension must be
negotiated/used or not for new publishers, default=true) record = true|false (whether this room should be recorded, default=false) rec_dir = <folder where recordings should be stored, when enabled> lock_record = true|false (whether recording can only be started/stopped if the secret is provided, or using the global enable_recording request, default=false) notify_joining = true|false (optional, whether to notify all participants when a new participant joins the room. The Videoroom plugin by design only notifies new feeds (publishers), and enabling this may result extra notification traffic. This flag is particularly useful when enabled with require_pvtid for admin to manage listening only participants. default=false) require_e2ee = true|false (whether all participants are required to publish and subscribe using end-to-end media encryption, e.g., via Insertable Streams; default=false) }
© 2021 ~ 2023, Walter Fan, Commons -- 4.0 License.
Built with Sphinx using by Read the Docs.
*请认真填写需求信息,我们会在24小时内与您取得联系。