不點藍字,我們哪來故事?
概述
與前端對接的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小時內與您取得聯系。