在现有大多数阅读性文章、博客等内容,不单有图文结合的形式让内容更加饱满更有关联链接来充实文章的真实、准确性。那么当这些连接进行跳转离开网站时,我们要如何确认即将前往网站的安全性呢?
跳转效果演示——点击进入码云官网
本文就该问题,使用js代码全局监听a标签跳转前进行跳转拦截。提取跳转连接,统一跳转到指定页面进行提示及跳转链接安全审核。
先来看看完整代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <a href="https://tool.xuexiareas.com/" target="_blank">六月初工具站</a> <a href="https://www.baidu.com/" target="_blank">baidu</a> <script> document.body.addEventListener("click",function (event) { var target = event.target || event.srcElement; // 兼容处理 if (target.nodeName.toLocaleLowerCase() === "a") { // 判断是否匹配目标元素 if (event.preventDefault) { // 对捕获到的 a 标签进行处理 event.preventDefault(); } else { window.event.returnValue = true; } choosePush(target); // 处理完 a 标签的内容,重新触发跳转,根据原来 a 标签页 target 来判断是否需要新窗口打开 } }.bind(this) );
function choosePush(el) {
const target = el.getAttribute("target");
const href = el.getAttribute("href");
if (target === "_blank") {
var domain = window.location.host.toLowerCase();
if (href.indexOf("localhost") != -1 || href.indexOf("baidu.com") != -1) {
window.location.href = href;
}else
{
window.open('/typg/site/link?target=' + encodeURIComponent(href));
}
} else {
window.location.href = href;
}
}
</script> </body> </html>
中间安全提示页:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=Edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="referrer" content="always"> <title>网站跳转中心-六月初博客站</title> <style> html, body { background: #F3F4F5; font-family: PingFang SC, Hiragino Sans GB, Arial, Microsoft YaHei, Verdana, Roboto, Noto, Helvetica Neue, sans-serif; padding: 0; margin: 0; height: 100vh; width: 100vw; overflow: hidden; } a { text-decoration: none; } #linkPage{ height: 100vh; width: 100vw; overflow: hidden; display: flex; align-items: center; justify-content: center; margin-top: -10%; } .content { width: 450px; margin: auto; word-break: break-all; } .content .logo-img img { display: block; width: 175px; margin: auto; margin-bottom: 16px; } .content .loading-item { background: #fff; padding: 24px; border-radius: 12px; border: 1px solid #E1E1E1; } .content .flex { display: flex; align-items: center; } .content .flex-end { display: flex; justify-content: flex-end; align-items: center; } .content .tip2 { background: #ecfaf8; } .content .loading-color2 { color: #09a88c; } .content .loading-tip { padding: 12px; margin-bottom: 16px; border-radius: 4px; } .content .loading-topic { font-size: 14px; color: #222226; line-height: 24px; margin-bottom: 24px; } .content .loading-img { width: 24px; height: 24px; } .content .loading-btn { white-space: nowrap; font-size: 14px; color: #09a88c; border: 1px solid #09a88c; display: inline-block; box-sizing: border-box; padding: 6px 18px; border-radius: 18px; margin-left: 8px; } .content .cancle-btn{ white-space: nowrap; font-size: 14px; color: #c7c7c7; border: 1px solid #c7c7c7; display: inline-block; box-sizing: border-box; padding: 6px 18px; border-radius: 18px; margin-left: 8px; } .content .loading-text { font-size: 16px; font-weight: 600; color: #222226; line-height: 22px; margin-left: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } @media (max-width: 450px) { .content { width: 94%; } } </style> </head> <body> <div id="linkPage" class="link-page"> <div class="content"> <div class="logo-img"><img src="./static/logo_text.png" alt=""></div> <div class="loading-item loading-others"> <div class="flex loading-tip tip2"> <img class="loading-img" src="./static/warning20201108.png" alt=""> <div class="loading-text">请注意您的账号和财产安全</div> </div> <div class="loading-topic"><span>您即将离开六月初博客站,去往:</span><a class="loading-color2" id="setUrl">https://tool.xuexiareas.com/</a> </div> <div class="flex-end"> <a class="cancle-btn" href="javascript:window.opener=null;window.close();">取消</a> <a class="loading-btn" id="setA" href="https://tool.xuexiareas.com/" target="_self">继续</a> </div> </div> </div> </div> <script> window.onload = function(){ if(getQueryString('target')){ document.getElementById('setUrl').innerHTML= getQueryString('target') document.getElementById('setA').href= getQueryString('target') if(getQueryString('target').slice(0, 27) == 'https://tool.xuexiareas.com'){ document.getElementById('setA').click() } }else{ document.getElementById('setA').href= 'https://tool.xuexiareas.com/' document.getElementById('setUrl').innerHTML= 'https://tool.xuexiareas.com/' } } function getQueryString(name) { var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); //构造一个含有目标参数的正则表达式对象 var r = window.location.search.substr(1).match(reg); //匹配目标参数 if( r != null ) return decodeURIComponent( r[2] ); return null; } </script> </body> </html>
js代码解析:
1、对body进行全局监听
2、当body区域有点击效果时,提取点击的节点是否是a标签
3、阻止默认a标签的原生跳转
4、提取a标签的相关数据,进行自定义从新跳转
通过以上四个步骤,js实现了对全局a标签的跳转监听、阻止效果。提取到跳转参数后,可以统一跳转到指定路径去进行二次确认跳转及跳转路径安全的验证。
java后端:
public class MobileInterceptor extends BaseService implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String redirect = request.getParameter("redirect"); String redirect_to = request.getParameter("redirect_to"); String redirect_url = request.getParameter("redirect_url"); String url = request.getParameter("url"); String jump = request.getParameter("jump"); String jump_to = request.getParameter("jump_to"); String target = request.getParameter("target"); String to = request.getParameter("to"); String link = request.getParameter("link"); String linkto = request.getParameter("linkto"); String domain = request.getParameter("domain"); String herf = ""; if(StringUtils.isNoEmpty(redirect)) { herf = redirect;} if(StringUtils.isNoEmpty(redirect_to)) { herf = redirect_to;} if(StringUtils.isNoEmpty(redirect_url)) { herf = redirect_url;} if(StringUtils.isNoEmpty(url)) { herf = url;} if(StringUtils.isNoEmpty(jump)) { herf = jump;} if(StringUtils.isNoEmpty(jump_to)) { herf = jump_to;} if(StringUtils.isNoEmpty(target)) { herf = target;} if(StringUtils.isNoEmpty(to)) { herf = to;} if(StringUtils.isNoEmpty(link)) { herf = link;} if(StringUtils.isNoEmpty(linkto)) { herf = linkto;} if(StringUtils.isNoEmpty(domain)) { herf = domain;} String ServerName = request.getServerName().toLowerCase();//获取服务器域名 Integer ServerPort = request.getServerPort();//获取服务器端口 if(StringUtils.isNoEmpty(herf) && !request.getRequestURL().toString().toLowerCase().contains("/site/link") && herf.toLowerCase().contains("http")) { Integer len = 12 + ServerName.length(); if(herf.length() >= len && !herf.substring(0, len).toLowerCase().contains(ServerName)) { response.sendRedirect("/typg/site/link?target=" + herf); return false; } } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { if (modelAndView != null){ // 如果是手机或平板访问的话,则跳转到手机视图页面。 // if(UserAgentUtils.isMobileOrTablet(request) && !StringUtils.startsWithIgnoreCase(modelAndView.getViewName(), "redirect:")){ // modelAndView.setViewName("mobile/" + modelAndView.getViewName()); // } } } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
spring-mvc.xml
<!-- 手机视图拦截器 --> <mvc:interceptor> <mvc:mapping path="/**" /> <bean class="com.thinkgem.jeesite.modules.sys.interceptor.MobileInterceptor" /> </mvc:interceptor> </mvc:interceptors>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!