无源码实现免登录功能

news2024/12/24 8:59:40

因项目要求需要对一个没有源代码的老旧系统实现免登录功能,系统采用前后端分离的方式部署,登录时前端调用后台的认证接口,认证接口返回token信息,然后将token以json的方式存储到cookie中,格式如下:
在这里插入图片描述
这里有一个auth_token采用JSON格式存储,尝试了好几种写入Cookie的方式,均无法实现,现将可以实现方式记录如下。

Nginx配置

首先,为了避免跨域问题,以及Cookie的作用域问题,必须将现有系统和免登录跳转系统配置到同一个端口下,配置如下

    server {
        listen       80;
        server_name  localhost;
  
        # 免登录跳转系统
        location / {
            proxy_pass http://127.0.0.1:8084;
            #proxy_cookie_path / "/; httponly; secure; SameSite=Strict";
            #proxy_cookie_flags ~ nosecure samesite=strict;
        }
        
        # 老旧系统
        location / {
            proxy_pass http://127.0.0.1:8000;
            proxy_cookie_path / "/; httponly; secure; SameSite=Strict";
            proxy_cookie_flags ~ nosecure samesite=strict;
        }

注意这里proxy_cookie_path和proxy_cookie_flags要注释掉,否则在免登录跳转系统中设置的Cookie在跳转到现有系统后无法认证成功,下面对这两个参数说明一下:

proxy_cookie_path

proxy_cookie_path / "/; httponly; secure; SameSite=Strict";

proxy_cookie_path:此指令用于修改通过Nginx代理传递的Cookie的路径以及其他属性。
/:指定Cookie的路径为根路径,这意味着Cookie对整个域名有效。
“; httponly; secure; SameSite=Strict”:这部分是对Cookie添加额外的属性:
httponly:此属性指示浏览器仅允许HTTP(S)协议访问该Cookie,不允许JavaScript访问,这有助于防止跨站脚本攻击(XSS)。
secure:此属性指示浏览器仅在HTTPS连接中传输该Cookie,不允许在不安全的HTTP连接中传输,这有助于保护Cookie不被中间人攻击窃取。
SameSite=Strict:此属性控制Cookie在跨站请求中的发送行为。设置为Strict意味着Cookie仅在同站请求中发送,不会在跨站请求中发送,这有助于减少跨站请求伪造(CSRF)攻击的风险。

proxy_cookie_flags

proxy_cookie_flags ~ nosecure samesite=strict;

proxy_cookie_flags:此指令用于设置或修改通过Nginx代理传递的Cookie的标志。
~:这是一个正则表达式匹配操作符,表示接下来的模式应用于所有匹配的Cookie。
nosecure:此标志指示Nginx移除Cookie中的Secure属性。这意味着即使原始Cookie设置了Secure属性,通过Nginx代理后,该属性将被移除,Cookie可以在HTTP和HTTPS连接中都传输。这通常不推荐,因为它降低了安全性。
samesite=strict:此标志指示Nginx添加或修改Cookie的SameSite属性为Strict。这与proxy_cookie_path中的SameSite=Strict类似,控制Cookie在跨站请求中的发送行为。
注意:通常情况下,不建议在生产环境中使用nosecure标志,因为它会降低Cookie的安全性。如果需要移除Secure属性,应该仔细考虑安全性影响,并确保有其他安全措施来保护数据。
这两行配置通常一起使用,以确保通过Nginx代理传递的Cookie具有适当的安全属性。然而,proxy_cookie_flags中的nosecure标志可能会抵消proxy_cookie_path中设置的secure属性,因此在实际应用中需要谨慎使用。

免登录跳转

搭建一个SpringBoot工程,前端实现一个简单的跳转页面test.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>OSS</title>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
</head>
<body>
    <a href="http://192.168.31.112">http://192.168.31.112</a>
</body>
</html>

后端IndexController类关键方法如下

package org.example.onemaposs.controller;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

@Slf4j
@Controller
public class IndexController {

    @Value("${app.login.url}")
    private String loginUrl;
    @Value("${app.user}")
    private String user;
    @Value("${app.pass}")
    private String pass;
    @Value("${app.index.url}")
    private String indexUrl;

    // 经过测试,该方法可以实现免登录功能,注意在Nginx中代理不能加如下两行,否测Cookie无法共享
    // proxy_cookie_path / "/; httponly; secure; SameSite=Strict";
    // proxy_cookie_flags ~ nosecure samesite=strict;
    @RequestMapping("/test")
    public String test(HttpServletRequest request, HttpServletResponse response, ModelMap model) {
        String token = null;
        JSONObject json = new JSONObject();
        json.put("user", user);
        json.put("pwd", pass);
        String str = doPost(loginUrl, json);
        JSONObject jsonObject = JSONObject.parseObject(str);
        JSONObject obj = jsonObject.getJSONObject("obj");
        JSONObject rtnJson = new JSONObject(true);
        JSONObject authToken = new JSONObject(true);
        if (obj != null && obj.containsKey("token")) {
            rtnJson.put("code", 200);
            rtnJson.put("msg", "登录成功");
            token = obj.getString("token");
            authToken.put("data", token);
        }
        String time = String.format("expires=%s; path=/", getTime());
        log.debug("Cookie time = {}", time);
        response.setHeader("authorization", token);
        response.addHeader("Set-Cookie", String.format("auth_token=%s; ", authToken.toJSONString()) + time);
        response.addHeader("Set-Cookie", "user_name=admin; " + time);
        response.addHeader("Set-Cookie", "is_anager=true; " + time);
        response.addHeader("Set-Cookie", "user_role={%22uId%22:%22650e5cc596d8932f88ec8c90%22%2C%22user%22:%22admin%22%2C%22realName%22:%22%22%2C%22role%22:%22Admin%22%2C%22userRolePrivilege%22:{%22rolePriv%22:[]%2C%22userPriv%22:{%22privileges%22:[]}}}; " + time);

        return "test";
    }

    private String getTime() {
        // 获取当前时间的ZonedDateTime实例
        ZonedDateTime now = ZonedDateTime.now(java.time.ZoneId.of("GMT"));
        // 增加8天
        ZonedDateTime futureDateTime = now.plusDays(8);
        // 定义日期时间格式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH);
        // 格式化日期时间
        return futureDateTime.format(formatter);
    }

    protected String doPost(String url, JSONObject json) {
        String rtn = null;
        CloseableHttpClient httpClient = null;
        try {
            httpClient = HttpClientBuilder.create().build();
            RequestConfig.custom().setConnectionRequestTimeout(5000).setSocketTimeout(5000).setConnectTimeout(5000).build();
            // 创建Get请求
            HttpPost http = new HttpPost(url);
            http.addHeader("Accept", "application/json, text/plain, */*");
            http.addHeader("Content-Type", "application/json");
            http.setEntity(new StringEntity(json.toJSONString(), "UTF-8"));
            CloseableHttpResponse response = null;
            // 由客户端执行(发送)Get请求
            response = httpClient.execute(http);
            // 从响应模型中获取响应实体
            HttpEntity responseEntity = response.getEntity();
            rtn = EntityUtils.toString(responseEntity);
        } catch (Exception e) {
            log.error(String.format("Search request throw exception: %s", e.getMessage()), e);
        } finally {
            if (httpClient != null) {
                try {
                    httpClient.close();
                } catch (Exception e) {
                    log.error(String.format("Close http client throw exception: %s", e.getMessage()), e);
                }
            }
        }
        return rtn;
    }
}

application.properties配置如下:

server.port=8084
server.servlet.context-path=/oss

app.login.url=http://192.168.31.112/v1/auth/login
app.user=admin
app.pass=4de93544234adffbb681ed60ffcfb941
app.index.url=http://192.168.31.112

spring.thymeleaf.cache=false
spring.thymeleaf.check-template=true
spring.thymeleaf.check-template-location=true
spring.thymeleaf.servlet.content-type=text/html
spring.thymeleaf.enabled=true
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.excluded-view-names=
spring.thymeleaf.mode=HTML5
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

这样当访问http://192.168.31.112/oss/test时,系统通过端调用原有系统的认证接口,将相关的认证信息写入cookie,然后当前端跳转到新系统时,就实现了免登录功能。

几种写Cookie坑

由于这里写入Cookie的值为JSON格式,且为没有编码的,因此这里尝试了几种其他的方式写入Cookie,都失效了,因此记录如下:

后端Java实现

    private void setCookie(HttpServletResponse response, String key, String value) {
        log.debug("Set cookie {}={}", key, value);
        Cookie cookie = new Cookie(key, value);
        cookie.setMaxAge(3600 * 24 * 30);
        cookie.setPath("/");
        response.addCookie(cookie);
    }

无法写入JSON,提示错误如下

2024-10-01 21:40:56.737 DEBUG Set cookie data={"a":"b"} [http-nio-8084-exec-3](org.example.onemaposs.controller.IndexController:121)
2024-10-01 21:40:56.749 ERROR Servlet.service() for servlet [dispatcherServlet] in context with path [/oss] threw exception [Request processing failed; nested exception is java.lang.IllegalArgumentException: An invalid character [34] was present in the Cookie value] with root cause [http-nio-8084-exec-3](org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/oss].[dispatcherServlet]:175)
java.lang.IllegalArgumentException: An invalid character [34] was present in the Cookie value
	at org.apache.tomcat.util.http.Rfc6265CookieProcessor.validateCookieValue(Rfc6265CookieProcessor.java:197)
	at org.apache.tomcat.util.http.Rfc6265CookieProcessor.generateHeader(Rfc6265CookieProcessor.java:123)
	at org.apache.catalina.connector.Response.generateCookieString(Response.java:1001)
	at org.apache.catalina.connector.Response.addCookie(Response.java:953)
	......

