无源码实现免登录功能

因项目要求需要对一个没有源代码的老旧系统实现免登录功能,系统采用前后端分离的方式部署,登录时前端调用后台的认证接口,认证接口返回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=/ossapp.login.url=http://192.168.31.112/v1/auth/login
app.user=admin
app.pass=4de93544234adffbb681ed60ffcfb941
app.index.url=http://192.168.31.112spring.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 valueat 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.mzph.cn/news/880823.shtml

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

相关文章

HTTPS加密流程

本文尽量用最小的篇幅来介绍HTTPS的加密过程&#xff0c;如果还看不懂可以参考文末尾的文章。 加密算法 先简单介绍一下HTTPS中使用的是混合加密算法&#xff0c;即对称加密和非对称加密的混合使用&#xff1a; 1.对称加密:顾名思义就是加密和解密都是使用同一个密钥。 优点…

MySQL 问题小结

mysqld --initialize 初始化 data 文件夹 初始化的密码在这个 err 文件夹中

DC00025【含论文】基于协同过滤推荐算法springboot视频推荐管理系统

1、项目功能演示 DC00025【含文档】基于springboot短视频推荐管理系统协同过滤算法视频推荐系统javaweb开发程序设计vue 2、项目功能描述 短视频推荐系统分为用户和系统管理员两个角色 2.1 用户角色 1、用户登录、用户注册 2、视频中心&#xff1a;信息查看、视频收藏、点赞、…

数据链路层 ——MAC

目录 MAC帧协议 mac地址 以太网帧格式 ARP协议 ARP报文格式​编辑 RARP 其他的网络服务或者协议 DNS ICMP协议 ping traceroute NAT技术 代理服务器 网络层负责规划转发路线&#xff0c;而链路层负责在网络节点之间的转发&#xff0c;也就是"一跳"的具体传输…

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 将…

【四】Spring Cloud OpenFeign原理分析

Spring Cloud OpenFeign原理分析 概述 Spring Cloud 微服务实践也有挺多年了&#xff0c;一直想着总结一下这系列的知识点&#xff0c;最近终于下定决心来出一个Spring Cloud 系列文章了。本文主要围绕fegin组件来进行讲解&#xff0c;文中将会给出基础使用的示例&#xff0c;还…

docker-compose 快速部署clickhouse集群

在本教程中&#xff0c;我们将学习如何使用 Docker Compose 部署一个带有三节点的 ClickHouse 集群&#xff0c;并使用 ZooKeeper 作为分布式协调服务。 前提条件 注意事项&#xff1a; 镜像版本号注意保持一致 [zookeeper:3.7, clickhouse/clickhouse-server:22.5.4]config…

uniapp微信小程序使用ucharts遮挡自定义tabbar的最佳解决方案

如图所示&#xff1a; 使用的ucharts遮挡住了我自定义的tabbar&#xff08;如果不是提需求的有病&#xff0c;我才不会去自定义tabbar&#xff09; 查阅了不少文档&#xff0c;说是开启 ucharts 的 canvas2d 即可&#xff1a; 官网文档地址&#xff1a; uCharts官网 - 秋云…

资源《Arduino 扩展板1-LED灯》说明。

资源链接&#xff1a;Arduino 扩展板1-LED灯 1.文件明细&#xff1a; 2.文件内容说明 包含&#xff1a;AD工程、原理图、PCB。 3.内容展示 4.简述 该文件为PCB工程&#xff0c;采用AD做的。 该文件打板后配合Arduino使用&#xff0c;属于Arduino的扩展板。 该文件主要有…

深度学习自编码器 - 分布式表示篇

序言 深度学习作为人工智能领域的重要分支&#xff0c;其核心在于表示学习&#xff08; Representation Learning \text{Representation Learning} Representation Learning&#xff09;&#xff0c;尤其是分布式表示&#xff08; Distributed Representation \text{Distribut…

