精华内容
下载资源
问答
  • java实现完全跨域SSO单点登录

    万次阅读 多人点赞 2018-08-16 16:33:01
    SSO(Single Sign On)单点登录是实现多个系统之间统一登录的验证系统,简单来说就是:有A,B,C三个系统,在A处登录过后,再访问B系统,B系统就已经处于了登录状态,C系统也是一样。举个生活中栗子:你同时打开天猫...

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhangjingao/article/details/81735041

    java实现SSO

    什么是SSO

    SSO(Single Sign On)单点登录是实现多个系统之间统一登录的验证系统,简单来说就是:有A,B,C三个系统,在A处登录过后,再访问B系统,B系统就已经处于了登录状态,C系统也是一样。举个生活中栗子:你同时打开天猫和淘宝,都进入login界面,都要求你登录的,现在你在淘宝处登录后,直接在天猫处刷新,你会发现,你已经登录了,而且就是你在淘宝上登录的用户。说明他们实现了SSO,并且持有相同的信息。

    当然这个特性意味着它的使用场景是:同一公司下的不同子系统,因为对于SSO来说,每一个子系统拥有的信息都一样,是用户的全部信息,如果是不同公司,那这肯定不合适。现在的天猫和淘宝就是这样的一套SSO。

    实现思想

    SSO简单来说就是一句话:一处登录,全部访问。
    现在有两个系统分别是:a.comb.com,我们要实现他们的SSO,那么我们就需要一个统一验证中心sso.com,我们所有的登录和身份验证都在sso.com中操作。看图看传统登录方式和SSO方式的差别如下:
    传统与SSO方式对比

    我们需要将统一信息存在cookie中。

    登录部分:

    在用户第一次访问a.com时,到达a.com的服务器,服务器请求sso.com/ssocheck验证,验证失败,a.com的服务器到达login界面,用户在login界面输入用户名和密码,到达a.com的服务器,请求sso.com/login验证,验证通过生成token(包括用户登录信息),然后携带token和所有子系统路径返回a.com的服务器,a.com的服务器到达首页,同时请求自己和所有子系统的addcookie方法,将token添加到自己的cookie中。
    在用户访问b.com时,同样向sso.com/check发出验证cookie请求,sso验证token,验证成功返回到b.com的服务器,然后到达b的首页显示登录成功。

    退出部分:

    用户点击a.com的退出按钮,访问sso.com/loginout,然后获得所有子系统信息,请求所有子系统clearcookie方法,并重定向到login界面。

    跨域部分:我使用的是ajax的jsonp方式。

    看下登录(退出就不看了,登录写出来后退出就很简单了)的流程图:
    登录流程图

    代码实现

    按流程展示代码:
    用户访问a.com,用户先验证cookie到达a.com/ssocheck

    	
    	/**
         * 
         * @return 响应界面:login/index
         */
        @GetMapping("/ssocheck")
        public ModelAndView checkCookies (HttpServletRequest request) {
            Cookie[] cookies = request.getCookies();
            if (cookies != null && cookies.length > 0) {
                for (Cookie cookie : cookies) {
                    if ("jian".equals(cookie.getName())) { //统一登录cookie为jian,如果存在就认证
                        log.info("cookie 存在,开始验证");
                        HttpUtil httpUtil = new HttpUtil("http://sso.com/sso/authcookies", Method.GET);
                        String result = httpUtil.send(cookie.getName(), cookie.getValue());
                        boolean authBoo  = Boolean.valueOf(result);
                        if (authBoo) {
                            log.info("验证通过");
                            return new ModelAndView("public/index");
                        }
                        break;
                    }
                }
            }
            return new ModelAndView("index");
        }
        
    
    

    在判断中,如果a.com中有名为jian的cookie,那么就去sso.com/sso/authcookies去认证cookievalue,那么在sso.com中方法是这样的:

    	
    	/**
         * 验证cookie是否通过
         * @param cookieName cookie名称
         * @param cookieValue cookie内容
         * @return 是否认证成功
         */
        @GetMapping("/authcookies")
        public boolean checkAuthCookies (String cookieName, String cookieValue) {
            boolean isUpdate = new JwtUtil(null,cookieValue).freeJwt();
            if ("jian".equals(cookieName) && "ok".equals(cookieValue)) {
                log.info("cookie验证通过");
                return true;
            }
            return false;
    	}
    
    
    
    

    这里用到了HttpUtil类,这是我自己封装的,先说正事,这个工具类下面再放它,不要打扰了主线。如果认证cookie通过,那么说明已经在别的系统处登录过了,然后a.com就返回到首页,如果认证失败,a.com就到达登录页面,在我例子这是分别是public/index和index界面。
    登录界面就不亮了,很简单,就两个输入框,输入用户名和密码,然后提交到a.com/login,然后看下这个方法

    	
    	/**
         * 登录
         * @param username 用户名
         * @param password 密码
         * @return index/login
         */
        @PostMapping("/login")
        public ModelAndView doLogin (String username, String password) {
            if (username != null && !"".equals(username) &&
                        password != null && !"".equals(password) ) {
                HttpUtil httpUtil = new HttpUtil("http://sso.com/sso/", "POST");
                Result result = httpUtil.sendLogin(username,password);
                //如果验证通过,就携带所有子系统域名返回首页
                int isLogin = result.getResultCode().getCode();
                if (isLogin == 1) {
                    @SuppressWarnings("all")
                    Map<String,String> param = (Map<String, String>) result.getData();
                    return new ModelAndView("public/index","sendparam",param);
                }
            }
            return new ModelAndView("index");
        }
        
    
    

    a.com处请求sso.com/sso验证

    
        /**
         * 统一处理login请求
         * @param username 用户名
         * @param password 密码
         */
        @PostMapping
        public Result<Map<String,Object>> checkLogin (String username, String password) {
            log.info("统一登录校验");
            TbUser user = userService.login(username, password);
            if (user != null) {
                //封装参数
                Map<String, Object> param = new HashMap<>();
                //获得所有子系统域名信息
                List<TbDomain> domains = domainService.selectAll();
                List<String> domainUrl = new ArrayList<>(domains.size());
                domains.forEach(domain->{
                    domainUrl.add(domain.getDomain()+"/addcookie");
                });
                //生成jwt,加密用户信息
                String cookieName = "jian";
                String cookieValue = new JwtUtil(user.toString(),null).creatJwt();
                param.put("cookieurl",domainUrl);
                param.put("cookieName", cookieName);
                param.put("cookieValue",cookieValue);
                Result<Map<String, Object>> result = new Result<>(ResultCodeEnum.AUTHSUCCESS);
                result.setData(param);
                return result;
            }
            return new Result<>(ResultCodeEnum.UNAUTHORIZEd,"账号或密码错误");
        }
    
    
    

    在这里如果验证失败就返回账号或密码错误,如果验证通过,就得到所有域名,然后加密当前用户信息,用到了jwt(json web token,不做过多讲解),然后返回a.com。在a.com发现验证通过就到达首页,验证失败继续到达登录,在首页要使用ajax循环访问所有子系统,将cookie信息添加到所有子系统下。
    我的模板引擎使用的是thymeleaf,首页js如下:

    
    <script th:inline="javascript">
        /*<![CDATA[*/
    
        $(function () {
    	    //后台的所有域名
            var params = [[${sendparam}]];
            if (params == null) {
                return;//中断执行
            }
            var arrDomain = params.cookieurl;
            jQuery.each(arrDomain, function () {  // this 指定值
                //循环访问
                $.ajax({
                    url: this + "?cookieName=" + params.cookieName + "&cookieValue=" + params.cookieValue,
                    type: "get",
                    dataType: "jsonp" //指定服务器返回的数据类型
                });
            });
        })
        /*]]>*/
    </script>
    
    
    

    然后在此访问所有子系统的/addcookie方法,这里涉及到跨域,可以看到,我跨域使用的是ajax的json方式,中间还遇到了一些异常,总之解决掉了。

    看下a.com/addcookie方法

    	/**
         *
         * @param cookieName cookie名称
         * @param cookieValue cookie值
         * @param response 响应
         */
        @GetMapping("/addcookie")
        public void addCookies (String cookieName, String cookieValue, HttpServletResponse response) {
            log.info("添加cookie");
            Cookie cookie = new Cookie(cookieName,cookieValue);
            cookie.setPath("/");
            cookie.setMaxAge(3600);
            cookie.setHttpOnly(true);
            response.addCookie(cookie);
        }
    
        
    

    然后这时候查看下浏览器的cookie就会发现已经为它写上了,访问b.com/ssocheck就会直接通过,b.com下也被写了cookie。

    看下退出,退出就简便多了。
    首先在a.com首页点击退出按钮,然后触发js,访问sso.com/logout方法。

    
    //退出登录,清空所有子系统的cookie
        $("#loginout").click (function (event) {
            $.ajax({
                url: "http://sso.com/sso/loginout",
                type: "get",
                jsonp: "callback",//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(默认为:callback)
                jsonpCallback:"success_jsonpCallback",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名
                dataType: "jsonp", //指定服务器返回的数据类型
                success: function (data) {
                    window.location.href="/loginout";
                    eachUrl(data);//循环清理掉所有子系统cookie
                },error:function (data) {
                    console.log(data.jqXHR+" "+data.status+" "+data.error);
                }
            });
        });
    
        function eachUrl(arrDomain) {
            jQuery.each(arrDomain, function () {  // this 指定值
                //循环访问
                $.ajax({
                    url: this,
                    type: "get",
                    dataType: "jsonp" //指定服务器返回的数据类型
                });
            });
        }
    
    
    

    会去访问sso.com/loginout方法,拿到所有域名清除cookie方法。

    
        /**
         * 添加需要清除的cookie
         */
        @GetMapping("/loginout")
        public String loginOut (HttpServletRequest request) {
            String callbackFuncation = request.getParameter("callback");
            log.info("start clear");
            List<TbDomain> domains = domainService.selectAll();
            List<String> domainUrl = new ArrayList<>(domains.size());
            domains.forEach(domain->{
                domainUrl.add(domain.getDomain()+"/clear");
            });
            String resultMsg = JSON.toJSONString(domainUrl);
            return callbackFuncation+"("+resultMsg+")";
        }
    
    
    

    然后拿到后,首先会自己跳回到登录界面,然后再去请求其他子系统的清除cookie方法,防止请求时间过长无法给用户响应。看下清除cookie方法。

    
        /**
         * 清除掉cookie
         * @param request 请求
         * @param response 响应
         */
        @GetMapping("/clear")
        public void clear (HttpServletRequest request,HttpServletResponse response) {
            //获得域名
            log.info("clear掉ip为:"+request.getRemoteHost()+"的cookie");
            Cookie [] cookies = request.getCookies();
            for (Cookie cookie: cookies) {
                if ("wlgzs".equals(cookie.getName())) {
                    cookie.setValue(null);
                    cookie.setMaxAge(0);
                    response.addCookie(cookie);
                }
            }
        }
    
    
    

    然后再去访问刚才还能访问的b.com就会发现验证失败返回到登录界面,它的cookie也被清除了。

    至此:SSO的流程就分享完了,至于其中的http,jwt工具类都是小东西,相比本文不是重点,就不贴出代码了。
    本文所有代码在github上已经发布。github地址:https://github.com/zhangjingao/sso

    我更新了另一篇关于SSO的文章,想做单点登录的我建议看看,另一个版本是我目前在使用的版本。
    链接
    java实现 SSO 单点登录(最终版)

    展开全文
  • 基于Java+EE的跨域单点登录系统的研究与实现 基于Java+EE的跨域单点登录系统的研究与实现
  • java跨域单点登录实现

    2021-01-28 10:05:08
    跨域单点登录实现项目代码
  • 完全跨域单点登录, 适用于java语言开发, demo中展示了单点登陆的原理, 可用于理论学习
  • java基于cookie实现跨域单点登录

    1、SSO简介

    单点登录(SingleSignOn,SSO),就是通过用户的一次性鉴别登录。当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。

    2、JSON Web Token

    传统的认证是采用session会话机制,存在以下的缺点:
    1)用户信息存储在内存中,用户规模大之后增加服务器开销;
    2)由于登录信息存储在内存中,限制了登录机器,不利于分布式站点。
    3)多个服务需要采用全局缓存或数据库做存储。

    所以在我们的SSO系统中采用了JSON Web Token(JWT)令牌的方式来进行用户认证。
    它的优点是任一子系统用户登录成功时创建token令牌,各子系统共用token令牌,只要携带的token令牌在SSO系统认证通过,表示该用户访问子系统合法。
    详解请访问:JSON Web Token(JWT)

    3、模拟域名

    编辑C:\Windows\System32\drivers\etc\hosts文件,添加以下内容

    127.0.0.1 www.sso.com
    127.0.0.1 www.web.com
    

    配置sso系统端口号为8080,使用 http://www.sso.com:8080 域名对sso系统进行访问;
    配置web系统端口号为8081,使用 http://www.web.com:8081 域名对web系统进行访问。

    4、项目结构

    (1)sso工程,单点登录系统
    主要实现了用户登录、创建token、校验token、把token发送到web系统。
    (2)sso-client工程,sso客户端jar包
    业务系统访问单点登录系统的公共代码封装,业务系统需要引用该jar包。
    (3)web工程,业务系统
    业务系统负责拦截需要登录权限url,并向sso系统认证token合法性。

    5、主要流程

    (1)登录流程
    1.web系统拦截器,先从request请求中获取token进行认证,认证成功进入步骤2,若认证失败进入步骤3
    2.将token保存到局部cookie中,拦截器返回true,进入业务代码,结束。
    3.从局部cookie中获取token进行认证,若成功进入步骤4,若失败进入步骤5
    4、拦截器返回true,进入业务代码,结束。
    5.携带returnUrl重定向到获取sso登录页面,sso从全局cookie获取token,认证成功进入步骤6,认证失败进入步骤7
    6.携带token重定向到returnUrl,进入步骤1
    7.跳转sso登录页面,提交用户密码,验证用户登录信息,若成功进入步骤8,若失败重新进入步骤7
    8.创建token,并将token保存到全局cookie和redis,携带token重定向到returnUrl,进入步骤1
    名词解释:
    局部cookie:业务系统对应的cookie。
    全局cookie:sso系统对应的cookie。
    流程图
    在这里插入图片描述

    (2)注销流程
    用户向web系统发出注销请求,web系统删除局部cookie中的token后,重定向到sso系统注销地址,sso系统删除全局cookie和redis中的token。
    流程图
    在这里插入图片描述

    6、主要代码

    1.SSO系统的主要实现类 SsoController

    /**
     * @author : alex
     * @date : 2020/1/28
     */
    @Controller
    public class SsoController {
    
        @Autowired
        RedisUtil redisUtil;
        
        /**
         * 验证token是否合法
         *
         * @param request
         * @param response
         * @return
         */
        @RequestMapping(value = "/verifyToken", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
        @ResponseBody
        public Result verifyToken(HttpServletRequest request, HttpServletResponse response) {
            String tokenParam = request.getParameter("token");
            if (verifyToken(tokenParam)) {
                delayToken(tokenParam);
                return Result.buildIsOk("success");
            }
            return Result.buildIsFail("fail");
        }
    
        /**
         * 跳转登录页
         *
         * @param request
         * @return
         * @throws Exception
         */
        @RequestMapping(value = "/login")
        public ModelAndView login(HttpServletRequest request, ModelAndView modelAndView) {
            String token = getCookies(Constants.TOKEN_COOKIE_KEY, request);
            String returnUrl = request.getParameter("returnUrl");
            if (verifyToken(token) && !StringUtils.isEmpty(returnUrl)) {
                returnUrl = returnUrl + "?token=" + token;
                modelAndView.setViewName("redirect:" + returnUrl);
            } else {
                //TODO 删除错误的token
                modelAndView.addObject("returnUrl", returnUrl);
                modelAndView.setViewName("login");
            }
            return modelAndView;
        }
    
        /**
         * 用户登录
         *
         * @param request
         * @param response
         * @param modelAndView
         * @return
         */
        @RequestMapping(value = "/signIn")
        public ModelAndView signIn(HttpServletRequest request, HttpServletResponse response, ModelAndView modelAndView) {
            String returnUrl = request.getParameter("returnUrl");
            String userName = request.getParameter("userName");
            String password = request.getParameter("password");
            //1.1、验证登录信息合法
            if (verifyUser(userName, password)) {
                //2、创建token
                String token = TokenUtil.createToken(userName);
                //3、将token保存到cookie和redis中
                Cookie clientCookie = new Cookie(Constants.TOKEN_COOKIE_KEY, token);
                clientCookie.setMaxAge(Constants.COOKIE_AGE_NEGATIVE);
                clientCookie.setPath(Constants.COOKIE_PATH);
                response.addCookie(clientCookie);
                this.delayToken(token);
                if (StringUtils.isEmpty(returnUrl)) {
                    //4.1、无returnUrl,跳转默认页面
                    modelAndView.setViewName("index");
                } else {
                    //4.2、转发到业务系统
                    returnUrl = returnUrl + "?token=" + token;
                    modelAndView.setViewName("redirect:" + returnUrl);
                }
            } else {
                //1.2、验证失败,跳转登录页面
                modelAndView.setViewName("login");
            }
            return modelAndView;
        }
    
        @RequestMapping(value = "/logout")
        public ModelAndView logout(HttpServletRequest request, HttpServletResponse response, ModelAndView modelAndView) {
            //1、验证cookie中token
            String returnUrl = request.getParameter("returnUrl");
            String token = getCookies(Constants.TOKEN_COOKIE_KEY, request);
            if (verifyToken(token)) {
                //2、删除cookie和redis中token
                deleteToken(token, response);
            }
            //3、跳转登录页面
            modelAndView.addObject("returnUrl", returnUrl);
            modelAndView.setViewName("login");
            return modelAndView;
        }
    
        /**
         * 从redis获取token
         *
         * @param key
         * @return
         */
        public String getToken(String key) {
            Object obj = redisUtil.get(key);
            if (Objects.nonNull(obj)) {
                return obj.toString();
            }
            return "";
        }
    
        /**
         * 用户通过验证后,延长redis中token过期时间
         *
         * @param token
         */
        private void delayToken(String token) {
            Map<String, Claim> claims = TokenUtil.getClaims(token);
            String tokenKey = claims.get("userName").asString();
            redisUtil.set(tokenKey, token, Constants.TOKEN_TTL);
        }
    
        private void deleteToken(String token, HttpServletResponse response) {
            Map<String, Claim> claims = TokenUtil.getClaims(token);
            String tokenKey = claims.get("userName").asString();
            //1、删除redis中token
            redisUtil.del(tokenKey);
            //2、删除cookie中token
            Cookie cookie = new Cookie(Constants.TOKEN_COOKIE_KEY, null);
            cookie.setMaxAge(Constants.COOKIE_AGE_ZERO);
            cookie.setPath(Constants.COOKIE_PATH);
            response.addCookie(cookie);
        }
    
        private String getCookies(String key, HttpServletRequest request) {
            Cookie[] cookies = request.getCookies();
            if (null == cookies || cookies.length < 0) {
                return null;
            }
            String tokenVal = "";
            for (Cookie cookie : cookies) {
                String name = cookie.getName();
                if (!StringUtils.isEmpty(name) && key.equals(name)) {
                    tokenVal = cookie.getValue();
                }
            }
    
            return tokenVal;
        }
    
        /**
         * 校验token是否合法,并判断redis中token是否过期
         *
         * @param token token
         * @return 验证结果
         */
        private boolean verifyToken(String token) {
            if (TokenUtil.verify(token)) {
                Map<String, Claim> claims = TokenUtil.getClaims(token);
                String tokenKey = claims.get("userName").asString();
                String tokenCache = getToken(tokenKey);
                if (tokenCache.equals(token)) {
                    return true;
                }
            }
            return false;
        }
    
        /**
         * 验证用户登录信息
         *
         * @param userName
         * @param password
         * @return
         */
        private boolean verifyUser(String userName, String password) {
            if (userName.equals(password)) {
                return true;
            } else {
                return false;
            }
        }
    }
    

    2.sso-client的主要封装

    
    /**
     * @author : alex
     * @date : 2020/1/30
     */
    @Service
    public class SsoClient {
        private static final String TOKEN_KEY = "token";
    
        private static final String CODE_KEY = "code";
    
        private static final String COOKIE_PATH = "/";
    
        public static final Integer COOKIE_AGE_NEGATIVE = -1;
    
        private String verifyUrl;
    
        public String getVerifyUrl() {
            return verifyUrl;
        }
    
        public void setVerifyUrl(String verifyUrl) {
            this.verifyUrl = verifyUrl;
        }
    
        /**
         * 重定向到sso登录页面
         *
         * @param request
         * @param response
         */
        public void redirectLogin(HttpServletRequest request, HttpServletResponse response, String redirectUrl, String returnUrl) {
            String urlString = request.getRequestURI();
            StringBuilder url = new StringBuilder(redirectUrl);
            url.append("?returnUrl=").append(returnUrl).append(urlString);
            try {
                response.sendRedirect(url.toString());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        private boolean verifyToken(String token) {
            Map<String, String> params = new HashMap<>();
            params.put(TOKEN_KEY, token);
            try {
                String result = HttpUtil.send(this.getVerifyUrl(), params, "UTF-8");
                JSONObject obj = JSON.parseObject(result);
                if (obj.getInteger(CODE_KEY).equals(0)) {
                    return true;
                } else {
                    return false;
                }
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
    
        /**
         * 验证request参数或cookie中token的合法性
         *
         * @param request
         * @param response
         * @param verifyUrl
         * @return
         */
        public boolean checkToken(HttpServletRequest request, HttpServletResponse response, String verifyUrl) {
            this.setVerifyUrl(verifyUrl);
            //1.1 验证参数中token是否合法
            if (checkParamToken(request, response)) {
                return true;
            } else {
                //1.2 验证cookie中token是否合法
                return checkCookieToken(request, response);
            }
        }
    
        /**
         * 校验cookie中token
         *
         * @param request
         * @param response
         * @return
         */
        private boolean checkCookieToken(HttpServletRequest request, HttpServletResponse response) {
            String token = CookiesGetUtil.getCookies(TOKEN_KEY, request);
            if (StringUtils.isEmpty(token)) {
                return false;
            } else {
                if (verifyToken(token)) {
                    return true;
                } else {
                    //cookie中token不能通过验证,删除其token
                    Cookie cookie = new Cookie(TOKEN_KEY, null);
                    cookie.setMaxAge(0);
                    cookie.setPath(COOKIE_PATH);
                    response.addCookie(cookie);
                    return false;
                }
            }
        }
    
        /**
         * 校验request参数中token
         *
         * @param request
         * @param response
         * @return
         */
        private boolean checkParamToken(HttpServletRequest request, HttpServletResponse response) {
            String token = request.getParameter(TOKEN_KEY);
            if (!StringUtils.isEmpty(token)) {
                //验证request参数中token合法,将token保存到cookie中
                if (verifyToken(token)) {
                    Cookie cookie = new Cookie(TOKEN_KEY, token);
                    cookie.setMaxAge(COOKIE_AGE_NEGATIVE);
                    cookie.setPath(COOKIE_PATH);
                    response.addCookie(cookie);
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        }
    }
    

    3.web系统拦截器的主要实现

    /**
     * @author : alex
     * @date : 2020/1/22
     */
    @Service
    public class LoginInterceptor implements HandlerInterceptor {
        @Autowired
        private SsoClient ssoClient;
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            //1、token验证通过
            if (ssoClient.checkToken(request, response, ConfigUtils.getProperty("sso.url.verifyToken"))) {
                return true;
            } else {
                //2、token验证失败,跳转sso登录页面
                ssoClient.redirectLogin(request, response, ConfigUtils.getProperty("sso.url.login"), ConfigUtils.getProperty("web.host"));
                return false;
            }
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
        }
    }
    
    

    以上的代码都有详细的注释,不再赘述其实现,若文章不够清晰,辅以代码阅读更好,更建议自己动手把项目运行起来。项目会有详细的说明以保障项目正常运行。

    7、思路发散

    (1)我在其他博文里看到,将token保存到session中,通过jsessionid获取对应的session,其实不失为一个解决问题方案。
    优点:客户端不用获取token,token全部保存在服务端了。
    缺点:session只保存在一个tomcat容器上,若业务系统当前容器宕机了,jsessionid对应的session就失效了,必须要重新从sso获取token,保存到新的session上去。

    (2)上文的sso是基于web版了,所有业务系统都是web系统,可以将token保存到cookie中。但是要是支持客户端(手机app或H5),就需要以API接口方式来支持,并且token由客户端保存(因为token无状态),每次客户端请求资源时认证token。
    可以定义出以下接口:
    1.用户名密码登录sso系统,登录成功后返回token给客户端。
    2.客户端携带token访问业务系统,业务系统访问sso接口认证token。
    3.用户注销,注销sso系统中token。

    (3)CAS框架:CAS(Central Authentication Service)是基于Kerberos票据方式实现SSO单点登录的框架。
    这是当前较为热门开源的SSO单点登录框架,单独部署CAS server,业务系统引用CAS Client就可以让我们的业务系统实现单点登录。

    8、附代码

    实现源码:源代码下载
    若文章对你有帮助,请点赞、收藏、分享

    展开全文
  • 第 PAGE 页码 页码 页 / 总页数 NUMPAGES 总页数 总页数 页 java实现跨域单点登录 [基于.NET,Web服务的跨域单点登录系统的实现] 摘要没有跨域单点登录的实现机制分析了跨域单点登录的原理提出了一种在SSO网站存储...
  • SSO(Single Sign On)单点登录是实现多个系统之间统一登录的验证系统,简单来说就是:有A,B,C三个系统,在A处登录过后,再访问B系统,B系统就已经处于了登录状态,C系统也是一样。举个生活中栗子:你同时打开天猫...

    SSO(Single Sign On)单点登录是实现多个系统之间统一登录的验证系统,简单来说就是:有A,B,C三个系统,在A处登录过后,再访问B系统,B系统就已经处于了登录状态,C系统也是一样。举个生活中栗子:你同时打开天猫和淘宝,都进入login界面,都要求你登录的,现在你在淘宝处登录后,直接在天猫处刷新,你会发现,你已经登录了,而且就是你在淘宝上登录的用户。说明他们实现了SSO,并且持有相同的信息。

    当然这个特性意味着它的使用场景是:同一公司下的不同子系统,因为对于SSO来说,每一个子系统拥有的信息都一样,是用户的全部信息,如果是不同公司,那这肯定不合适。现在的天猫和淘宝就是这样的一套SSO。

     

    java实现完全跨域SSO单点登录:https://blog.csdn.net/zhangjingao/article/details/81735041

    java实现 SSO 单点登录(最终版)--补充完全跨域SSO:https://blog.csdn.net/zhangjingao/article/details/89052764 

    展开全文
  • 单点登录系统总结 关于登录 一、登录 1、当用户点击登录的时候,把当前页面的url用参数传递到登录页面 2、用户成功登录,生成token,保存到redis中(service层),key为token,value为用户对象信息 3、同时将用户token...

    单点登录系统总结

    关于登录
    一、登录
    1、当用户点击登录的时候,把当前页面的url用参数传递到登录页面
    2、用户成功登录,生成token,保存到redis中(service层),key为token,value为用户对象信息
    3、同时将用户token和用户对象信息存入到cookie中。注意将密码进行MD5加密处理
    4、登录成功后,通过判断是否有returnUrl来返回到用户登录前的页面
    5、这里是做了如下的处理:
    function login(query) {
    return location.href = “http://localhost:8084/login?ReturnUrl=http://localhost:8082/search.html?q=”+query;
    }

    即每个模块都会在点击登录的方法中写上当前的服务器域名和端口号,并将当前页面信息使用参数传递到后台controller
    具体参数是在页面使用参数传到js文件中。(否则取不到页面信息)
    当然首页登录就不需要做特殊处理了。
    

    6、在后台获取到returnUrl后,存入到model中。返回给前台。
    前台如果收到了returnUrl不为空,那么跳转到returnUrl页面

    二、跨域
    1、当用户成功登录后,在cookie和redis中都有对应的token,当用户每次访问其他模块,都会发送一个ajax的跨域请求来取出token
    2、取token的前提是cookie中有token(即用户登录过),如果cookie中有,redis没有,那么表示用户登录过期
    3、当cookie中的token和redis中的token匹配,那么可以认为用户正常SSO登录访问。
    2、原理:发送ajax跨域请求,需要使用jsonp,传递一个callback参数,用来表示是跨域请求。使用下面方法来返回跨域信息
    if(StringUtils.isNotBlank(callback)){
    //请求中包含了callback,表示是一个跨域请求
    MappingJacksonValue mappingJacksonValue=new MappingJacksonValue(result);
    mappingJacksonValue.setJsonpFunction(callback);
    return mappingJacksonValue;
    }

    *:cookie中设置token的时候进行了跨域处理。(老师写的,具体详情未知)
    

    跨域获取token:
    public JDResult getUserByToken(String token) {
    String json=jedisClient.get(USER_SESSION+token);
    if(StringUtils.isBlank(json)){
    //表示用户登录过期,redis中没有,token有
    return JDResult.bind(400, “用户登录过期”);
    }
    //重置session时间
    jedisClient.expire(USER_SESSION+token, SESSION_EXPIRE);
    //将json对象转换成user对象
    TbUser user=JsonUtils.jsonToPojo(json, TbUser.class);
    return JDResult.ok(user);
    }

    //对于spring4.1或以上版本才能使用
    @RequestMapping(value="/user/token/{token}",method=RequestMethod.GET)
    @ResponseBody
    public Object getUserByToken(@PathVariable("token") String token,String callback){
    	JDResult result = userService.getUserByToken(token);
    	if(StringUtils.isNotBlank(callback)){
    		//请求中包含了callback,表示是一个跨域请求
    		MappingJacksonValue mappingJacksonValue=new MappingJacksonValue(result);
    		mappingJacksonValue.setJsonpFunction(callback);
    		return mappingJacksonValue;
    	}
    	return result;
    } 
    
    //对于spring4.1以下版本这样获取token
    @RequestMapping(value="/user/token/{token}",method=RequestMethod.GET)
    @ResponseBody
    public Object getUserByToken(@PathVariable("token") String token,String callback){
    	JDResult result = userService.getUserByToken(token);
    	if(StringUtils.isNotBlank(callback)){
    		//请求中包含了callback,表示是一个跨域请求
    		return callback+"("+JsonUtils.objectToJson(result)+")";
    	}
    	return result;
    }
    

    三、注销
    1、注销的a链接是在用户登录后,每次访问模块的时候通过ajax动态生成的,那么需要给这个a链接绑定一个事件
    var username=result.data.username;
    var html=username+",欢迎来到京东![退出]";
    $("#loginbar").html(html);
    $("#sa").bind(‘click’,function(){

    *:注意,这里用到了bind,没有使用on,发现jquery版本低的时候不可以使用on来绑定事件。
    之前没有用bind,而是直接写的 …发现报错。考虑这个事件没有被注册和加载到内存中
    因此使用了bind来绑定事件

    2、当点击注销的时候,发现ajax跨域请求,用到jsonp,和callback参数,返回状态码
    KaTeX parse error: Expected '}', got 'EOF' at end of input: …0){ var que=("#que").val();
    var html=“您好,欢迎来到京东[登录] [免费注册]”;
    $("#loginbar").html(html);
    }
    }

    *:注意:这里用到了一个隐藏域,因为考虑到点击注销的时候,又可以再次点击登录,而点登录的时候是需要页面参数的,
    因为将页面关键参数信息通过隐藏域放入到了一个input里,然后通过jquery获取到隐藏域中的值
    注意:方法中login(""+que+""),需要用到转义字符",之前这样写: login(’"+que"’)方法报错,不被识别。
    具体原因是不是因为引号,未知。
    首页注销发送ajax请求,不做特殊处理。其他域注销的时候,需要获取到页面参数。(因为点登录的时候需要用)

    跨域注销:
    public JDResult logout(String token) {
    try {
    jedisClient.expire(USER_SESSION+token, 0);
    return JDResult.ok();
    } catch (Exception e) {
    e.printStackTrace();
    return JDResult.bind(400, “退出登录失败”);
    }
    }

    @RequestMapping(value="/user/logout/{token}",method=RequestMethod.GET)
    @ResponseBody
    public Object logout(@PathVariable("token") String token,String callback,
    		HttpServletRequest request,HttpServletResponse response){
    	JDResult jdResult = userService.logout(token);
    	CookieUtils.deleteCookie(request, response, SESSION_TOKEN);
    	if(StringUtils.isNotBlank(callback)){
    		//请求中包含了callback,表示是一个跨域请求
    		MappingJacksonValue mappingJacksonValue=new MappingJacksonValue(jdResult);
    		mappingJacksonValue.setJsonpFunction(callback);
    		return mappingJacksonValue;
    	}
    	return jdResult;
    } 
    
    package org.java.common.utils;
    import java.io.UnsupportedEncodingException;
    import java.net.URLDecoder;
    import java.net.URLEncoder;
    
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    
    /**
     * 
     * Cookie 工具类
     *
     */
    public final class CookieUtils {
    
        /**
         * 得到Cookie的值, 不编码
         * 
         * @param request
         * @param cookieName
         * @return
         */
        public static String getCookieValue(HttpServletRequest request, String cookieName) {
            return getCookieValue(request, cookieName, false);
        }
    
        /**
         * 得到Cookie的值,
         * 
         * @param request
         * @param cookieName
         * @return
         */
        public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {
            Cookie[] cookieList = request.getCookies();
            if (cookieList == null || cookieName == null) {
                return null;
            }
            String retValue = null;
            try {
                for (int i = 0; i < cookieList.length; i++) {
                    if (cookieList[i].getName().equals(cookieName)) {
                        if (isDecoder) {
                            retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");
                        } else {
                            retValue = cookieList[i].getValue();
                        }
                        break;
                    }
                }
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return retValue;
        }
    
        /**
         * 得到Cookie的值,
         * 
         * @param request
         * @param cookieName
         * @return
         */
        public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {
            Cookie[] cookieList = request.getCookies();
            if (cookieList == null || cookieName == null) {
                return null;
            }
            String retValue = null;
            try {
                for (int i = 0; i < cookieList.length; i++) {
                    if (cookieList[i].getName().equals(cookieName)) {
                        retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);
                        break;
                    }
                }
            } catch (UnsupportedEncodingException e) {
            	 e.printStackTrace();
            }
            return retValue;
        }
    
        /**
         * 设置Cookie的值 不设置生效时间默认浏览器关闭即失效,也不编码
         */
        public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                String cookieValue) {
            setCookie(request, response, cookieName, cookieValue, -1);
        }
    
        /**
         * 设置Cookie的值 在指定时间内生效,但不编码
         */
        public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                String cookieValue, int cookieMaxage) {
            setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);
        }
    
        /**
         * 设置Cookie的值 不设置生效时间,但编码
         */
        public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                String cookieValue, boolean isEncode) {
            setCookie(request, response, cookieName, cookieValue, -1, isEncode);
        }
    
        /**
         * 设置Cookie的值 在指定时间内生效, 编码参数
         */
        public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                String cookieValue, int cookieMaxage, boolean isEncode) {
            doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);
        }
    
        /**
         * 设置Cookie的值 在指定时间内生效, 编码参数(指定编码)
         */
        public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                String cookieValue, int cookieMaxage, String encodeString) {
            doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);
        }
    
        /**
         * 删除Cookie带cookie域名
         */
        public static void deleteCookie(HttpServletRequest request, HttpServletResponse response,
                String cookieName) {
            doSetCookie(request, response, cookieName, "", -1, false);
        }
    
        /**
         * 设置Cookie的值,并使其在指定时间内生效
         * 
         * @param cookieMaxage cookie生效的最大秒数
         */
        private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,
                String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) {
            try {
                if (cookieValue == null) {
                    cookieValue = "";
                } else if (isEncode) {
                    cookieValue = URLEncoder.encode(cookieValue, "utf-8");
                }
                Cookie cookie = new Cookie(cookieName, cookieValue);
                if (cookieMaxage > 0)
                    cookie.setMaxAge(cookieMaxage);
                if (null != request) {// 设置域名的cookie
                	String domainName = getDomainName(request);
                	//System.out.println(domainName);
                    if (!"localhost".equals(domainName)) {
                    	cookie.setDomain(domainName);
                    }
                }
                cookie.setPath("/");
                response.addCookie(cookie);
            } catch (Exception e) {
            	 e.printStackTrace();
            }
        }
    
        /**
         * 设置Cookie的值,并使其在指定时间内生效
         * 
         * @param cookieMaxage cookie生效的最大秒数
         */
        private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,
                String cookieName, String cookieValue, int cookieMaxage, String encodeString) {
            try {
                if (cookieValue == null) {
                    cookieValue = "";
                } else {
                    cookieValue = URLEncoder.encode(cookieValue, encodeString);
                }
                Cookie cookie = new Cookie(cookieName, cookieValue);
                if (cookieMaxage > 0)
                    cookie.setMaxAge(cookieMaxage);
                if (null != request) {// 设置域名的cookie
                	String domainName = getDomainName(request);
                	System.out.println(domainName);
                    if (!"localhost".equals(domainName)) {
                    	cookie.setDomain(domainName);
                    }
                }
                cookie.setPath("/");
                response.addCookie(cookie);
            } catch (Exception e) {
            	 e.printStackTrace();
            }
        }
    
        /**
         * 得到cookie的域名
         */
        private static final String getDomainName(HttpServletRequest request) {
            String domainName = null;
    
            String serverName = request.getRequestURL().toString();
            if (serverName == null || serverName.equals("")) {
                domainName = "";
            } else {
                serverName = serverName.toLowerCase();
                serverName = serverName.substring(7);
                final int end = serverName.indexOf("/");
                serverName = serverName.substring(0, end);
                final String[] domains = serverName.split("\\.");
                int len = domains.length;
                if (len > 3) {
                    // www.xxx.com.cn
                    domainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];
                } else if (len <= 3 && len > 1) {
                    // xxx.com or xxx.cn
                    domainName = "." + domains[len - 2] + "." + domains[len - 1];
                } else {
                    domainName = serverName;
                }
            }
    
            if (domainName != null && domainName.indexOf(":") > 0) {
                String[] ary = domainName.split("\\:");
                domainName = ary[0];
            }
            return domainName;
        }
    
    }
    
    
    展开全文
  • 单点登录跨域碰上SameSite问题

    千次阅读 2020-08-26 13:45:01
    单点登录跨域碰上SameSite问题项目场景:问题描述:原因分析:解决方案:这里还遇到一个问题:headers一直是空? 项目场景: 两个项目:auth_webapp、admin_webapp 单点登录流程: 点击登录请求:/auth/api/login?...
  • java实现完全跨域SSO单点登录-JWT

    千次阅读 2019-05-16 19:40:39
    SSO(Single Sign On)单点登录是实现多个系统之间统一登录的验证系统,简单来说就是:有A,B,C三个系统,在A处登录过后,再访问B系统,B系统就已经处于了登录状态,C系统也是一样。举个生活中栗子:你同时打开天猫...
  • java实现 SSO 单点登录(最终版)--补充完全跨域SSO

    万次阅读 多人点赞 2019-04-06 10:35:07
      前面我写了一篇文章,java实现完全跨域SSO单点登录,最后我会比较两种方案。   那篇文章主要说明完全跨域SSO单点登录的实现,但是我最终并没有使用那篇,当然,那篇完全可以实现SSO跨域,但是那篇有一些不太...
  • 慕课java实现sso单点登录自己实现的代码,基于Struts2,本课程首先以新浪为例介绍了单点登录SSO的实现场景,然后对实现SSO所用的核心技术和原理进行分析,最后通过案例演示了同域SSO和跨域SSO的实现。
  • 那就意味着做单点登录咯,至于不知道什么是单点登录的同学,建议去找一下万能的度娘。 刚接到这个需求的时候,老夫心里便不屑的认为:区区登录何足挂齿,但是,开发的过程狠狠的打了我一巴掌(火辣辣的一巴掌)。。...
  • JavaWeb跨域单点登录

    2018-08-06 14:44:30
    1、该代码,使用maven构建的模块化项目工程,ssm框架,直接import --> existing maven projects 即可 ...4、单点登录教程可以参考 https://blog.csdn.net/weixin_42686388/article/details/81299609
  • 使用JWT实现单点登录(完全跨域方案)

    万次阅读 多人点赞 2018-09-10 15:57:56
    授权:这是最常见的使用场景,解决单点登录问题。因为JWT使用起来轻便,开销小,服务端不用记录用户状态信息(无状态),所以使用比较广泛; 信息交换:JWT是在各个服务之间安全传输信息的好方法。因为JWT可以签名...
  • java实现跨域SSO单点登录 springboot + SSO + JWT 什么是SSO SSO(Single Sign On)单点登录是实现多个系统之间统一登录的验证系统,简单来说就是:有A,B,C三个系统,在A处登录过后,再访问B系统,B系统就已经处于...
  • 通过动态写入html,留的后门...script><style><img> ------------web1-----------------------index.jsp------------- ...%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> &...
  • 单点登录有两种模型,一种是共同父域下的单点登录(例如域名都是 xx.a.com),还有就是完全跨域下的单点登录(例如域名是xx.a.com,xx.b.com),本文我们讲一下完全跨域下的单点登录该怎么实现。基于安全考虑,想...
  • 最近在研究SSO单点登录技术,其中有一种就是通过js的跨域设置cookie来达到单点登录目的的,下面就已京东商城为例来解释下跨域设置cookie的过程 涉及的关键知识点: 1、jQueryajax跨域重定向,要理ajax解跨域重定向...
  • 跨域单点登录

    2017-02-11 10:03:56
    单点登录(SSO)的技术被越来越广泛地运用到各个领域的软件系统当中。本文从业务的角度分析了单点登录的需求和应用领域;从技术本身的角度分析了单点登录技术的内部机制和实现手段,并且给出Web-SSO和桌面SSO的实现...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 14,109
精华内容 5,643
关键字:

java单点登录跨域

java 订阅