前端JavaScript实现1

采用jquery.cookie.min.js,代码如下

function setCookie(key, value) {
    $.cookie(key, value, {
        expires: 8,   // 有效期为7天
        path: '/',    // 在整个网站有效
        domain: '',   // 默认的域
        secure: false // 不使用安全协议
    });
}

setCookie('auth_token', '{"data":"xxxxxx"}');

写入Cookie中的{、}、"、:、+、空格等等均被编码了,因此现有系统在读取后转JSON报错。

前端JavaScript实现2

以下这种方法也不行,去掉encodeURIComponent也不行


setCookies([
    ["user_name", "admin"],
    ["auth_token", "{\"data\":\"xxxxxx\"}"]
]);

function setCookies(nameValuePairList) {
    var cookieList = nameValuePairList.map(function(nameValuePair) {
        var cookieString = nameValuePair[0] + "=" + encodeURIComponent(nameValuePair[1]);
        // 可以添加其他属性,如过期时间、路径、域等
        return cookieString;
    });
    document.cookie = cookieList.join("; ");
}

function escapeCookieValue(value) {
    return value.replace(/{/g, '\\{').replace(/}/g, '\\}').replace(/"/g, '\\"').replace(/,/g, '\\,').replace(/;/g, '\\;').replace(/ /g, '\\ ');
}

function setCookie(key, value) {
    $.cookie(key, value, {
        expires: 8,   // 有效期为7天
        path: '/',    // 在整个网站有效
        domain: '',   // 默认的域
        secure: false // 不使用安全协议
    });
}

前端JavaScript实现3

定义Cookie类也不行

let ht;
if (!ht) ht = {};

ht.Cookie = function () {
    let _p = ht.Cookie.prototype;
    _p.getCookieVal = function (offset) {
        let endstr = document.cookie.indexOf(";", offset);
        if (endstr == -1) {
            endstr = document.cookie.length;
        }
        return unescape(document.cookie.substring(offset, endstr));
    };
    _p.set = function (name, value) {
        let expdate = new Date();
        let argv = arguments;
        let argc = arguments.length;
        let expires = (argc > 2) ? argv[2] : null;
        let path = (argc > 3) ? argv[3] : "/";
        let domain = (argc > 4) ? argv[4] : null;
        let secure = (argc > 5) ? argv[5] : false;
        if (expires != null) {
            let temp = 0;
            let rdigit = /\d/;
            if (rdigit.test(expires) && !isNaN(expires)) {
                temp = parseInt(expires);
            }
            temp = temp <= 0 ? 1 : temp;
            expdate.setTime(expdate.getTime() + (temp * 1000 * 3600 * 24));
        }
        document.cookie = name
            + "=" + value
            // + escape(value)
            + ((expires == null) ? "" : ("; expires=" + expdate.toGMTString()))
            + ((path == null) ? "" : ("; path=" + path))
            + ((domain == null) ? "" : ("; domain=" + domain))
            + ((secure == true) ? "; secure" : "");
    };
    _p.get = function (name) {
        let arg = name + "=";
        let alen = arg.length;
        let clen = document.cookie.length;
        let i = 0;
        while (i < clen) {
            var j = i + alen;
            if (document.cookie.substring(i, j) == arg) {
                return this.getCookieVal(j);
            }
            i = document.cookie.indexOf(" ", i) + 1;
            if (i == 0) {
                break;
            }
        }
        return null;
    };
    _p.remove = function (name) {
        let exp = new Date();
        exp.setTime(exp.getTime() - 1);
        let cval = this.get(name);
        document.cookie = name + "=" + cval + "; expires=" + exp.toGMTString() + "; path=/";
    };

    _p.removeAll = function clearAllCookie() {
        let keys = top.document.cookie.match(/[^ =;]+(?=\=)/g);
        if(keys) {
            for(var i = keys.length; i--;)  {
                let exp = new Date();
                exp.setTime(exp.getTime() - 1);
                document.cookie = keys[i] + '=0;expires=' + exp.toGMTString() + "; path=/";
            }
        }
    }
};
// cookie实例
ht.cookie = new ht.Cookie();

ht.cookie.set('user_name', 'admin');
ht.cookie.set('auth_token', '{"data":"xxxxxx"}');

前端JavaScript实现4

使用document.cookie来设置Cookie也无法生效

$(document).ready(function() {
    let str = 'auth_token={"data":"xxxxxx"}; user_name=admin; is_anager=true;';
    let expirationDate = new Date();
    expirationDate.setDate(expirationDate.getDate() + 7);
    document.cookie = str + "expires=" + expirationDate.toUTCString() + ";path=/";
});

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2183340.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

10月1日星期二今日早报简报微语报早读

10月1日星期二&#xff0c;国庆节&#x1f1e8;&#x1f1f3;&#xff0c;农历八月廿九&#xff0c;早报#微语早读。 1、A股暴涨刷新多项历史纪录&#xff1a;两市成交总额近2.6万亿元&#xff0c;创指涨逾15%&#xff1b; 2、文旅部&#xff1a;常年不超过最高承载量的旅游景…

Docker 安装 Citus 单节点集群:全面指南与详细操作

Docker 安装 Citus 单节点集群&#xff1a;全面指南与详细操作 文章目录 Docker 安装 Citus 单节点集群&#xff1a;全面指南与详细操作一 服务器资源二 部署图三 安装部署1 创建网络2 运行脚本1&#xff09;docker-compose.cituscd1.yml2&#xff09;docker-compose.cituswk1.…

zi2zi-chain: 中国书法字体图片生成和字体制作的一站式开发

在zi2zi-pytorch的基础上&#xff0c;做了进一步的修复和完善。本项目github对应网址为https://github.com/not-bald-owl/zi2zi-chain/tree/master。 修复部分为&#xff1a;针对预处理部分的函数弃用、生僻字无法生成、训练和推理部分单卡支持改为多卡并行、以及扩展从本地的…

过去8年,编程语言的流行度发生了哪些变化?PHP下降,Objective-C已过时

前天有一个汇总9个不同排名数据的“地表最强”编程语言排行榜&#xff0c;为了更好地理解语言流行度的变化&#xff0c;作者将2016年的类似调查结果与2024年的数据进行了比较。 虽然2016年的调查只包含6个排名&#xff0c;但它仍然提供了宝贵的参考数据。 我们来看看详细的情…

C++之String类(下)

片头 嗨喽~ 我们又见面啦&#xff0c;在上一篇C之String类&#xff08;上&#xff09;中&#xff0c;我们对string类的函数有了一个初步的认识&#xff0c;这一篇中&#xff0c;我们将继续学习string类的相关知识。准备好了吗&#xff1f;咱们开始咯~ 二、标准库中的string类 …

业务封装与映射 -- AMP BMP GMP

概述 不同单板支持不同的封装模式&#xff0c;主要包括: AMP (Asynchronous Mapping Procedure&#xff0c;异步映射规程)BMP (Bit-synchronous Mapping Procedure&#xff0c;比特同步映射规程)GMP (Generic Mapping Procedure&#xff0c;通用映射规程) AMP/BMP&#xff1a…

Qt_绘图

目录 1、绘图核心类 2、QPainter类的使用 2.1 绘制线段 2.2 绘制矩形 2.3 绘制圆形 2.4 绘制文本 3、QPen类的使用 3.1 使用画笔 4、QBrush类的使用 4.1 使用画刷 5、绘制图片 5.1 测试QPixmap 5.1.1 图片移动 5.1.2 图标缩小 5.1.3 旋转图片 5.1.4 将…

【逐行注释】MATLAB下的粒子滤波代码(三维状态与观测,可直接复制粘贴到MATLAB上面运行)

文章目录 程序设计1. 介绍2. 系统模型3. 算法步骤源代码(直接复制到MATLAB上面可以运行)运行结果程序设计 1. 介绍 粒子滤波(Particle Filter, PF)是一种基于贝叶斯理论的递归估计方法,广泛用于动态系统状态的估计和跟踪。该方法通过一组粒子(即假设的状态)及其权重来…

【Android 13源码分析】Activity生命周期之onCreate,onStart,onResume-1

忽然有一天&#xff0c;我想要做一件事&#xff1a;去代码中去验证那些曾经被“灌输”的理论。                                                                                  – 服装…

5款惊艳全网的AI写作论文神器!从此告别写作烦恼!

在当今的学术研究和写作领域&#xff0c;撰写高质量的论文是一项挑战性的任务。幸运的是&#xff0c;随着人工智能技术的发展&#xff0c;AI论文写作工具逐渐成为帮助学者和学生提高写作效率的重要工具。这些工具不仅能够提高写作效率&#xff0c;还能帮助研究者生成高质量的论…

ECharts 快速使用

最终效果 使用介绍 echarts图表的绘制&#xff0c;大体分为三步&#xff1a; 根据 DOM实例&#xff0c;通过 echarts.init方法&#xff0c;生成 echarts实例构建 options配置对象&#xff0c;整个echarts的样式&#xff0c;皆有该对象决定最后通过实例.setOption方法&#xf…

【测试-BUG篇】软件测试的BUG知识你了解多少呢?

文章目录 1. 软件测试的生命周期2. BUG3. BUG的生命周期4. 与开发人员起争执怎么办 1. 软件测试的生命周期 &#x1f34e;软件测试 贯穿整个软件的生命周期&#xff1b; &#x1f34e;软件测试的生命周期是指测试流程&#xff1b; ①需求分析 用户角度&#xff1a;软件需求是…

C++:一文搞懂友元类(friend class)

C的友元&#xff08;friend&#xff09;是个很重要的概念&#xff0c;好些朋友对此却很迷惑&#xff0c;本文将对友元类&#xff08;friend class&#xff09;详细讲解&#xff0c;一文搞懂。 友元的特性&#xff1a; 1、使用friend修饰的友元类可以访问本类的私有成员(priva…

中国电信解锁万亿参数大模型:TeleAI的创新与突破

首个由万卡集群训练出来的万亿参数大模型&#xff0c;已被一家央企解锁。 具体而言&#xff0c;为了推动纯国产人工智能的探索&#xff0c;带来这条新路径的正是中国电信人工智能研究院&#xff08;TeleAI&#xff09;。 该研究院由中国电信集团的CTO、首席科学家兼院长李学龙…

坡印廷矢量(也叫功率流密度,对面积积分就是功率)

坡印廷矢量在静电场&#xff0c;静磁场&#xff0c;恒定电流的电场&#xff0c;和时变电磁场中的表达式不同。 我们看时变电磁场的坡印廷矢量 坡印廷矢量就等于这个&#xff0c;其中的电场和磁场是实数表示的 坡印廷矢量用复数形式的场求 这里的E和H是复数表示的场&#xff0…

电影票接口api对接有哪些优势?

一、业务功能拓展方面的优势 多平台整合可以整合多个影院票务系统&#xff0c;通过一个接口获取众多影院的信息&#xff0c;包括影院、影厅、座位、影片、场次、日期及票价等信息&#xff0c;方便在自己的应用程序中展示这些信息&#xff0c;从而实现电影票的在线预订、支付和…

人工智能价格战——如何降低成本让人工智能更易于普及

十年前&#xff0c;开发人工智能 (AI) 是只有大公司和资金充足的研究机构才能负担得起的事情。必要的硬件、软件和数据存储成本非常高。但从那时起&#xff0c;情况发生了很大变化。一切始于 2012 年的 AlexNet&#xff0c;这是一种深度学习模型&#xff0c;展示了神经网络的真…

微服务jvisualvm解析部署使用全流程

1、介绍 VisualVM 是Netbeans的profile 2、启动 进入正在使用的jdk下bin目录&#xff0c;运行jvisualvm.exe。 3、选中要监控的线程 4、安装gc插件 5、插件安装报错 VisualVM: Plugins Centers 访问这个地址&#xff0c;找到对应版本再配置 https://visualvm.github.io/uc/…

【CKA】六、四层负载-Service应用

6、四层负载-Service应用 1. 考题内容&#xff1a; 2. 答题思路&#xff1a; 1、编辑front-end的deploy服务&#xff0c;添加端口信息 2、暴露svc端口 3. 官网地址&#xff1a; https://kubernetes.io/zh-cn/docs/tutorials/services/connect-applications-service/#the-ku…

nominatim部署OSM离线地图

第一步&#xff1a;准备一个大内存的服务器&#xff0c;磁盘PG大小根据实际导入的数据确定&#xff0c;全量数据1T&#xff0c;osm.pdf属于压缩文件&#xff0c;如果能下载&#xff0c;但下载很慢&#xff0c;可以尝试用迅雷下载。 osm.pdf下载 osm.pdf另外一个下载路径 全量数…