netty之基础aio,bio,nio

前言 在Java中&#xff0c;提供了一些关于使用IO的API&#xff0c;可以供开发者来读写外部数据和文件&#xff0c;我们称这些API为Java IO。IO是Java中比较重要知识点&#xff0c;且比较难学习的知识点。并且随着Java的发展为提供更好的数据传输性能&#xff0c;目前有三种IO共…

Go基础学习06-Golang标准库container/list(双向链表)深入讲解;延迟初始化技术;Element;List;Ring

基础介绍 单向链表中的每个节点包含数据和指向下一个节点的指针。其特点是每个节点只知道下一个节点的位置&#xff0c;使得数据只能单向遍历。 示意图如下&#xff1a; 双向链表中的每个节点都包含指向前一个节点和后一个节点的指针。这使得在双向链表中可以从前向后或从后…

无人机之数据提取篇

一、无人机平台与传感器 无人机是进行数据采集的基础设施&#xff0c;其稳定性、可靠性、灵活性和负载能力直接影响到数据采集的效果。根据实际需求&#xff0c;需选择适合的无人机类型&#xff0c;如固定翼无人机适合大范围、长时间的数据采集&#xff0c;而多旋翼无人机则更适…

HTML基础用法介绍一

VS code 如何快速生成HTML骨架注释是什么&#xff1f;为什么要写注释&#xff1f;注释的标签是什么&#xff1f;标题标签段落标签换行标签与水平线标签 (都是单标签&#xff09;文本格式化标签图片标签超链接标签音频标签视频标签 &#x1f698;正片开始 VS code 如何快速生成…

基于Spring框架的分层解耦详解

博客主页&#xff1a;誓则盟约系列专栏&#xff1a;Java Web关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Java Web 三层架构&#xff1a; Java Web可以大致被分为三层架构&#xff1a;…

成都睿明智科技有限公司抖音电商服务靠谱吗?

在这个电商风起云涌的时代&#xff0c;抖音作为短视频直播的超级流量池&#xff0c;正深刻改变着人们的购物习惯。无数商家蜂拥而至&#xff0c;渴望在这片蓝海中找到属于自己的岛屿。而提及抖音电商服务&#xff0c;成都睿明智科技有限公司无疑是一个备受瞩目的名字。那么&…

Linux 进程的基本概念及描述

目录 0.前言 1. 什么是进程 1.1 进程的定义与特性 1.2 进程与线程的区别 2.描述进程 2.1 PCB (进程控制块) 2.2 task_struct 3.查看进程 3.1 查看进程信息 3.1.1 /proc 文件系统 3.1.2 ps 命令 3.1.2 top 和 htop 命令 3.2 获取进程标识符 3.2.1使用命令获取PID 3.2.2 使用C语言…

加密与安全_HTOP 一次性密码生成算法

文章目录 HOTP 的基础原理HOTP 的工作流程HOTP 的应用场景HOTP 的安全性安全性增强措施Code生成HOTP可配置项校验HOTP可拓展功能计数器&#xff08;counter&#xff09;计数器在客户端和服务端的作用计数器的同步机制客户端和服务端中的计数器表现服务端如何处理计数器不同步计…

AIGC学习笔记—minimind详解+训练+推理

前言 这个开源项目是带我的一个导师&#xff0c;推荐我看的&#xff0c;记录一下整个过程&#xff0c;总结一下收获。这个项目的slogan是“大道至简”&#xff0c;确实很简。作者说是这个项目为了帮助初学者快速入门大语言模型&#xff08;LLM&#xff09;&#xff0c;通过从零…

vue3学习记录-computed

vue3学习记录-computed 1.为什么要用computed2.使用方法2.1 基本实例2.2 可写计算属性 1.为什么要用computed 写个购物车的案例 <script setup> import { ref, reactive,computed } from "vue" const tableData reactive([{ name: 商品1, price: 10, num: 1…