精华内容
下载资源
问答
  • 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 实现单点登录及后门登录

    千次阅读 2016-09-26 20:06:08
    单点登录: /** * 单点登录 * * @param loginId * @param session * @return */ @Outer @RequestMapping("/login") public ModelAndView login(String loginId, HttpSession se

    单点登录:实质就是传一个登录id过来,到数据库中查出此id对应的用户信息,并将用户信息放入session中。

    <span "><strong> </strong>/**
         * 单点登录
         * 
         * @param loginId
         * @param session
         * @return
         */
        @Outer
        @RequestMapping("/login")
        public ModelAndView login(String loginId, HttpSession session) {
            // 登录跳转页面
            String view = "common/login";
    
            // 单点登录信息
            if (StringUtils.isBlank(loginId)) {
                return new ModelAndView(view, "message", "用户信息丢失,请重新登录!");
            }
    
            // 获取当前登录用户信息
            UserInfo userInfo = userService.getUserInfo(loginId);
            if (userInfo == null) {
                return new ModelAndView(view, "message", "用户信息不存在,请重新登录!");
            }
    
            // 当前系统只支持个体户用户和个人用户登录
            // String userType = userInfo.getUserType();
            // if (!GlobalConstants.USER_TYPE_PE.equals(userType) && !GlobalConstants.USER_TYPE_PERSONAL.equals(userType)) {
            // return new ModelAndView(view, "message", "当前系统只支持个体户用户和个人用户登录!");
            // }
    
            // 将当前登录用户信息放入session
            session.setAttribute(GlobalConstants.SESSION_USER, userInfo);
    
            // 转至登录跳转页面
            return new ModelAndView(view);
        }
    
        /**
         * 注销登录
         * 
        
         */
        @RequestMapping("/logout")
        public String logout(HttpSession session) {
            // 获取当前登录用户信息
            UserInfo user = (UserInfo) session.getAttribute(GlobalConstants.SESSION_USER);
    
            // 清除单点登录信息
            try{
            	userService.deleteLoginInfo(user.getLoginId());
            }catch(Exception e){
            	//
            }
            // 清除session中的用户数据
            Enumeration<?> enu = session.getAttributeNames();
            while (enu.hasMoreElements()) {
                session.removeAttribute((String) enu.nextElement());
            }
    
            return "redirect:" + ConfigConstants.getInstance().get("logout.url");
        }</span>


    展开全文
  • SSO单点登录Java实现实例

    千次阅读 多人点赞 2021-02-05 14:43:44
    本文主要讲解,基于令牌token方式实现,SpringBoot工程下的SSO单点登录整合代码实例Demo,文末附源码地址。 1.环境准备 SSO认证中心服务( www.mysso.com) 客户端1(www.myclient1.com) 客户端2...
    随着公司系统的增加,每次新建一个项目是否还在做登录功能呢,还在做重复的工作?统一登录SSO你值得拥有。本文主要讲解,基于令牌token方式实现,SpringBoot工程下的SSO单点登录整合代码实例Demo,文末附源码地址。
    

    1.环境准备

    • SSO认证中心服务( www.mysso.com)
    • 客户端1(www.myclient1.com)
    • 客户端2(www.myclient2.com)

    由于是Demo实例,这里若有要访问-需要修改一下本机host,添加如下映射

     127.0.0.1 www.mysso.com
     127.0.0.1 www.myclient1.com
     127.0.0.1 www.myclient2.com
    

    2.搭建SSO认证中心服务

    问题来了,搭建一个SSO统一登录需要哪些功能?

    • 统一登录页+接口
    • 校验令牌有效性接口(调用来源:子系统)
    • 校验登录状态接口(调用来源:子系统)
    • 统一退出(调用来源:子系统或退出认证中心服务)

    这里就不贴Maven依赖了,主要讲解功能,详见文末附源码。

    2.1统一登录页

    创建一个统一登录页

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>程序员小强-SSODemo</title>
    </head>
    <style>body.center {
        text-align: center;
    }
    </style>
    <body class="center">
    <div>
        <h1>SSO用户统一登录</h1>
    </div>
    <div>
        <form name="loginForm" action="/sso/login" method="POST" accept-charset="UTF-8">
            <div><input placeholder="用户名" value="admin" name="username" type="text"/></div>
            <div><input placeholder="密码" value="123456" name="password" type="password"/></div>
            <div style="color: red"><span th:text="${msg}"></span></div>
            <div><input name="redirectUrl" type="hidden" th:value="${redirectUrl}"/></div>
            <input type="submit" value="登录"/>
            <input type="reset" value="重置"/>
        </form>
    </div>
    </body>
    </html>
    

    效果图
    在这里插入图片描述

    2.2统一登录接口

    这里为了简化示例,未引入redis等缓存数据库,使用的 session存储登录信息

      /**
         * 认证中心SSO统一登录方法
         */
        @RequestMapping("/login")
        public String login(LoginParam loginParam, RedirectAttributes redirectAttributes,
                            HttpSession session, Model model) {
    
            //Demo 项目此处模拟数据库账密校验
            if (!"admin".equals(loginParam.getUsername()) || !"123456".equals(loginParam.getPassword())) {
                model.addAttribute("msg", "账户或密码错误,请重新登录!");
                model.addAttribute("redirectUrl", loginParam.getRedirectUrl());
                return "login";
            }
    
            //登录成功
            //创建令牌
            String ssoToken = UUID.randomUUID().toString();
            //把令牌放到全局会话中
            session.setAttribute("ssoToken", ssoToken);
            //设置session失效时间-单位秒
            session.setMaxInactiveInterval(3600);
            //将有效的令牌-放到map容器中(存在该容器中的token都是合法的,正式环境建议redis或存库)
            SSOConstantPool.TOKEN_POOL.add(ssoToken);
    
            //未携带重定向跳转地址-默认跳转到认证中心首页
            if (StringUtils.isEmpty(loginParam.getRedirectUrl())) {
                return "index";
            }
    
            // 携带令牌到客户端
            redirectAttributes.addAttribute("ssoToken", ssoToken);
            log.info("[ SSO登录 ] login success ssoToken:{} , sessionId:{}", ssoToken, session.getId());
            // 跳转到客户端
            return "redirect:" + loginParam.getRedirectUrl();
        }
    

    2.3校验令牌接口

    在跳转到子系统后会携带令牌token,这时候需要调用以下接口来校验token的有效性
    以下接口一共做了2件事情

    • 校验token是否有效
    • 若有效-则记录了注册上来的子系统信息,用于统一注销时候使用
    /**
     * 校验令牌是否合法
     *
     * @param ssoToken    令牌
     * @param loginOutUrl 退出登录访问地址
     * @param jsessionid
     * @return 令牌是否有效
     */
    @ResponseBody
    @RequestMapping("/checkToken")
    public String verify(String ssoToken, String loginOutUrl, String jsessionid) {
        // 判断token是否存在map容器中,如果存在则代表合法
        boolean isVerify = SSOConstantPool.TOKEN_POOL.contains(ssoToken);
        if (!isVerify) {
            log.info("[ SSO-令牌校验 ] checkToken 令牌已失效 ssoToken:{}", ssoToken);
            return "false";
        }
    
        //把客户端的登出地址记录起来,后面注销的时候需要根据使用(生产环境建议存redis或库)
        List<ClientRegisterModel> clientInfoList =
            SSOConstantPool.CLIENT_REGISTER_POOL.computeIfAbsent(ssoToken, k -> new ArrayList<>());
        ClientRegisterModel vo = new ClientRegisterModel();
        vo.setLoginOutUrl(loginOutUrl);
        vo.setJsessionid(jsessionid);
        clientInfoList.add(vo);
        log.info("[ SSO-令牌校验 ] checkToken success ssoToken:{} , clientInfoList:{}", ssoToken, clientInfoList);
        return "true";
    }
    

    2.4校验登录状态接口

    当令牌校验返回失败时,子系统需要调用此接口

    /**
     * 校验是否已经登录认证中心(是否有全局会话)
     * 1.若存在则携带令牌ssoToken跳转至目标页面
     * 2.若不存在则跳转到登录页面
     */
    @RequestMapping("/checkLogin")
    public String checkLogin(String redirectUrl, RedirectAttributes redirectAttributes,
                             Model model, HttpServletRequest request) {
        //从认证中心-session中判断是否已经登录过(判断是否有全局会话)
        Object ssoToken = request.getSession().getAttribute("ssoToken");
    
        // ssoToken为空 - 没有全局回话
        if (StringUtils.isEmpty(ssoToken)) {
            log.info("[ SSO-登录校验 ] checkLogin fail 没有全局回话 ssoToken:{}", ssoToken);
            //登录成功需要跳转的地址继续传递
            model.addAttribute("redirectUrl", redirectUrl);
            //跳转到统一登录页面
            return "login";
        }
    
        log.info("[ SSO-登录校验 ] checkLogin success 有全局回话  ssoToken:{}", ssoToken);
        //重定向参数拼接(将会在url中拼接)
        redirectAttributes.addAttribute("ssoToken", ssoToken);
        //重定向到目标系统
        return "redirect:" + redirectUrl;
    }
    
    

    2.5统一退出接口

        /**
         * 统一注销
         * 1.注销全局会话
         * 2.通过监听全局会话session时效性,向已经注册的所有子系统发起注销请求
         */
        @RequestMapping("/logOut")
        public String logOut(HttpServletRequest request) {
            HttpSession session = request.getSession();
            log.info("[ SSO-统一退出 ] ....start.... sessionId:{}", session.getId());
            //注销全局会话, SSOSessionListener 监听器会处理后续操作
            request.getSession().invalidate();
            log.info("[ SSO-统一退出 ] ....end.... sessionId:{}", session.getId());
            return "logout";
        }
    

    退出监听,当统一认证中心session销毁时,同时注销子系统

    /**
     * session监听器
     *
     * @author 程序员小强
     */
    @Slf4j
    @WebListener
    public class SSOSessionListener implements HttpSessionListener {
        
        /**
         * 销毁事件监听
         * <p>
         * 1.session超时的时候会调用
         * 2.手动调用session.invalidate()方法时会调用.
         *
         * @param se
         */
        @Override
        public void sessionDestroyed(HttpSessionEvent se) {
            HttpSession session = se.getSession();
            String token = (String) session.getAttribute("ssoToken");
            log.debug("[ SSOSessionListener ] ...start..... sessionId:{},token:{}", session.getId(), token);
            //注销全局会话,SSOSessionListener监听类删除对应的信息
            session.invalidate();
            if (StringUtils.isEmpty(token)) {
                log.debug("[ SSOSessionListener ] token is null sessionId:{}", session.getId());
                return;
            }
            //清除存储的有效token数据
            SSOConstantPool.TOKEN_POOL.remove(token);
            //清除并返回已经注册的系统信息
            List<ClientRegisterModel> clientRegisterList = SSOConstantPool.CLIENT_REGISTER_POOL.remove(token);
            if (CollectionUtils.isEmpty(clientRegisterList)) {
                return;
            }
            for (ClientRegisterModel client : clientRegisterList) {
                if (null == client) {
                    continue;
                }
                //取出注册的子系统,依次调用子系统的登出方法(通过会话ID退出子系统的局部会话)
                sendHttpRequest(client.getLoginOutUrl(), client.getJsessionid());
                log.info("[ SSOSessionListener ] 注销系统 url:{},Jsessionid:{}", client.getLoginOutUrl(), client.getJsessionid());
            }
            log.debug("[ SSOSessionListener ] ...end..... sessionId:{},token:{}", session.getId(), token);
        }
    
        /**
         * 发送退出登录请求
         * 模拟浏览器访问形式
         *
         * @param reqUrl     发送请求的地址
         * @param jesssionId 会话Id
         */
        private static void sendHttpRequest(String reqUrl, String jesssionId) {
            try {
                //建立URL连接对象
                URL url = new URL(reqUrl);
                //创建连接
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                //设置请求的方式(需要是大写的)
                conn.setRequestMethod("POST");
                //设置需要响应结果
                conn.setDoOutput(true);
                //通过设置JSESSIONID模拟浏览器端操作
                conn.addRequestProperty("Cookie", "JSESSIONID=" + jesssionId);
                //发送请求到服务器
                conn.connect();
                conn.getInputStream();
                conn.disconnect();
            } catch (Exception e) {
                log.error("[ sendHttpRequest ] exception >> reqUrl:{}", reqUrl, e);
            }
        }
    }
    

    3.搭建客户端服务

    问题来了,搭建一个客户端需要哪些功能?

    • 拦截请求
    • 请求认证中心校验令牌有效性
      • 有效则创建局部会话
      • 无效则继续请求认证中心登录
    • 注销系统-请求认证中心统一注销

    3.1核心请求拦截器实现

    @Configuration
    public class WebConfig extends WebMvcConfigurationSupport {
    
        /**
         * 创建拦截器
         */
        @Bean
        WebInterceptor webInterceptor() {
            return new WebInterceptor();
        }
    
        /**
         * 添加拦截器-进行拦截
         * addPathPatterns 添加拦截
         * excludePathPatterns 排除拦截
         **/
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(this.webInterceptor())
                    .addPathPatterns("/**")
                    .excludePathPatterns("/logOut");
            super.addInterceptors(registry);
        }
    
        /**
         * 返回值-编码 UTF-8
         */
        @Bean
        public HttpMessageConverter<String> responseBodyConverter() {
            return new StringHttpMessageConverter(StandardCharsets.UTF_8);
        }
    
        @Override
        public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
            configurer.favorPathExtension(false);
        }
    
        /**
         * 资源处理器-资源路径 映射
         *
         * @param registry
         */
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            registry.addResourceHandler("/webjars/**")
                    .addResourceLocations("classpath:/META-INF/resources/webjars/");
        }
    
    }
    
    /**
     * 创建拦截器-拦截需要安全访问的请求
     * 方法说明
     * 1.preHandle():前置处理回调方法,返回true继续执行,返回false中断流程,不会继续调用其它拦截器
     * 2.postHandle():后置处理回调方法,但在渲染视图之前
     * 3.afterCompletion():全部后置处理之后,整个请求处理完毕后回调。
     *
     * @author 程序员小强
     */
    @Slf4j
    public class WebInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
            log.info("[ WebInterceptor ] >> preHandle  requestUrl:{} ", request.getRequestURI());
            //判断是否有局部会话
            HttpSession session = request.getSession();
            Object isLogin = session.getAttribute("isLogin");
            if (isLogin != null && (Boolean) isLogin) {
                log.debug("[ WebInterceptor ] >> 已登录,有局部会话 requestUrl:{}", request.getRequestURI());
                return true;
            }
            //获取令牌ssoToken
            String token = SSOClientHelper.getSsoToken(request);
    
            //无令牌
            if (StringUtils.isEmpty(token)) {
                //认证中心验证是否已经登录(是否存在全局会话)
                SSOClientHelper.checkLogin(request, response);
                return true;
            }
    
            //有令牌-则请求认证中心校验令牌是否有效
            Boolean checkToken = SSOClientHelper.checkToken(token, session.getId());
    
            //令牌无效
            if (!checkToken) {
                log.debug("[ WebInterceptor ] >> 令牌无效,将跳转认证中心进行认证 requestUrl:{}, token:{}", request.getRequestURI(), token);
                //认证中心验证是否已经登录(是否存在全局会话)
                SSOClientHelper.checkLogin(request, response);
                return true;
            }
    
            //token有效,创建局部会话设置登录状态,并放行
            session.setAttribute("isLogin", true);
            //设置session失效时间-单位秒
            session.setMaxInactiveInterval(1800);
            //设置本域cookie
            CookieUtil.setCookie(response, SSOClientHelper.SSOProperty.TOKEN_NAME, token, 1800);
            log.debug("[ WebInterceptor ] >> 令牌有效,创建局部会话成功 requestUrl:{}, token:{}", request.getRequestURI(), token);
            return true;
        }
    
    }
    

    4.源码介绍

    源码地址:传送门
    在这里插入图片描述

    5.源码实例测试

    由于是Demo实例,这里若有要访问-需要修改一下本机host,添加如下映射

     127.0.0.1 www.mysso.com
     127.0.0.1 www.myclient1.com
     127.0.0.1 www.myclient2.com
    

    添加好本机host映射后-分别启动
    sso-server 域名使用内网域名(www.mysso.com:8081)
    sso-client1 域名使用内网域名(www.myclient1.com:8082)
    sso-client2 域名使用内网域名(www.myclient1.com:8083)在这里插入图片描述

    5.1 客户端1登录

    5.1.1在浏览器端访问 www.myclient1.com:8082

    由于没有登录过任何一个系统,也没有全局会话,所以会跳转到统一认证中心进行登录,可以查看到redirectUrl就是我们要访问的www.myclient1.com:8082
    在这里插入图片描述

    点击登录(账密demo项目写死了,可以直接点击登录)
    在这里插入图片描述
    登录成功后会跳转到redirectUrl地址,并且携带ssoToken,这个时候客户端系统就可以请求认证中心校验ssoToken后创建内部的局部会话。

    5.2 客户端2登录

    由于客户端1已经登录,也就是说已经存在全局会话了,那么在访问客户端2的时候,其实无需登录的,只需要认证中心将最新的ssoToken携带过来就可以了。

    浏览器输入 www.myclient2.com:8083
    在这里插入图片描述
    可以查看到统一认证中心直接返回了认证token

    5.3 统一退出登录

    这里退出客户端1,退出完成后查看客户端1 与客户端2的登录状态
    在这里插入图片描述
    在这里插入图片描述

    这里继续访问www.myclient1.com:8082,会提示继续登录
    在这里插入图片描述

    访问www.myclient2.com:8083,会提示继续登录
    在这里插入图片描述

    说明统一退出登录成功了

    查看一下监听的日志,也是注销了每一个局部会话
    在这里插入图片描述

    至此SSO统一登录实战讲解完毕。

    需要完善的点还很多,比如服务端与客户端交互的时候可以通过加密或者加签方式防止数据被篡改。

    展开全文
  • 慕课java实现sso单点登录自己实现的代码,基于Struts2,本课程首先以新浪为例介绍了单点登录SSO的实现场景,然后对实现SSO所用的核心技术和原理进行分析,最后通过案例演示了同域SSO和跨域SSO的实现。
  • 单点登录的机制也一样,如下图所示,当用户第一次访问应用系统1的时候,因为还没有登录,会被引导到认证系统中进行登录(1);根据用户提供的登录信息,认证系统进行身份效验,如果通过效验,应该返回给用户一...

    1 . 什么是单点登陆

    单点登录(Single Sign On),简称为 SSO,SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统

    2. 单点登陆的技术实现机制

    单点登录的机制也一样,如下图所示,当用户第一次访问应用系统1的时候,因为还没有登录,会被引导到认证系统中进行登录(1);根据用户提供的登录信息,认证系统进行身份效验,如果通过效验,应该返回给用户一个认证的凭据--ticket(2);用户再访问别的应用的时候(3,5)就会将这个ticket带上,作为自己认证的凭据,应用系统接受到请求之后会把ticket送到认证系统进行效验,检查ticket的合法性(4,6)。

    如果通过效验,用户就可以在不用再次登录的情况下访问应用系统2和应用系统3了

    从上面的视图可以看出,要实现SSO,需要以下主要的功能

    • 所有应用系统共享一个身份认证系统
      统一的认证系统是SSO的前提之一。认证系统的主要功能是将用户的登录信息和用户信息库相比较,对用户进行登录认证;认证成功后,认证系统应该生成统一的认证标志(ticket),返还给用户。另外,认证系统还应该对ticket进行效验,判断其有效性。
    • 所有应用系统能够识别和提取ticket信息
      要实现SSO的功能,让用户只登录一次,就必须让应用系统能够识别已经登录过的用户。应用系统应该能对ticket进行识别和提取,通过与认证系统的通讯,能自动判断当前用户是否登录过,从而完成单点登录的功能。

    上面的功能只是一个非常简单的SSO架构,在现实情况下的SSO有着更加复杂的结构。有两点需要指出的是:

    • 单一的用户信息数据库并不是必须的,有许多系统不能将所有的用户信息都集中存储,应该允许用户信息放置在不同的存储中,如下图所示。事实上,只要统一认证系统,统一ticket的产生和效验,无论用户信息存储在什么地方,都能实现单点登录。

     

    • 统一的认证系统并不是说只有单个的认证服务器,如下图所示,整个系统可以存在两个以上的认证服务器,这些服务器甚至可以是不同的产品。认证服务器之间要通过标准的通讯协议,互相交换认证信息,就能完成更高级别的单点登录。如下图,当用户在访问应用系统1时,由第一个认证服务器进行认证后,得到由此服务器产生的ticket。当他访问应用系统4的时候,认证服务器2能够识别此ticket是由第一个服务器产生的,通过认证服务器之间标准的通讯协议(例如SAML)来交换认证信息,仍然能够完成SSO的功能。

     

    3. WEB-SSO的实现

    众所周知,Web协议(也就是HTTP)是一个无状态的协议。一个Web应用由很多个Web页面组成,每个页面都有唯一的URL来定义。用户在浏览器的地址栏输入页面的URL,浏览器就会向Web Server去发送请求。如下图,浏览器向Web服务器发送了两个请求,申请了两个页面。这两个页面的请求是分别使用了两个单独的HTTP连接。所谓无状态的协议也就是表现在这里,浏览器和Web服务器会在第一个请求完成以后关闭连接通道,在第二个请求的时候重新建立连接。Web服务器并不区分哪个请求来自哪个客户端,对所有的请求都一视同仁,都是单独的连接这样的方式大大区别于传统的(Client/Server)C/S结构,在那样的应用中,客户端和服务器端会建立一个长时间的专用的连接通道。正是因为有了无状态的特性,每个连接资源能够很快被其他客户端所重用,一台Web服务器才能够同时服务于成千上万的客户端。

    但是我们通常的应用是有状态的。先不用提不同应用之间的SSO,在同一个应用中也需要保存用户的登录身份信息。例如用户在访问页面1的时候进行了登录,但是刚才也提到,客户端的每个请求都是单独的连接,当客户再次访问页面2的时候,如何才能告诉Web服务器,客户刚才已经登录过了呢?浏览器和服务器之间有约定:通过使用cookie技术来维护应用的状态。Cookie是可以被Web服务器设置的字符串,并且可以保存在浏览器中。如下图所示,当浏览器访问了页面1时,web服务器设置了一个cookie,并将这个cookie和页面1一起返回给浏览器,浏览器接到cookie之后,就会保存起来,在它访问页面2的时候会把这个cookie也带上,Web服务器接到请求时也能读出cookie的值,根据cookie值的内容就可以判断和恢复一些用户的信息状态。

    Web-SSO完全可以利用Cookie结束来完成用户登录信息的保存,将浏览器中的Cookie和上文中的Ticket结合起来,完成SSO的功能。

     

    为了完成一个简单的SSO的功能,需要两个部分的合作:

    1. 统一的身份认证服务。
    2. 修改Web应用,使得每个应用都通过这个统一的认证服务来进行身份效验。

    4. SSO原理:

    SOIDs保存了在用户成功的登录后所产生的cookie和用户名的对应关系。它的功能显而易见:当用户成功登录以后,再次访问别的系统,为了鉴别这个用户请求所带的cookie的有效性,需要到SSOIDs中检查这样的映射关系是否存在。

    5. 当前方案的安全局限性

    当前这个WEB-SSO的方案是一个比较简单的雏形,主要是用来演示SSO的概念和说明SSO技术的实现方式。有很多方面还需要完善,其中安全性是非常重要的一个方面。

    我们说过,采用SSO技术的主要目的之一就是加强安全性,降低安全风险。因为采用了SSO,在网络上传递密码的次数减少,风险降低是显然的,但是当前的方案却有其他的安全风险。由于cookie是一个用户登录的唯一凭据,对cookie的保护措施是系统安全的重要环节:

    • cookie的长度和复杂度
      在本方案中,cookie是有一个固定的字符串(我的姓名)加上当前的时间戳。这样的cookie很容易被伪造和猜测。怀有恶意的用户如果猜测到合法的cookie就可以被当作已经登录的用户,任意访问权限范围内的资源
    • cookie的效验和保护
      在本方案中,虽然密码只要传输一次就够了,可cookie在网络中是经常传来传去。一些网络探测工具(如sniff, snoop,tcpdump等)可以很容易捕获到cookie的数值。在本方案中,并没有考虑cookie在传输时候的保护。另外对cookie的效验也过于简单,并不去检查发送cookie的来源究竟是不是cookie最初的拥有者,也就是说无法区分正常的用户和仿造cookie的用户。
    • 当其中一个应用的安全性不好,其他所有的应用都会受到安全威胁
      因为有SSO,所以当某个处于 SSO的应用被黒客攻破,那么很容易攻破其他处于同一个SSO保护的应用。

    这些安全漏洞在商业的SSO解决方案中都会有所考虑,提供相关的安全措施和保护手段,例如Sun公司的Access Manager,cookie的复杂读和对cookie的保护都做得非常好。另外在OpneSSO (https://opensso.dev.java.net)的架构指南中也给出了部分安全措施的解决方案。

    6. 当前方案的功能和性能局限性

    除了安全性,当前方案在功能和性能上都需要很多的改进:

    • 当前所提供的登录认证模式只有一种:用户名和密码,而且为了简单,将用户名和密码放在内存当中。事实上,用户身份信息的来源应该是多种多样的,可以是来自数据库中,LDAP中,甚至于来自操作系统自身的用户列表。还有很多其他的认证模式都是商务应用不可缺少的,因此SSO的解决方案应该包括各种认证的模式,包括数字证书,Radius, SafeWord ,MemberShip,SecurID等多种方式。最为灵活的方式应该允许可插入的JAAS框架来扩展身份认证的接口
    • 我们编写的Filter只能用于J2EE的应用,而对于大量非Java的Web应用,却无法提供SSO服务。
    • 在将Filter应用到Web应用的时候,需要对容器上的每一个应用都要做相应的修改,重新部署。而更加流行的做法是Agent机制:为每一个应用服务器安装一个agent,就可以将SSO功能应用到这个应用服务器中的所有应用。
    • 当前的方案不能支持分别位于不同domain的Web应用进行SSO。这是因为浏览器在访问Web服务器的时候,仅仅会带上和当前web服务器具有相同domain名称的那些cookie。要提供跨域的SSO的解决方案有很多其他的方法,在这里就不多说了。Sun的Access Manager就具有跨域的SSO的功能。
    • 另外,Filter的性能问题也是需要重视的方面。因为Filter会截获每一个符合URL映射规则的请求,获得cookie,验证其有效性。这一系列任务是比较消耗资源的,特别是验证cookie有效性是一个远程的http的调用,来访问SSOAuth的认证服务,有一定的延时。因此在性能上需要做进一步的提高。例如在本样例中,如果将URL映射从“.jsp”改成“/*”,也就是说filter对所有的请求都起作用,整个应用会变得非常慢。这是因为,页面当中包含了各种静态元素如gif图片,css样式文件,和其他html静态页面,这些页面的访问都要通过filter去验证。而事实上,这些静态元素没有什么安全上的需求,应该在filter中进行判断,不去效验这些请求,性能会好很多。另外,如果在filter中加上一定的cache,而不需要每一个cookie效验请求都去远端的身份认证服务中执行,性能也能大幅度提高。
    • 另外系统还需要很多其他的服务,如在内存中定时删除无用的cookie映射等等,都是一个严肃的解决方案需要考虑的问题。

    (该文是参考后按照自己的理解进行着重点标记)

    转自:http://www.blogjava.net/xcp/archive/2010/04/13/318125.html

    展开全文
  • java实现完全跨域SSO单点登录-JWT

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

    万次阅读 2019-05-30 17:53:54
    之前看过一些文章,直接说不清楚,因为我也是第一次研究单点登录,所有自己想写一篇希望能有所帮助 dome地址: 0.准备工作 1、tomcat8本地的安装 2、maven3本地的安装 3、使用maven发布项目到本地 1.简介 json...
  • "java实现简单的单点登录"源码包

    热门讨论 2013-06-21 17:25:36
    http://www.blogjava.net/xcp/archive/2010/04/13/318125.html 页面的源码地址无法访问,我在其他地方找到了。可以运行的三个简单应用,配置完成后可以实现单点登录,供初学者研究其原理。
  • java实现 SSO 单点登录(最终版)--补充完全跨域SSO

    万次阅读 多人点赞 2019-04-06 10:35:07
      前面我写了一篇文章,java实现完全跨域SSO单点登录,最后我会比较两种方案。   那篇文章主要说明完全跨域SSO单点登录的实现,但是我最终并没有使用那篇,当然,那篇完全可以实现SSO跨域,但是那篇有一些不太...
  • java实现简单的单点登录

    千次阅读 2017-02-24 14:22:18
    从技术本身的角度分析了单点登录技术的内部机制和实现手段,并且给出Web-SSO和桌面SSO的实现、源代码和详细讲解;还从安全和性能的角度对现有的实现技术进行进一步分析,指出相应的风险和需要改进的方面。本文除了从...
  • java构建一个单点登录和安全验证系统,单点登录系统需要提供10万级别用户访问群的承载,安全验证需提供各种可能的验证模式,验证方式采用一种高级的即可 安全验证同时需要提供一种比较好的权限控制机制,以实例...
  • java 单点登录源代码

    热门讨论 2014-11-10 15:48:44
    java web单点登录源代码,一个验证系统,两个测试系统
  • Java Web 系统单点登录实现

    千次阅读 2013-05-30 15:31:27
    在“CAS单点登录(SSO)服务器配置”这篇文章中我介绍了单点登录的概念和原理,以及单点登录服务器的配置方式。...  下面我们将介绍JAVA开发的Web系统的单点登录的... 要实现单点登录,首先要有单点登录服务器(SSO_S
  • 公司最近要做单点登录,对单点登录要求不是很高,请问下假如自己实现单点登录的话难么?有人自己实现过单点登录或者有什么好的思路和资料么? 框架的话暂时就知道CAS ,但是能不用框架最好...
  • java 单点登录

    万次阅读 2017-12-08 17:04:59
    单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。 较大的企业内部,一般都有很多的...
  • 单点登录sso的原理与java实现详细讲解与示例(文档与源码)
  • Java单点登录 踢出上一次登录 类似qq的"顶号
  • java web 实现单点登录是不是要求几个子系统必须通用一个用户表 这样才能确保几个子系统账号和密码不同的情况下 通过判断用户表的主键 就行用户的判断和登录
  • Java httpclient实现CAS单点登录

    千次阅读 2018-09-18 16:19:51
    nvps.add(new BasicNameValuePair("submit", "登录")); post.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8")); HttpResponse response = httpClient.execute(post); ...
  • java单点登录实现

    万次阅读 2011-03-23 09:20:00
    单点登录首先首先要保持数据库数据的一致性,这个实现方式很多我就不多废话了。 刚开始我的想法是在一个应用中登陆的时候发送一个http请求到另一个应用保证两个应用同时拥有session,后来考虑到session...
  • 从技术本身的角度分析了单点登录技术的内部机制和实现手段,并且给出Web-SSO和桌面SSO的实现、源代码和详细讲解;还从安全和性能的角度对现有的实现技术进行进一步分析,指出相应的风险和需要改进的方面。本文除了从...
  • Java实现的SSO单点登录

    万次阅读 2014-09-02 13:29:33
    想像一下,一家企业从无信息化系统开始着手实现自己公司的信息化,假如这家公司有自己的IT团队,第一个系统公司一般都会先上OA系统,他的基本结构如下: 系统上线后运行很正常,公司从企业信息化中尝到甜头,想...
  • Oauth2方式实现单点登录

    千次阅读 2020-07-30 11:18:48
    下面先简单介绍一下Oauth2的原理 Oauth2是什么? Oauth2是一种授权机制,用来授权给第三方应用,获取用户数据。 Oauth2是什么的解释 这是阮一峰老师的解释,因此解释的比较好了,就不在此... 其他的几种方式可以去这
  • 单点登录原理与简单实现 一、单系统登录机制 1、http无状态协议  web应用采用browser/server架构,http作为通信协议。http是无状态协议,浏览器的每一次请求,服务器会独立处理,不与之前或之后的请求产生关联,...
  • java使用CAS实现SSO单点登录

    千次阅读 2017-03-24 21:22:02
    开源的企业级单点登录解决方案CAS Server 是需要独立部署的 Web 应用CAS Client 支持非常多的客户端(这里指单点登录系统中的各个 Web 应用),包括 Java, .Net, PHP, Perl, Apache, uPortal, Ruby 等   CAS服务...
  • 要做一个Java单点登录系统,用户登录系统后,可以直接跳转到所需的子系统主页,无需再输入用户名密码。网上查了些资料,准备用cas实现,但发现子系统有权限管理,举个例子,用户所属部门不同,能访问的某个子系统的...
  • JAVA单点登录

    万次阅读 2017-08-10 16:35:22
    首先我们先了解一下什么是单点登录单点登录Single Sign On简称为SSO,是目前比较流行的企业业务整合的解决方案之一。F5 BIG-IP Edge Gateway解决方案借助SSO改进用户体验。SSO的定义是在多个应用系统中,用户只...
  • cas实现单点登录,登出(java和php客户端)

    万次阅读 热门讨论 2011-08-17 15:43:37
    最近项目中需要做单点登录,客户端包含java和php,java有几个应用程序,php...要这几个客户端都要能单点登录和登出,在网上找了许多相关资料,今天终于配置成功,步骤如下: 1、cas服务端:下载地址:http://downl
  • java单点登录需求分析与代码实现

    万次阅读 多人点赞 2018-11-16 17:56:49
    需求背景:随着公司的发展,公司...如果同时要使用CRM系统、WMS系统、OA系统,用户需要登录三次,如果10个系统需要登录分别登录十次,非常繁琐。  3.如果不使用了,还需要分别在三个系统中依次的注销。 需求:  ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 677,410
精华内容 270,964
关键字:

java实现单点登录的方式

java 订阅