建设局发公告的网站,微山做网站,网站建设公司宣传文案,用vps建网站备案因项目要求需要对一个没有源代码的老旧系统实现免登录功能#xff0c;系统采用前后端分离的方式部署#xff0c;登录时前端调用后台的认证接口#xff0c;认证接口返回token信息#xff0c;然后将token以json的方式存储到cookie中#xff0c;格式如下#xff1a; 这里有…因项目要求需要对一个没有源代码的老旧系统实现免登录功能系统采用前后端分离的方式部署登录时前端调用后台的认证接口认证接口返回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; SameSiteStrict;#proxy_cookie_flags ~ nosecure samesitestrict;}# 老旧系统location / {proxy_pass http://127.0.0.1:8000;proxy_cookie_path / /; httponly; secure; SameSiteStrict;proxy_cookie_flags ~ nosecure samesitestrict;}注意这里proxy_cookie_path和proxy_cookie_flags要注释掉否则在免登录跳转系统中设置的Cookie在跳转到现有系统后无法认证成功下面对这两个参数说明一下
proxy_cookie_path
proxy_cookie_path / /; httponly; secure; SameSiteStrict;proxy_cookie_path此指令用于修改通过Nginx代理传递的Cookie的路径以及其他属性。 /指定Cookie的路径为根路径这意味着Cookie对整个域名有效。 “; httponly; secure; SameSiteStrict”这部分是对Cookie添加额外的属性 httponly此属性指示浏览器仅允许HTTP(S)协议访问该Cookie不允许JavaScript访问这有助于防止跨站脚本攻击XSS。 secure此属性指示浏览器仅在HTTPS连接中传输该Cookie不允许在不安全的HTTP连接中传输这有助于保护Cookie不被中间人攻击窃取。 SameSiteStrict此属性控制Cookie在跨站请求中的发送行为。设置为Strict意味着Cookie仅在同站请求中发送不会在跨站请求中发送这有助于减少跨站请求伪造CSRF攻击的风险。
proxy_cookie_flags
proxy_cookie_flags ~ nosecure samesitestrict;proxy_cookie_flags此指令用于设置或修改通过Nginx代理传递的Cookie的标志。 ~这是一个正则表达式匹配操作符表示接下来的模式应用于所有匹配的Cookie。 nosecure此标志指示Nginx移除Cookie中的Secure属性。这意味着即使原始Cookie设置了Secure属性通过Nginx代理后该属性将被移除Cookie可以在HTTP和HTTPS连接中都传输。这通常不推荐因为它降低了安全性。 samesitestrict此标志指示Nginx添加或修改Cookie的SameSite属性为Strict。这与proxy_cookie_path中的SameSiteStrict类似控制Cookie在跨站请求中的发送行为。 注意通常情况下不建议在生产环境中使用nosecure标志因为它会降低Cookie的安全性。如果需要移除Secure属性应该仔细考虑安全性影响并确保有其他安全措施来保护数据。 这两行配置通常一起使用以确保通过Nginx代理传递的Cookie具有适当的安全属性。然而proxy_cookie_flags中的nosecure标志可能会抵消proxy_cookie_path中设置的secure属性因此在实际应用中需要谨慎使用。
免登录跳转
搭建一个SpringBoot工程前端实现一个简单的跳转页面test.html
!DOCTYPE html
html xmlns:thhttp://www.thymeleaf.org
headtitleOSS/titlemeta charsetUTF-8/meta nameviewport contentwidthdevice-width, initial-scale1.0,minimum-scale1.0,maximum-scale1.0,user-scalablenometa http-equivX-UA-Compatible contentIEEdge /
/head
bodya hrefhttp://192.168.31.112http://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; SameSiteStrict;// proxy_cookie_flags ~ nosecure samesitestrict;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_nameadmin; time);response.addHeader(Set-Cookie, is_anagertrue; 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.port8084
server.servlet.context-path/ossapp.login.urlhttp://192.168.31.112/v1/auth/login
app.useradmin
app.pass4de93544234adffbb681ed60ffcfb941
app.index.urlhttp://192.168.31.112spring.thymeleaf.cachefalse
spring.thymeleaf.check-templatetrue
spring.thymeleaf.check-template-locationtrue
spring.thymeleaf.servlet.content-typetext/html
spring.thymeleaf.enabledtrue
spring.thymeleaf.encodingUTF-8
spring.thymeleaf.excluded-view-names
spring.thymeleaf.modeHTML5
spring.thymeleaf.prefixclasspath:/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_nameadmin; is_anagertrue;;let expirationDate new Date();expirationDate.setDate(expirationDate.getDate() 7);document.cookie str expires expirationDate.toUTCString() ;path/;
});