Phoenix框架:Java解码Session Cookie乱码的原因及解决方法
问题背景
我尝试用两种方式解码Phoenix Session Cookie:
IEx(Elixir交互shell)示例
iex(1)> set_cookie = "SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYZFRuNUtQMkJ5YWtKT1JnWUtCeXhmNmdP.l0T3G-i8I5dMwz7lEZnQAeK_WeqEZTxcDeyNY2poz_M" "SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYZFRuNUtQMkJ5YWtKT1JnWUtCeXhmNmdP.l0T3G-i8I5dMwz7lEZnQAeK_WeqEZTxcDeyNY2poz_M" iex(2)> [_, payload, _] = String.split(set_cookie, ".", parts: 3) ["SFMyNTY", "g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYZFRuNUtQMkJ5YWtKT1JnWUtCeXhmNmdP", "l0T3G-i8I5dMwz7lEZnQAeK_WeqEZTxcDeyNY2poz_M"] iex(3)> {:ok, encoded_term } = Base.url_decode64(payload, padding: false) {:ok, <<131, 116, 0, 0, 0, 1, 109, 0, 0, 0, 11, 95, 99, 115, 114, 102, 95, 116, 111, 107, 101, 110, 109, 0, 0, 0, 24, 100, 84, 110, 53, 75, 80, 50, 66, 121, 97, 107, 74, 79, 82, 103, 89, 75, 66, 121, 120, 102, ...>>} iex(4)> :erlang.binary_to_term(encoded_term) %{"_csrf_token" => "dTn5KP2ByakJORgYKByxf6gO"}
Java示例
public static String decodePhoenixSessionCookie(String sessionCookie) { String payload = sessionCookie.split("\\.")[1]; byte[] encoded_term = Base64.getUrlDecoder().decode(payload.getBytes()); return new String(encoded_term); }
Java输出
�tm_csrf_tokenmdTn5KP2ByakJORgYKByxf6gO
乱码原因
Phoenix Session的payload是Erlang Term二进制格式,并非纯文本:
- Erlang Term有固定二进制结构:开头的
<<131>>是Erlang Term的魔数,<<116>>表示这是Map类型,后续还有字段长度、字符串长度等二进制标识位。 - IEx中的
:erlang.binary_to_term/1是专门解析该格式的函数,会自动处理这些标识位,只提取有效键值对。 - Java直接将二进制转成String时,那些非可打印的标识位(魔数、类型标记、长度字段)会被显示成乱码,仅其中的字符串内容(
_csrf_token及对应值)是可打印的。
实现干净输出的方法
Java需要使用能解析Erlang Term格式的库,官方推荐JInterface(Erlang官方提供的Java交互库),或第三方库如erlang-term。以下是JInterface的实现示例:
1. 引入依赖(Maven)
<dependency> <groupId>com.ericsson.otp</groupId> <artifactId>erlang</artifactId> <version>2.2.1</version> </dependency>
2. 解码代码
import com.ericsson.otp.erlang.*; import java.util.Base64; public class PhoenixSessionDecoder { public static OtpErlangMap decodePhoenixSession(String sessionCookie) throws OtpErlangDecodeException { // 拆分Cookie,获取中间的Payload部分 String payload = sessionCookie.split("\\.")[1]; // URL安全Base64解码得到Erlang Term二进制数据 byte[] termBytes = Base64.getUrlDecoder().decode(payload); // 解析Erlang Term为Map结构 OtpInputStream inputStream = new OtpInputStream(termBytes); return (OtpErlangMap) inputStream.read_any(); } public static void main(String[] args) throws OtpErlangDecodeException { String cookie = "SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYZFRuNUtQMkJ5YWtKT1JnWUtCeXhmNmdP.l0T3G-i8I5dMwz7lEZnQAeK_WeqEZTxcDeyNY2poz_M"; OtpErlangMap sessionMap = decodePhoenixSession(cookie); // 输出指定键的值 OtpErlangString csrfToken = (OtpErlangString) sessionMap.get(new OtpErlangString("_csrf_token")); System.out.println("_csrf_token: " + csrfToken.stringValue()); // 遍历所有键值对 System.out.println("===== 所有Session内容 ====="); OtpErlangObject[] keys = sessionMap.keys(); for (OtpErlangObject key : keys) { OtpErlangObject value = sessionMap.get(key); System.out.printf("%s: %s%n", ((OtpErlangString) key).stringValue(), ((OtpErlangString) value).stringValue()); } } }
输出结果
_csrf_token: dTn5KP2ByakJORgYKByxf6gO ===== 所有Session内容 ===== _csrf_token: dTn5KP2ByakJORgYKByxf6gO
这样就能得到和IEx一致的干净输出结果。
内容的提问来源于stack exchange,提问作者okan
相关产品推荐
相关产品推荐

