zoukankan      html  css  js  c++  java
  • 绝对完全跨域统一单点登录登出

    应用场景:多个系统下同属于一个用户,当用户登录了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());
    }

    关注公众号,分享干货,讨论技术




  • 相关阅读:
    聚集索引
    第一天 尝试Thread
    sql 分区函数
    sql 查询表定义
    千万数量级分页存储过程
    成语解释
    sql 分组查询满足条件所以数据
    sql存储过程
    联表更新的反思
    从表保存了主表的id,以分号分隔,怎么样用一条sql搞定主表满足条件的查询? 不希望单独写存储过程,或者后台拆成int后传进来,就一条sql 搞定,一条
  • 原文地址:https://www.cnblogs.com/molashaonian/p/9097566.html
Copyright © 2011-2022 走看看