Files

892 lines
43 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<meta name="theme-color" content="#222"><meta name="generator" content="Hexo 6.3.0">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
<link rel="mask-icon" href="/images/logo.svg" color="#222">
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.0/css/all.min.css" integrity="sha256-VHqXKFhhMxcpubYf9xiWdCiojEbY9NexQ4jh8AxbvcM=" crossorigin="anonymous">
<script class="next-config" data-name="main" type="application/json">{"hostname":"toozhao.com","root":"/","images":"/images","scheme":"Muse","darkmode":false,"version":"8.28.0","exturl":false,"sidebar":{"position":"left","width_expanded":320,"width_dual_column":240,"display":"post","padding":18,"offset":12,"b2t":true,"scrollpercent":true},"hljswrap":true,"codeblock":{"theme":{"light":"default","dark":"stackoverflow-dark"},"prism":{"light":"prism","dark":"prism-dark"},"copy_button":{"enable":true,"style":"mac"},"fold":{"enable":false,"height":500},"language":false},"bookmark":{"enable":true,"color":"#222","save":"auto"},"mediumzoom":true,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"stickytabs":false,"motion":{"enable":false,"async":false,"duration":200,"transition":{"menu_item":"fadeInDown","post_block":"fadeIn","post_header":"fadeInDown","post_body":"fadeInDown","coll_header":"fadeInLeft","sidebar":"fadeInUp"}},"prism":false,"i18n":{"placeholder":"搜索...","empty":"没有找到任何搜索结果:${query}","hits_time":"找到 ${hits} 个搜索结果(用时 ${time} 毫秒)","hits":"找到 ${hits} 个搜索结果"},"path":"/search.xml","localsearch":{"enable":true,"top_n_per_article":5,"unescape":false,"preload":false,"trigger":"auto"}}</script><script src="/js/config.js" defer></script>
<meta name="description" content="原谅我使用如此晦涩的词语来表达我的意思,其实我想表达的意思的用java实现类似于当其他人登录你的QQ时,你会收到“你的账号已在其他地方登录,请重新登录。”这样的东西,简单的说就是同一时刻一账号只允许一个人登录在线状态。也许就是保证时间、空间上的唯一性。但是我想强调的是这种效果绝不是单点登录,真正的单点登录是类似于Google中,使用多个产品只需要登录一次,其实QQ也有这样的功能。好了,废话少说下面">
<meta property="og:type" content="article">
<meta property="og:title" content="Java实现单一账号某时刻单一登录">
<meta property="og:url" content="https://toozhao.com/2011/12/20/java-single-sign-on/">
<meta property="og:site_name" content="大染志">
<meta property="og:description" content="原谅我使用如此晦涩的词语来表达我的意思,其实我想表达的意思的用java实现类似于当其他人登录你的QQ时,你会收到“你的账号已在其他地方登录,请重新登录。”这样的东西,简单的说就是同一时刻一账号只允许一个人登录在线状态。也许就是保证时间、空间上的唯一性。但是我想强调的是这种效果绝不是单点登录,真正的单点登录是类似于Google中,使用多个产品只需要登录一次,其实QQ也有这样的功能。好了,废话少说下面">
<meta property="og:locale" content="zh_CN">
<meta property="article:published_time" content="2011-12-20T13:00:00.000Z">
<meta property="article:modified_time" content="2026-08-11T12:22:51.369Z">
<meta property="article:author" content="Junv">
<meta property="article:tag" content="java">
<meta property="article:tag" content="Application">
<meta property="article:tag" content="JAVA EE">
<meta property="article:tag" content="Session">
<meta name="twitter:card" content="summary">
<link rel="canonical" href="https://toozhao.com/2011/12/20/java-single-sign-on/">
<script class="next-config" data-name="page" type="application/json">{"sidebar":"","isHome":false,"isPost":true,"lang":"zh-CN","comments":true,"permalink":"https://toozhao.com/2011/12/20/java-single-sign-on/","path":"2011/12/20/java-single-sign-on/","title":"Java实现单一账号某时刻单一登录"}</script>
<script class="next-config" data-name="calendar" type="application/json">""</script>
<title>Java实现单一账号某时刻单一登录 | 大染志</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js" integrity="sha256-XL2inqUJaslATFnHdJOi9GfQ60on8Wx1C2H8DYiN1xY=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/medium-zoom/1.1.0/medium-zoom.min.js" integrity="sha256-ZgMyDAIYDYGxbcpJcfUnYwNevG/xi9OHKaR/8GK+jWc=" crossorigin="anonymous" defer></script>
<script src="/js/utils.js" defer></script><script src="/js/sidebar.js" defer></script><script src="/js/next-boot.js" defer></script><script src="/js/bookmark.js" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/hexo-generator-searchdb/1.5.0/search.js" integrity="sha256-xFC6PJ82SL9b3WkGjFavNiA9gm5z6UBxWPiu4CYjptg=" crossorigin="anonymous" defer></script>
<script src="/js/third-party/search/local-search.js" defer></script>
<style>
/* =========================================================
Toozhao blog custom styles (injected via head.njk)
阅读体验优化 — 微信读书风格
========================================================= */
html { scroll-behavior: smooth; }
/* 主内容阅读宽度 */
.post-block, .posts-expand .post-block {
max-width: 760px;
}
.post-title {
font-size: 1.7em;
font-weight: 700;
letter-spacing: 0.01em;
}
.post-meta { color: #999; font-size: 0.9em; }
.post-body {
font-size: 1.05em;
line-height: 1.9;
letter-spacing: 0.02em;
color: #333;
}
.post-body p {
margin: 1.1em 0;
text-align: justify;
text-justify: inter-ideograph;
}
.post-body h1, .post-body h2, .post-body h3, .post-body h4, .post-body h5, .post-body h6 {
line-height: 1.4;
margin-top: 1.6em;
margin-bottom: 0.8em;
font-weight: 600;
color: #222;
}
.post-body h2::before, .post-body h3::before, .post-body h4::before {
content: '';
display: inline-block;
width: 4px;
height: 0.9em;
background: #4a90d9;
border-radius: 2px;
margin-right: 10px;
vertical-align: -0.08em;
}
.post-body img {
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
margin: 0.6em auto;
display: block;
max-width: 100%;
}
.post-body blockquote {
border-left: 4px solid #4a90d9;
background: rgba(74, 144, 217, 0.06);
margin: 1.4em 0;
padding: 0.8em 1.2em;
border-radius: 0 8px 8px 0;
color: #555;
}
.post-body blockquote p { margin: 0.4em 0; }
.post-body a {
color: #2d6cdf;
border-bottom: 1px solid rgba(45, 108, 223, 0.3);
transition: color 0.2s, border-color 0.2s;
}
.post-body a:hover { color: #1a4fae; border-bottom-color: #1a4fae; }
.post-body code {
background: rgba(0, 0, 0, 0.05);
border-radius: 4px;
padding: 2px 6px;
font-size: 0.9em;
color: #c7254e;
}
.post-body table {
border-collapse: collapse;
margin: 1.2em auto;
font-size: 0.95em;
}
.post-body table th, .post-body table td {
border: 1px solid #ddd;
padding: 8px 14px;
}
.post-body table th { background: #f5f7fa; font-weight: 600; }
.post-body table tr:nth-child(even) { background: #fafbfc; }
.post-body ul, .post-body ol { padding-left: 1.6em; margin: 0.8em 0; }
.post-body ul li, .post-body ol li { margin: 0.35em 0; }
.post-body hr {
border: none;
border-top: 1px dashed #ccc;
margin: 2em auto;
width: 60%;
}
/* 暗色模式(跟随系统,不依赖 NexT darkmode class */
@media (prefers-color-scheme: dark) {
body { background: #1b1f24; color: #c8cdd3; }
.post-body { color: #c8cdd3; }
.post-body h1, .post-body h2, .post-body h3, .post-body h4, .post-body h5, .post-body h6 { color: #e8eaed; }
.post-body blockquote { background: rgba(74, 144, 217, 0.1); color: #9aa5b1; }
.post-body code { background: rgba(255, 255, 255, 0.1); color: #e88388; }
.post-body table th, .post-body table td { border-color: #3a3f45; }
.post-body table th { background: #2d3238; }
.post-body table tr:nth-child(even) { background: #262a2f; }
.post-meta { color: #777; }
}
::selection { background: rgba(74, 144, 217, 0.25); }
/* =========================================================
阅读设置(Aa 悬浮按钮 + 面板)— 参考 Links Reading settings
========================================================= */
/* 悬浮按钮:左下,与 sidebar-toggle 排成一列 */
.reading-toggle {
position: fixed;
left: 24px;
bottom: 108px;
z-index: 1000;
width: 34px;
height: 34px;
border-radius: 50%;
background: rgba(0, 0, 0, 0.65);
color: #fff;
font-family: Georgia, "Times New Roman", serif;
font-size: 16px;
font-weight: 700;
font-style: italic;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
user-select: none;
border: none;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.28);
transition: background 0.2s, transform 0.2s;
}
.reading-toggle:hover { background: #4a90d9; transform: scale(1.1); }
.reading-toggle.active { background: #4a90d9; }
/* 设置面板 */
.reading-panel {
position: fixed;
left: 68px;
bottom: 108px;
z-index: 1001;
width: 268px;
background: #fff;
border-radius: 14px;
box-shadow: 0 14px 44px rgba(0, 0, 0, 0.2);
padding: 16px 18px 14px;
font-size: 13px;
color: #333;
display: none;
transform-origin: bottom left;
animation: rpIn 0.18s ease;
}
.reading-panel.open { display: block; }
@keyframes rpIn {
from { opacity: 0; transform: translateY(8px) scale(0.96); }
to { opacity: 1; transform: none; }
}
.reading-panel h4 {
margin: 0 0 8px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.1em;
color: #9aa0a8;
text-transform: uppercase;
}
.reading-panel .rp-row { margin-bottom: 15px; }
.reading-panel .rp-row:last-of-type { margin-bottom: 10px; }
/* 主题色 swatch */
.rp-themes { display: flex; gap: 8px; }
.rp-themes .sw {
flex: 1;
border: 2px solid transparent;
border-radius: 10px;
padding: 6px 4px 5px;
cursor: pointer;
background: none;
font-size: 12px;
color: #666;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
transition: border-color 0.15s;
}
.rp-themes .sw span {
display: block;
width: 30px;
height: 22px;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, 0.12);
}
.rp-themes .sw.active { border-color: #4a90d9; color: #4a90d9; font-weight: 600; }
.sw-light span { background: #ffffff; }
.sw-paper span { background: #f7f0e4; }
.sw-green span { background: #e2efe4; }
.sw-night span { background: #1b1f24; }
/* 字体选项 */
.rp-fonts { display: flex; flex-wrap: wrap; gap: 6px; }
.rp-fonts button {
flex: 1 1 40%;
border: 1px solid #e2e5ea;
background: #f8f9fb;
border-radius: 8px;
padding: 6px 4px;
font-size: 12.5px;
color: #444;
cursor: pointer;
transition: all 0.15s;
}
.rp-fonts button.active { background: #eaf2fd; border-color: #4a90d9; color: #2d6cdf; font-weight: 600; }
.rp-fonts button:nth-child(n+5) { flex-basis: 100%; }
/* 字号 */
.rp-size { display: flex; align-items: center; gap: 10px; }
.rp-size button {
width: 36px;
height: 30px;
border-radius: 8px;
border: 1px solid #e2e5ea;
background: #f8f9fb;
font-size: 14px;
color: #444;
cursor: pointer;
transition: all 0.15s;
}
.rp-size button:hover { background: #eaf2fd; border-color: #4a90d9; color: #2d6cdf; }
.rp-size-val {
flex: 1;
text-align: center;
font-size: 13.5px;
font-weight: 600;
color: #555;
background: #f8f9fb;
border-radius: 8px;
padding: 5px 0;
}
/* 行距 */
.rp-spacing { display: flex; gap: 6px; }
.rp-spacing button {
flex: 1;
border: 1px solid #e2e5ea;
background: #f8f9fb;
border-radius: 8px;
padding: 6px 0;
font-size: 12.5px;
color: #444;
cursor: pointer;
transition: all 0.15s;
}
.rp-spacing button.active { background: #eaf2fd; border-color: #4a90d9; color: #2d6cdf; font-weight: 600; }
/* 重置 */
.rp-reset {
width: 100%;
border: none;
background: none;
color: #9aa0a8;
font-size: 12.5px;
padding: 8px 0 2px;
cursor: pointer;
border-top: 1px solid #eef0f3;
margin-top: 2px;
transition: color 0.15s;
}
.rp-reset:hover { color: #4a90d9; }
/* ---- 阅读主题应用 ---- */
body[data-rtheme="paper"] { background: #f7f0e4 !important; }
body[data-rtheme="paper"] .post-body { color: #4a4238; }
body[data-rtheme="paper"] .post-body h1, body[data-rtheme="paper"] .post-body h2,
body[data-rtheme="paper"] .post-body h3, body[data-rtheme="paper"] .post-body h4,
body[data-rtheme="paper"] .post-body h5, body[data-rtheme="paper"] .post-body h6 { color: #3a332b; }
body[data-rtheme="paper"] .post-body a { color: #8a5a2b; border-bottom-color: rgba(138, 90, 43, 0.3); }
body[data-rtheme="paper"] .post-body a:hover { color: #6b4420; }
body[data-rtheme="paper"] .post-body blockquote { background: rgba(138, 90, 43, 0.07); color: #6b5d4a; }
body[data-rtheme="paper"] .post-body code { background: rgba(0, 0, 0, 0.06); color: #8a4a3a; }
body[data-rtheme="paper"] .post-body table th { background: #efe7d5; }
body[data-rtheme="paper"] .post-body table tr:nth-child(even) { background: #f4ede0; }
body[data-rtheme="paper"] .reading-panel { background: #fdfaf3; color: #4a4238; }
body[data-rtheme="green"] { background: #e2efe4 !important; }
body[data-rtheme="green"] .post-body { color: #2f4a36; }
body[data-rtheme="green"] .post-body h1, body[data-rtheme="green"] .post-body h2,
body[data-rtheme="green"] .post-body h3, body[data-rtheme="green"] .post-body h4,
body[data-rtheme="green"] .post-body h5, body[data-rtheme="green"] .post-body h6 { color: #23402c; }
body[data-rtheme="green"] .post-body a { color: #1d6b3c; border-bottom-color: rgba(29, 107, 60, 0.3); }
body[data-rtheme="green"] .post-body a:hover { color: #14522d; }
body[data-rtheme="green"] .post-body blockquote { background: rgba(29, 107, 60, 0.08); color: #41604c; }
body[data-rtheme="green"] .post-body code { background: rgba(0, 0, 0, 0.05); color: #1d6b3c; }
body[data-rtheme="green"] .post-body table th { background: #d4e8d8; }
body[data-rtheme="green"] .post-body table tr:nth-child(even) { background: #dcecdd; }
body[data-rtheme="green"] .reading-panel { background: #f0f8f1; color: #2f4a36; }
body[data-rtheme="night"] { background: #1b1f24 !important; }
body[data-rtheme="night"] .post-body { color: #c8cdd3; }
body[data-rtheme="night"] .post-body h1, body[data-rtheme="night"] .post-body h2,
body[data-rtheme="night"] .post-body h3, body[data-rtheme="night"] .post-body h4,
body[data-rtheme="night"] .post-body h5, body[data-rtheme="night"] .post-body h6 { color: #e8eaed; }
body[data-rtheme="night"] .post-body a { color: #7ba6f0; border-bottom-color: rgba(123, 166, 240, 0.35); }
body[data-rtheme="night"] .post-body a:hover { color: #a5c4f5; }
body[data-rtheme="night"] .post-body blockquote { background: rgba(123, 166, 240, 0.1); color: #9aa5b1; }
body[data-rtheme="night"] .post-body code { background: rgba(255, 255, 255, 0.1); color: #e88388; }
body[data-rtheme="night"] .post-body table th, body[data-rtheme="night"] .post-body table td { border-color: #3a3f45; }
body[data-rtheme="night"] .post-body table th { background: #2d3238; }
body[data-rtheme="night"] .post-body table tr:nth-child(even) { background: #262a2f; }
body[data-rtheme="night"] .reading-toggle { background: rgba(255, 255, 255, 0.14); }
body[data-rtheme="night"] .reading-panel { background: #23282e; color: #c8cdd3; }
body[data-rtheme="night"] .reading-panel h4 { color: #7a828c; }
body[data-rtheme="night"] .rp-fonts button, body[data-rtheme="night"] .rp-size button,
body[data-rtheme="night"] .rp-spacing button { background: #2d333a; border-color: #3a414a; color: #c8cdd3; }
body[data-rtheme="night"] .rp-fonts button.active, body[data-rtheme="night"] .rp-size button:hover,
body[data-rtheme="night"] .rp-spacing button.active { background: #35465c; border-color: #4a90d9; color: #9dc0f0; }
body[data-rtheme="night"] .rp-size-val { background: #2d333a; color: #c8cdd3; }
body[data-rtheme="night"] .rp-themes .sw { color: #9aa5b1; }
body[data-rtheme="night"] .rp-reset { border-top-color: #333a42; color: #7a828c; }
/* 移动端:按钮缩小、面板右移防溢出 */
@media (max-width: 600px) {
.reading-toggle { left: 16px; bottom: 96px; width: 32px; height: 32px; }
.reading-panel { left: 56px; bottom: 96px; width: 240px; }
}
</style>
<link rel="stylesheet" href="/css/tag-cloud.css">
<noscript>
<link rel="stylesheet" href="/css/noscript.css">
</noscript>
</head>
<body itemscope itemtype="http://schema.org/WebPage">
<div class="headband"></div>
<main class="main">
<div class="column">
<header class="header" itemscope itemtype="http://schema.org/WPHeader"><div class="site-brand-container">
<div class="site-nav-toggle">
<div class="toggle" aria-label="切换导航栏" role="button">
<span class="toggle-line"></span>
<span class="toggle-line"></span>
<span class="toggle-line"></span>
</div>
</div>
<div class="site-meta">
<a href="/" class="brand" rel="start">
<i class="logo-line"></i>
<p class="site-title">大染志</p>
<i class="logo-line"></i>
</a>
<p class="site-subtitle" itemprop="description">Junv's blog</p>
</div>
<div class="site-nav-right">
<div class="toggle popup-trigger" aria-label="搜索" role="button">
<i class="fa fa-search fa-fw fa-lg"></i>
</div>
</div>
</div>
<nav class="site-nav">
<ul class="main-menu menu"><li class="menu-item menu-item-home"><a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a></li><li class="menu-item menu-item-tags"><a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a></li><li class="menu-item menu-item-archives"><a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a></li><li class="menu-item menu-item-about"><a href="/about/" rel="section"><i class="fa fa-user fa-fw"></i>关于</a></li>
<li class="menu-item menu-item-search">
<a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
</a>
</li>
</ul>
</nav>
<div class="search-pop-overlay">
<div class="popup search-popup">
<div class="search-header">
<span class="search-icon">
<i class="fa fa-search"></i>
</span>
<div class="search-input-container">
<input autocomplete="off" autocapitalize="off" maxlength="80"
placeholder="搜索..." spellcheck="false"
type="search" class="search-input">
</div>
<span class="popup-btn-close" role="button">
<i class="fa fa-times-circle"></i>
</span>
</div>
<div class="search-result-container">
<div class="search-result-icon">
<i class="fa fa-spinner fa-pulse fa-5x"></i>
</div>
</div>
</div>
</div>
</header>
<aside class="sidebar">
<div class="sidebar-inner sidebar-overview-active">
<ul class="sidebar-nav">
<li class="sidebar-nav-toc">
文章目录
</li>
<li class="sidebar-nav-overview">
站点概览
</li>
</ul>
<div class="sidebar-panel-container">
<!--noindex-->
<div class="post-toc-wrap sidebar-panel">
</div>
<!--/noindex-->
<div class="site-overview-wrap sidebar-panel">
<div class="site-author animated" itemprop="author" itemscope itemtype="http://schema.org/Person">
<img class="site-author-image" itemprop="image" alt="Junv"
src="/images/avatar.gif">
<p class="site-author-name" itemprop="name">Junv</p>
<div class="site-description" itemprop="description">分享编程、运维、生活与思考</div>
</div>
<div class="site-state-wrap animated">
<nav class="site-state">
<div class="site-state-item site-state-posts">
<a href="/archives/">
<span class="site-state-item-count">310</span>
<span class="site-state-item-name">日志</span>
</a>
</div>
<div class="site-state-item site-state-tags">
<a href="/tags/">
<span class="site-state-item-count">249</span>
<span class="site-state-item-name">标签</span></a>
</div>
</nav>
</div>
<div class="links-of-author animated">
<span class="links-of-author-item">
<a href="https://github.com/wahyd4" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;wahyd4" rel="noopener me" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
</span>
</div>
<div class="cc-license animated" itemprop="license">
<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" class="cc-opacity" rel="noopener" target="_blank"><img src="https://cdnjs.cloudflare.com/ajax/libs/creativecommons-vocabulary/2020.11.3/assets/license_badges/small/by_nc_sa.svg" alt="Creative Commons"></a>
</div>
</div>
</div>
</div>
</aside>
</div>
<div class="main-inner post posts-expand">
<div class="post-block">
<article itemscope itemtype="http://schema.org/Article" class="post-content" lang="zh-CN">
<link itemprop="mainEntityOfPage" href="https://toozhao.com/2011/12/20/java-single-sign-on/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="Junv">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="大染志">
<meta itemprop="description" content="分享编程、运维、生活与思考">
</span>
<span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
<meta itemprop="name" content="Java实现单一账号某时刻单一登录 | 大染志">
<meta itemprop="description" content="">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
Java实现单一账号某时刻单一登录
</h1>
<div class="post-meta-container">
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2011-12-21 00:00:00" itemprop="dateCreated datePublished" datetime="2011-12-21T00:00:00+11:00">2011-12-21</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar-check"></i>
</span>
<span class="post-meta-item-text">更新于</span>
<time title="修改时间:2026-08-11 22:22:51" itemprop="dateModified" datetime="2026-08-11T22:22:51+10:00">2026-08-11</time>
</span>
</div>
</div>
</header>
<div class="post-body" itemprop="articleBody"><p>原谅我使用如此晦涩的词语来表达我的意思,其实我想表达的意思的用java实现类似于当其他人登录你的QQ时,你会收到“你的账号已在其他地方登录,请重新登录。”这样的东西,简单的说就是同一时刻一账号只允许一个人登录在线状态。也许就是保证时间、空间上的唯一性。但是我想强调的是这种效果绝不是单点登录,真正的单点登录是类似于Google中,使用多个产品只需要登录一次,其实QQ也有这样的功能。好了,废话少说下面说正经的。</p>
<p>我们最常用的用来验证用户使用登录的东西是Session,但是session并不能达到我们想要的效果,因为每个客户端访问网站的时候都会获得一个session,因此虽然是同一个账号登录系统,只要用户使用的浏览器,服务器都会产生两个session。但是每个客户端访问服务器的服务器的时候都会产生一个SessionId,而这个ID这是唯一的,我们会在后面用到。这里我们使用使用Application 域,在Java EE里面是ServletContext。这个东西是一个全局变量,当Web 程序运行的时候便会一直存在。而且整个程序只有一个Application.</p>
<p>于是我们可以这样。我们将数据存入Application中。而存入的内容则是:键(key)为当前用户使用用户名或者其他一个可以识别用户身份信息的ID,而值(value)则是用户得到的sessionID。这样便保证了一个账号同时至于一个用户对应。在用户登录的时候想Application中写入 key:value信息,在以后用户每次请求数据的时候,进行验证。看Application中的SessionID与用户本身的SessionID是否一致,不一致则说明多人登录。</p>
<p>下面是代码了:(我感觉说了好多废话)</p>
<p>首先获取Application:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">HttpSession</span> <span class="variable">session</span> <span class="operator">=</span> request.getSession(); <span class="comment">//request为HttpServletRequest对象</span></span><br><span class="line"> <span class="type">ServletContext</span> <span class="variable">application</span> <span class="operator">=</span> session.getServletContext();</span><br></pre></td></tr></table></figure>
<p>向其中存入我们的验证数据,这里键为用户ID,值为该用户实例获得的sessionID</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">application.setAttribute(userId, session.getId());<span class="comment">//userID为用户ID</span></span><br></pre></td></tr></table></figure>
<p>在每次验证的时候,在java里面我们可以采用至少两种方法,一种是Filter(Servlet 过滤器)、另外一种则是Spring AOC 面向切面编程,这里由于我对Spring AOC还不太熟悉,所以就用Filter,Filter具体设置,编写都不说了,只说一些怎么处理。拿到一个客户端来的请求,就进行sessionID比较,如果相同就让请求继续,如果不相同,就将该请求所对于的session清空(如果采用session验证用户是否登录,就可以达到让该用户退出登录),并给出提示信息,跳转到登录页面。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//获取application对象</span></span><br><span class="line"> <span class="type">ServletContext</span> <span class="variable">application</span> <span class="operator">=</span> session.getServletContext();</span><br><span class="line"> <span class="type">String</span> <span class="variable">sessionId</span> <span class="operator">=</span> (String) application.getAttribute(userid);</span><br><span class="line"> <span class="type">String</span> <span class="variable">trueSessionId</span> <span class="operator">=</span> session.getId();</span><br><span class="line"> System.out.println(<span class="string">&quot;sessionID=====&quot;</span>+sessionId+<span class="string">&quot;现在的sessionID&quot;</span>+trueSessionId);</span><br><span class="line"> <span class="keyword">if</span>(sessionId.equals(trueSessionId))&#123;</span><br><span class="line"> <span class="comment">//do nothing</span></span><br><span class="line"> &#125;<span class="keyword">else</span>&#123;</span><br><span class="line"> <span class="comment">//说明有多个用户登录,去掉其session</span></span><br><span class="line"> session.removeAttribute(sessionKey);</span><br><span class="line"> <span class="comment">//response.sendRedirect(request.getContextPath() + redirectURL);</span></span><br><span class="line"> response.setContentType(<span class="string">&quot;text/html;charset=utf-8&quot;</span>);</span><br><span class="line"> <span class="type">PrintWriter</span> <span class="variable">out</span> <span class="operator">=</span> response.getWriter();</span><br><span class="line"> out.println(MyTools.getAlertMessage(request.getContextPath(), <span class="string">&quot;您的账户已在别处登录,请重新登录&quot;</span>, <span class="number">3</span>));</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>
<p>这里可以使用sendRedirect,将用户直接跳转到某个页面,但是这样不太友好,因为没有提示信息,我选择另外一个使用response给用户一些提示信息,然后设置一段时间后跳转到另外的页面。这就是我写的一个静态方法 getAlertMessage,下面贴出具体代码吧。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 生成用户返回的警告页面信息</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> path</span></span><br><span class="line"><span class="comment"> * 系统根目录</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> content</span></span><br><span class="line"><span class="comment"> * 需要显示的内容</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> second</span></span><br><span class="line"><span class="comment"> * 需要停留消息的秒数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> String</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">getAlertMessage</span><span class="params">(String path, String content, <span class="type">int</span> second)</span> &#123;</span><br><span class="line"> <span class="type">StringBuilder</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line"> sb.append(<span class="string">&quot;&quot;</span>);</span><br><span class="line"> sb.append(<span class="string">&quot;&quot;</span>);</span><br><span class="line"> sb.append(<span class="string">&quot;&quot;</span>);</span><br><span class="line"> sb.append(<span class="string">&quot;&quot;</span>);</span><br><span class="line"> sb.append(<span class="string">&quot;&quot;</span>);</span><br><span class="line"> sb.append(<span class="string">&quot;&quot;</span></span><br><span class="line"> + content</span><br><span class="line"> + <span class="string">&quot;,&quot;</span></span><br><span class="line"> + second</span><br><span class="line"> + <span class="string">&quot;秒后你将跳转到登录页面&quot;</span>);</span><br><span class="line"> sb.append(<span class="string">&quot;&quot;</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> sb.toString();</span><br><span class="line"></span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>
<p>好了,现在差不多实现这个功能的主要代码都有了。原谅我的表达能力不够好,如果各位有什么问题,留言吧。我们可以讨论一下。</p>
</div>
<footer class="post-footer">
<div class="post-copyright">
<ul>
<li class="post-copyright-author">
<strong>本文作者: </strong>Junv
</li>
<li class="post-copyright-link">
<strong>本文链接:</strong>
<a href="https://toozhao.com/2011/12/20/java-single-sign-on/" title="Java实现单一账号某时刻单一登录">https://toozhao.com/2011/12/20/java-single-sign-on/</a>
</li>
<li class="post-copyright-license">
<strong>版权声明: </strong>本博客所有文章除特别声明外,均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" rel="noopener" target="_blank"><i class="fab fa-fw fa-creative-commons"></i>BY-NC-SA</a> 许可协议。转载请注明出处!
</li>
</ul>
</div>
<div class="post-tags">
<a href="/tags/java/" rel="tag"># java</a>
<a href="/tags/Application/" rel="tag"># Application</a>
<a href="/tags/JAVA-EE/" rel="tag"># JAVA EE</a>
<a href="/tags/Session/" rel="tag"># Session</a>
</div>
<div class="post-nav">
<div class="post-nav-item">
<a href="/2011/11/28/spring-hibernate-db-too-many-connection/" rel="prev" title="解决Spring 管理Hibernate执行数据库操作出现“too many connection&amp;#8221;的错误">
<i class="fa fa-angle-left"></i> 解决Spring 管理Hibernate执行数据库操作出现“too many connection&#8221;的错误
</a>
</div>
<div class="post-nav-item">
<a href="/2011/12/29/nexus-cant-run-under-jdk-1-7/" rel="next" title="Nexus 1.9 在JDK 1.7下不能安装运行">
Nexus 1.9 在JDK 1.7下不能安装运行 <i class="fa fa-angle-right"></i>
</a>
</div>
</div>
</footer>
</article>
</div>
</div>
</main>
<footer class="footer">
<div class="footer-inner">
<div class="copyright">
&copy; 2012
<span itemprop="copyrightYear">2026</span>
<span class="with-love">
<i class="fa fa-heart"></i>
</span>
<span class="author" itemprop="copyrightHolder">Junv</span>
</div>
<div class="powered-by"><a href="https://hexo.io/" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.js.org/muse/" rel="noopener" target="_blank">NexT.Muse</a> 强力驱动
</div>
</div>
</footer>
<div class="toggle sidebar-toggle" role="button">
<span class="toggle-line"></span>
<span class="toggle-line"></span>
<span class="toggle-line"></span>
</div>
<div class="sidebar-dimmer"></div>
<div class="back-to-top" role="button" aria-label="返回顶部">
<i class="fa fa-arrow-up fa-lg"></i>
<span>0%</span>
</div>
<div class="reading-progress-bar"></div>
<a role="button" class="book-mark-link book-mark-link-fixed"></a>
<noscript>
<div class="noscript-warning">Theme NexT works best with JavaScript enabled</div>
</noscript>
<script src="/js/tag-cloud.js"></script>
<script>
/* =========================================================
阅读设置面板(Aa 按钮)— 参考 Links Reading settings
支持:背景主题 / 字体类型 / 字号 / 行距 / 重置,localStorage 记忆
========================================================= */
(function () {
'use strict';
var LS_KEY = 'toozhao-reading';
var state = { theme: 'light', font: 'system', size: 1.05, spacing: 'cozy' };
var FONTS = {
system: '', /* 默认(Linux 系统字体) */
serif: 'Georgia, "Noto Serif SC", "Source Han Serif SC", "Songti SC", SimSun, serif',
sans: '"Noto Sans SC", "Source Han Sans SC", "Microsoft YaHei", "PingFang SC", "WenQuanYi Micro Hei", sans-serif',
kai: '"KaiTi", "STKaiti", "Kaiti SC", "AR PL UKai CN", serif',
mono: 'SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace'
};
var THEMES = { light: '明亮', paper: '纸感', green: '护眼', night: '夜间' };
var SPACING = {
compact: { label: '紧凑', lh: 1.6 },
cozy: { label: '舒适', lh: 1.9 },
relaxed: { label: '宽松', lh: 2.3 }
};
function loadState() {
try {
var raw = localStorage.getItem(LS_KEY);
if (!raw) return;
var s = JSON.parse(raw);
if (THEMES[s.theme]) state.theme = s.theme;
if (FONTS.hasOwnProperty(s.font)) state.font = s.font;
if (typeof s.size === 'number' && s.size >= 0.8 && s.size <= 1.5) state.size = s.size;
if (SPACING[s.spacing]) state.spacing = s.spacing;
} catch (e) { /* ignore */ }
}
function saveState() {
try { localStorage.setItem(LS_KEY, JSON.stringify(state)); } catch (e) { /* ignore */ }
}
function buildUI() {
if (document.getElementById('reading-toggle')) return;
var toggle = document.createElement('button');
toggle.id = 'reading-toggle';
toggle.className = 'reading-toggle';
toggle.title = '阅读设置';
toggle.textContent = 'Aa';
document.body.appendChild(toggle);
var panel = document.createElement('div');
panel.id = 'reading-panel';
panel.className = 'reading-panel';
// 主题(swatch 全部为硬编码静态 HTML,无用户输入,安全)
var rowT = document.createElement('div');
rowT.className = 'rp-row';
rowT.innerHTML = '<h4>主题</h4><div class="rp-themes">' +
['light', 'paper', 'green', 'night'].map(function (t) {
return '<button class="sw sw-' + t + '" data-theme="' + t + '" title="' + THEMES[t] + '"><span></span>' + THEMES[t] + '</button>';
}).join('') + '</div>';
var rowF = document.createElement('div');
rowF.className = 'rp-row';
rowF.innerHTML = '<h4>字体</h4><div class="rp-fonts">' +
['system', 'serif', 'sans', 'kai', 'mono'].map(function (f) {
var label = { system: '系统', serif: '宋体', sans: '黑体', kai: '楷体', mono: '等宽' }[f];
return '<button data-font="' + f + '">' + label + '</button>';
}).join('') + '</div>';
var rowS = document.createElement('div');
rowS.className = 'rp-row';
rowS.innerHTML = '<h4>字号</h4><div class="rp-size">' +
'<button class="rp-minus" title="减小">A</button><span class="rp-size-val">17px</span><button class="rp-plus" title="增大">A+</button></div>';
var rowSp = document.createElement('div');
rowSp.className = 'rp-row';
rowSp.innerHTML = '<h4>行距</h4><div class="rp-spacing">' +
['compact', 'cozy', 'relaxed'].map(function (s) {
return '<button data-spacing="' + s + '">' + SPACING[s].label + '</button>';
}).join('') + '</div>';
var reset = document.createElement('button');
reset.className = 'rp-reset';
reset.textContent = '↺ 恢复默认';
reset.addEventListener('click', function () {
state = { theme: 'light', font: 'system', size: 1.05, spacing: 'cozy' };
saveState();
apply();
render();
});
panel.appendChild(rowT);
panel.appendChild(rowF);
panel.appendChild(rowS);
panel.appendChild(rowSp);
panel.appendChild(reset);
document.body.appendChild(panel);
toggle.addEventListener('click', function (e) {
e.stopPropagation();
panel.classList.toggle('open');
toggle.classList.toggle('active', panel.classList.contains('open'));
});
panel.addEventListener('click', function (e) { e.stopPropagation(); });
panel.querySelectorAll('.sw').forEach(function (b) {
b.addEventListener('click', function () { state.theme = b.dataset.theme; saveState(); apply(); render(); });
});
panel.querySelectorAll('.rp-fonts button').forEach(function (b) {
b.addEventListener('click', function () { state.font = b.dataset.font; saveState(); apply(); render(); });
});
panel.querySelectorAll('.rp-spacing button').forEach(function (b) {
b.addEventListener('click', function () { state.spacing = b.dataset.spacing; saveState(); apply(); render(); });
});
panel.querySelector('.rp-minus').addEventListener('click', function () {
state.size = Math.max(0.8, +(state.size - 0.05).toFixed(2)); saveState(); apply(); render();
});
panel.querySelector('.rp-plus').addEventListener('click', function () {
state.size = Math.min(1.5, +(state.size + 0.05).toFixed(2)); saveState(); apply(); render();
});
document.addEventListener('click', function (e) {
if (panel.classList.contains('open') && !panel.contains(e.target) && e.target !== toggle) {
panel.classList.remove('open');
toggle.classList.remove('active');
}
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && panel.classList.contains('open')) {
panel.classList.remove('open');
toggle.classList.remove('active');
}
});
}
function apply() {
var body = document.body;
var pb = document.querySelector('.post-body');
body.setAttribute('data-rtheme', state.theme);
if (pb) {
pb.style.fontFamily = FONTS[state.font] || '';
pb.style.fontSize = state.size + 'em';
pb.style.lineHeight = SPACING[state.spacing].lh;
}
}
function render() {
var panel = document.getElementById('reading-panel');
if (!panel) return;
panel.querySelectorAll('.sw').forEach(function (b) {
b.classList.toggle('active', b.dataset.theme === state.theme);
});
panel.querySelectorAll('.rp-fonts button').forEach(function (b) {
b.classList.toggle('active', b.dataset.font === state.font);
});
panel.querySelectorAll('.rp-spacing button').forEach(function (b) {
b.classList.toggle('active', b.dataset.spacing === state.spacing);
});
var val = panel.querySelector('.rp-size-val');
if (val) val.textContent = Math.round(16 * state.size) + 'px';
}
function init() {
loadState();
buildUI();
apply();
render();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
</script>
</body>
</html>