絕對完全跨域統一單點登入登出
應用場景:多個系統下同屬於一個使用者,當使用者登入了web1系統,那麼訪問web2,web3. . . . 時候,使用者就無需再次登入。如:淘寶與天貓,登出也如此,一個系統登出,其他系統的登入也隨之失效,這就是統一單點登入登出。
這裡配置三個web系統,一個使用者中心繫統為栗子
配置hosts實現跨域:
127.0.0.1 ssofront.ljtest.xxxx.com #使用者中心
127.0.0.1 my.kuaiji.com #web1
127.0.0.1 my.zikao.com #web2
127.0.0.1 my.xuelxuew.com #web3
nginx 配置用於統一登入頁面轉發
server {
listen 80;
server_name ssofront.ljtest.xxxx.com;
location /ajax/ {
proxy_pass http://ssofront.ljtest.xxxx.com:9092/;
}
location / {
root D:/Java/projects/SSO_Single;
index index.html;
}
}
先看看效果圖:
統一登入頁面:
所有的web系統登入都重定向到使用者中心的統一登入頁面
統一登入之後訪問各系統
當web1點選登入重定向到使用者中心統一登入之後,再訪問web2,web3系統都已經是登入狀態了。
能夠實現這種效果就說明使用者中心,3個web系統都分別在自己的域名下成功的把token放到各自的cookie下了,而且4個系統都是跨域的,所以cookie在這四個系統間是不可共享的,所以已經達到跨域統一登入了。
如何實現的??我們先看看流程圖
各位看官如果看完流程圖還不是很清晰,且聽我分析:
當我們第一次訪問web1系統時(未登入狀態,其他web系統也未登入過),此時經過過濾器,token,action當然為null,就會查詢cookie是否存在token,不過是否取得都把這個token拿到使用者中心去校驗,第一次肯定拿不到,所以校驗失敗,efftoken有效token為空,重定向到使用者中心(重定向由瀏覽器重新發出可以獲取到使用者中心的cookie)獲取cookie中的token,token去到SSO校驗是否失效,第一次訪問所以token為空失效,返回token,重定向回web1系統(此時得帶上引數:返回的token 和 action=callback(用以標識是使用者中心重定向回來的,避免多次重定向)),到這裡就完成了一次詢問,假如一直未登入,每次訪問web1系統都會去使用者中心詢問是否有別的系統登入過啊,有就返回有效token放入web1系統的session和cookie中,那麼下次訪問就拿著這個token去使用者中心校驗判斷是否失效。當我們去到使用者中心登入了生成token,token會放入使用者中心的cookie中,然後重定向回web1系統,經過過濾器時執行上述步驟便可以拿到使用者中心的有效token。其中任何一個系統登出都會呼叫使用者中心單點登出,此時token失效,其他系統再次訪問時就會再次詢問是否失效,失效清空cookie,這樣就完成統一登出啦。其他系統同理也是如此。
這麼還存在一個小問題就是當從使用者中心詢問發現沒有登入過,帶引數重定向回web1系統,瀏覽器上的url會加上引數 ?token=&action=callback,當我們只是重新整理頁面沒有其他操作,此時過濾器中action值為callback會跳過到使用者中心的詢問,而token是失效的未登入狀態,所以的點選頁面重新整理,這個問題大家如果想的好的解決辦法可以告訴我一下。
主要程式碼:
web系統:
Login.java
@RequestMapping("/login")
public void login(HttpServletRequest request, HttpServletResponse response){
try {
//獲取上一個URL地址
String previousUrl = request.getHeader("Referer").toString();
response.sendRedirect(LOGIN_URL+previousUrl);
}catch (IOException e) {
e.printStackTrace();
}
}
過濾器
/**
* 統一登入過濾器
* Created by Administrator on 2018/4/21 0021.
* @author Evan
*/
public class UnifiedLoginFilter extends OncePerRequestFilter {
@Value("${tokenExpired.url}")
private String TOKENEXPIRED_URL;
@Value("${userCenterToken.url}")
private String USERCENTERTOKEN_URL;
private final static String ACTION_NAME = "callback";
@Autowired
private UserService userService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String action = ServletRequestUtils.getStringParameter(request, "action", null);
HttpSession session = request.getSession();
String currentUrl = request.getRequestURL().toString();
//獲取使用者中心有效token
String effectiveToken = null;
String currentToken = null;
boolean doFilter = true;
if(ACTION_NAME.equals(action)) {
effectiveToken = ServletRequestUtils.getStringParameter(request, "token", null);
} else {
currentToken = (String) session.getAttribute(SystemConfig.SESSION_TOKEN_KEY);
Cookie[] cookies = request.getCookies();
if (StringUtils.isBlank(currentToken) && cookies != null) {
for (int i = 0; i < cookies.length; i++) {
Cookie cookie = cookies[i];
if (cookie != null && SystemConfig.COOKIE_TOKEN_NAME.equals(cookie.getName())) {
currentToken = cookie.getValue();
break;
}
}
}
String result = HttpUtil.doGet4Json(TOKENEXPIRED_URL+currentToken);
if(StringUtils.isNotBlank(result)) {
JSONObject object = JSONObject.parseObject(result);
effectiveToken = object.getString("data");
if(StringUtils.isBlank(effectiveToken)) {
doFilter = false;
response.sendRedirect(USERCENTERTOKEN_URL+currentUrl);
}
}
}
if(doFilter) {
//使用者中心token有效
if(StringUtils.isNotBlank(effectiveToken)) {
if(effectiveToken.equals(currentToken)) {
//當前系統token有效,把token存入session
session.setAttribute(SystemConfig.SESSION_TOKEN_KEY, effectiveToken);
} else {
//當前系統token失效,更新token
setCookie(effectiveToken, request, response);
}
}
//使用者中心token失效
else {
removeCookie(request, response);
}
//重定向去掉url地址引數
if(ACTION_NAME.equals(action) && StringUtils.isNotBlank(effectiveToken)) {
response.sendRedirect(currentUrl);
} else {
filterChain.doFilter(request, response);
}
}
}
}
過濾器配置web.xml
<!-- 統一登入過濾 -->
<filter>
<filter-name>unifiedLoginFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>unifiedLoginFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
使用DelegatingFilterProxy代理過濾器,可以讓Spring管理被代理的過濾器,這樣就可以在過濾器中訪問Spring容器中的bean,屬性等。
使用者中心:
token校驗 UserInfo.java
@ApiOperation(value = "重定向獲取使用者中心Token")
@ApiImplicitParam(name = "redirectUrl", value = "回撥地址", required = true, dataType = "String", paramType = "query")
@RequestMapping(value = "/userCenterToken", method = RequestMethod.GET)
public void userCenterToken(HttpServletRequest request, HttpServletResponse response) {
String redirectUrl = ServletRequestUtils.getStringParameter(request, "redirectUrl", null);
String schoolId = this.getSchoolId(request);
String token = null;
Cookie[] cookies = request.getCookies();
if(null != cookies) {
for (int i = 0; i < cookies.length; i++) {
Cookie cookie = cookies[i];
if (cookie != null && config.getCookieTokenName().equals(cookie.getName())) {
token = cookie.getValue();
break;
}
}
}
try {
String effectiveToken = getEffectiveToken(token,schoolId);
response.sendRedirect(redirectUrl+"?token="+effectiveToken+"&action=callback");
} catch (Exception e) {
TRACER.error("", e);
e.printStackTrace();
}
}
@ApiOperation(value = "校驗Token是否失效")
@ApiImplicitParam(name = "token", value = "使用者 Token", required = true, dataType = "String", paramType = "query")
@RequestMapping(value = "/tokenExpired", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<WrappedResponse<String>> tokenExpired(HttpServletRequest request, HttpServletResponse response) {
try {
String token = ServletRequestUtils.getStringParameter(request, "token", null);
String schoolId = this.getSchoolId(request);
String effectiveToken = getEffectiveToken(token,schoolId);
return this.success(effectiveToken);
} catch (Throwable t) {
TRACER.error("", t);
return this.fail(TransactionStatus.INTERNAL_SERVER_ERROR);
}
}
private String getEffectiveToken(String token, String schoolId) throws Exception {
String effectiveToken = "";
HashMap<String, Object> map = new HashMap<>(1);
map.put("token", token);
HttpPlainResult result = httpConnManager.invoke(HttpMethod.GET, config.getSsoHost()+"/inner/tokenExpired", map,schoolId);
TRACER.info(result.getResult());
HttpResultDetail<TokenStatus> entry = HttpResultHandler.handle(result, TokenStatus.class);
if (entry.isOK()) {
TokenStatus tokenStatus = entry.getResult();
if (!tokenStatus.getExpired()) {
effectiveToken = token;
}
}
return effectiveToken;
}
統一登入 Login.java
HttpPlainResult result = httpConnManager.invoke(HttpMethod.POST, config.getSsoHost() + "/inner/login", map, schoolId);
TRACER.info(result.getResult());
HttpResultDetail<Token> entry = HttpResultHandler.handle(result,Token.class);
if(entry.isOK()){
ClientTypeEnum clientTypeEnum = ClientTypeEnum.convertString2ClientType(clientType);
if(clientTypeEnum == ClientTypeEnum.WEB){
String token = entry.getResult().getToken();
Cookie token_cookie = new Cookie(config.getCookieTokenName(),token);
token_cookie.setMaxAge(config.getCookieTokenTimeout());
token_cookie.setDomain(request.getHeader("Host").split(":")[0]);
response.addCookie(token_cookie);
return this.success(redirectUrl,token_cookie);
}else{
return this.success(entry.getResult(), entry.getResponseStatus());
}
}else if(entry.isClientError()){
return this.error(entry.getResponseMessage(), entry.getResponseStatus());
}else if(entry.isServerError()){
return this.fail(entry.getResponseMessage(), entry.getResponseStatus());
}
統一登出 Logout.java
HttpPlainResult result = httpConnManager.invoke(HttpMethod.POST, config.getSsoHost()+"/inner/logout", map,schoolId);
TRACER.info(result.getResult());
HttpResultDetail<String> entry = HttpResultHandler.handle(result,String.class);
if(entry.isOK()){
Cookie token_cookie = new Cookie(config.getCookieTokenName(),null);
token_cookie.setMaxAge(0);
token_cookie.setDomain(request.getHeader("Host").split(":")[0]);
response.addCookie(token_cookie);
return this.success(entry.getResult(), entry.getResponseStatus());
}else if(entry.isClientError()){
return this.error(entry.getResponseMessage(), entry.getResponseStatus());
}else if(entry.isServerError()){
return this.fail(entry.getResponseMessage(), entry.getResponseStatus());
}
關注公眾號,分享乾貨,討論技術
相關文章
- 跨域分散式系統單點登入的實現(CAS單點登入)跨域分散式
- 完全跨域的單點登入(SSO)解決方案原始碼解析跨域原始碼
- 最強SSO單點登入教程(三)單點登出流程分析
- 利用Redis單點登入跨伺服器的方法Redis伺服器
- jQuery Ajax 跨域前端實現登入jQuery跨域前端
- 跨域不完全探究跨域
- AJAX跨域完全講解跨域
- 簡單對比一下CORS跨域與Nginx反向代理跨域優劣CORS跨域Nginx
- 單點登入原理
- CAS單點登入(SSO)實戰(一)
- win10怎麼登出賬戶登入_怎麼登出windows10賬戶登入Win10Windows
- Vue+Express實現登入,登出VueExpress
- 自定義登入和登出頁面
- (十二) 整合spring cloud雲架構 - SSO單點登入之OAuth2.0 登出流程(3)SpringCloud架構OAuth
- 初探單點登入 SSO
- 解決單點登入的一個方法!
- Cookie Session跨站無法共享問題(單點登入解決方案)CookieSession
- windows10系統登入後自動登出如何解決Windows
- 一圖搞懂Web應用的單點登入Web
- 記一次 SSO 單點登入實現
- CAS單點登入-簡介
- SSO單點登入邏輯
- 如何自己實現一個健壯的 SSO 單點登入系統
- Ubuntu部署Maxkey單點登入認證系統Ubuntu
- java B2B2C Springboot電子商城系統- SSO單點登入之OAuth2.0 登出流程(3)JavaSpring BootOAuth
- 關於容器安全機制的登入/登出
- PbootCMS登入後立即被登出怎麼辦boot
- Linux學習(CentOS-7)---登入和登出Linux系統LinuxCentOS
- 單點登入原理與簡單實現
- java實現簡單的單點登入Java
- CAS單點登入-基礎搭建
- CAS單點登入-https配置HTTP
- 使用CAS實現單點登入
- MVC - 單點登入中介軟體MVC
- 【大前端】認識單點登入前端
- win10系統提示即將登出你的登入如何解決Win10
- sql中使用cmd命令登出登入使用者SQL
- 跨域的那點事跨域