SSO搭建(框架KISSO)

来源:互联网 发布:sql应用程序在哪里 编辑:程序博客网 时间:2024/05/16 10:45

首先贴出国内大神的开源SSO框架 KISSO

http://git.oschina.net/baomidou/kisso

废话不多说,开始~

这里写图片描述

贴一张KISSO文档上的原理图

其实就是用户在登录业务系统的时候。看一下本地是否有Cookie

如果没有Cookie。访问SSO项目。SSO也没有Cookie的话。

进行登录。登录成功将加密的Token写入Cookie

回传给业务系统。通过几次的加密。认证,双方认证OK后

在业务系统域名下写入Cookie。完成登录


一、KISSO服务器端集成(springMVC+redis)

1、maven依赖增加

<!-- kisso begin -->       <dependency>           <groupId>com.baomidou</groupId>           <artifactId>kisso</artifactId>           <version>3.6.10</version>       </dependency>       <dependency>           <groupId>org.bouncycastle</groupId>           <artifactId>bcprov-jdk14</artifactId>           <version>1.50</version>       </dependency>       <dependency>           <groupId>com.alibaba</groupId>           <artifactId>fastjson</artifactId>           <version>1.1.46</version>       </dependency>       <dependency>           <groupId>com.google.code.gson</groupId>           <artifactId>gson</artifactId>           <version>2.8.0</version>       </dependency>       <!-- kisso end -->

2、web.xml(这里使用过滤器方式,不使用spring-mvc拦截器方式)

over.url:这里参数为不需要过滤器过滤的方法。

<!-- kisso -->    <context-param>        <param-name>kissoConfigLocation</param-name>        <param-value>classpath:properties/sso.properties</param-value>    </context-param>    <listener>        <listener-class>com.baomidou.kisso.web.KissoConfigListener</listener-class>    </listener>    <!-- SSOFilter -->    <filter>        <filter-name>SSOFilter</filter-name>        <filter-class>com.baomidou.kisso.web.filter.SSOFilter</filter-class>        <init-param>            <param-name>over.url</param-name>            <param-value>/phoneSms;/PhoneSmsCode;</param-value>        </init-param>    </filter>    <filter-mapping>        <filter-name>SSOFilter</filter-name>        <url-pattern>/*</url-pattern>    </filter-mapping>

其实就是看本地有没有Cookie 。没有就跳转到SSO项目

public class SSOClientFilter implements Filter {    private static final Logger logger = Logger.getLogger("SSOFilter");    private static String OVERURL = null;    public SSOClientFilter() {    }    public void init(FilterConfig config) throws ServletException {        OVERURL = config.getInitParameter("over.url");    }    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {        HttpServletRequest req = (HttpServletRequest)request;        HttpServletResponse res = (HttpServletResponse)response;        boolean isOver = HttpUtil.inContainURL(req, OVERURL);        if(!isOver) {            Token token = SSOHelper.getToken(req);            if(token == null) {                logger.fine("logout. request url:" + req.getRequestURL());                SSOProperties prop = SSOConfig.getSSOProperties();                String retStr = prop.get("sso.defined.proxyloginurl");//                String retUrl = HttpUtil.getQueryString(req, "UTF-8");//                this.logger.fine("loginAgain redirect pageUrl.." + retUrl);                res.sendRedirect(HttpUtil.encodeRetURL(prop.get("sso.login.url"), "ReturnURL", retStr));                return;            }            SsoUser user = (SsoUser) req.getSession().getAttribute(UserConstants.LOGIN_USER);            if(user == null){                SSOHelper.logout(req,res);                return;            }            req.setAttribute("SSOTokenAttr", token);        }        chain.doFilter(request, response);    }    public void destroy() {        OVERURL = null;    }}

3、KISSO的配置文件 sso.properties

这里具体就不贴出来了。 查阅一下文档写的非常详细

################ SSOConfig file #################sso.role=应用名sso.secretkey=秘钥sso.cookie.domain=cookie存储的位置sso.login.url=登录的url#cookie setting# crossdomain secretkeysso.authcookie.secretkey=跨域对称加密的秘钥#cachesso.cache.class=缓存实现类。实现SSOCache接口 sso.cache.expires=有效时间# userConfig definedsso.defined.my_public_key=业务系统的公钥(校验业务系统)sso.defined.sso_private_key=SSO系统的私钥(加密使用)

4、服务端控制层

@Controllerpublic class SsoController {    private static Logger log = Logger.getLogger(SsoController.class);    @Autowired    private SsoUserService ssoUserService;    protected String redirectTo(String url) {        StringBuffer rto = new StringBuffer("redirect:");        rto.append(url);        return rto.toString();    }    /**     * 登录 (注解跳过权限验证)     */    @Login(action = Action.Skip)    @RequestMapping("/login")    public String login(RedirectAttributesModelMap modelMap, Model model, HttpServletRequest request, HttpServletResponse response) {        log.info("登录 (注解跳过权限验证)");        String returnUrl = request.getParameter(SSOConfig.getInstance().getParamReturl());        Token token = SSOHelper.getToken(request);        if (token == null) {            /**             * 正常登录 需要过滤sql及脚本注入             */            WafRequestWrapper wr = new WafRequestWrapper(request);            String loginUser = wr.getParameter("loginUser");            String loginPass = wr.getParameter("loginPass");            //定义校验失败标识符            boolean falg = false;            //定义校验失败的字符串            String falgStr = "";            if (loginUser != null && !"".equals(loginUser)) {                SsoUser user = ssoUserService.findByName(loginUser);                if(user != null){                    if(user.getPassword().equals(Md5Util.MD5Encode(loginPass))){                        /*                         * 设置登录 Cookie                         * 最后一个参数 true 时添加 cookie 同时销毁当前 JSESSIONID 创建信任的 JSESSIONID                         */                        SSOToken st = new SSOToken(request, user.getId()+"");//                        st.setData("jjc看源码哦");                        //记住密码就设置                        //SSOConfig.getInstance().setCookieMaxage(604800);                        SSOHelper.setSSOCookie(request, response, st, true);                    }else{//证明密码不匹配                        falg = true;                        falgStr = "密码不正确";                    }                }else{//证明没有用户名                    falg = true;                    falgStr = "用户名不正确";                }            } else {                falg = true;                falgStr = "";            }            if(falg){//校验失败。跳转回登录页                model.addAttribute("msg", falgStr);                if (StringUtils.isNotEmpty(returnUrl)) {                    model.addAttribute("ReturnURL", returnUrl);                }                model.addAttribute("loginUser", loginUser);                model.addAttribute("loginPass", loginPass);                return "login";            }else{//校验成功,重定向到业务系统                // 重定向到指定地址 returnUrl                if (StringUtils.isEmpty(returnUrl)) {                    returnUrl = "/index.html";                } else {                    returnUrl = HttpUtil.decodeURL(returnUrl);                }                return redirectTo(returnUrl);            }        } else {            if (StringUtils.isEmpty(returnUrl)) {                returnUrl = "/index.html";            }            return redirectTo(returnUrl);        }    }    @ResponseBody    @RequestMapping("/replylogin")    public void replylogin(HttpServletRequest request, HttpServletResponse response) {        log.info("SSO回复子系统");        StringBuffer replyData = new StringBuffer();        replyData.append(request.getParameter("callback")).append("({\"msg\":\"");        Token token = SSOHelper.getToken(request);        if (token != null) {            String askData = request.getParameter("askData");            if (askData != null && !"".equals(askData)) {                /**                 *                 * 用户自定义配置获取                 *                 * <p>                 * 由于不确定性,kisso 提倡,用户自己定义配置。                 * </p>                 *                 */                SSOProperties prop = SSOConfig.getSSOProperties();                //下面开始验证票据,签名新的票据每一步都必须有。                AuthToken at = SSOHelper.replyCiphertext(request, askData);                if (at != null) {                    //1、业务系统公钥验证签名合法性(此处要支持多个跨域端,取 authToken 的 app 名找到对应系统公钥验证签名)                    at = at.verify(prop.get("sso.defined." + at.getApp() + "_public_key"));                    if (at != null) {                        //at.getUuid() 作为 key 设置 authToken 至分布式缓存中,然后 sso 系统二次验证                        //at.setData(data); 设置自定义信息,当然你也可以直接 at.setData(token.jsonToken()); 把当前 SSOToken 传过去。                        at.setUid(token.getUid());//设置绑定用户ID                        at.setTime(token.getTime());//设置登录时间                        //2、SSO 的私钥签名                        at.sign(prop.get("sso.defined.sso_private_key"));                        //3、生成回复密文票据                        replyData.append(at.encryptAuthToken());                    } else {                        //非法签名, 可以重定向至无权限界面,自己处理                        replyData.append("-2");                    }                } else {                    //非法签名, 可以重定向至无权限界面,自己处理                    replyData.append("-2");                }            }        } else {            // 未登录            replyData.append("-1");        }        try {            replyData.append("\"})");            AjaxHelper.outPrint(response, replyData.toString(), "UTF-8");        } catch (IOException e) {            e.printStackTrace();        }    }    /**     * 统一退出,调用对外提供退出的所有接口     */    @RequestMapping("/logout")    public String logout(HttpServletRequest request,HttpServletResponse response) {        SSOHelper.clearLogin(request, response);        return "logout";    }    /**     */    @RequestMapping("/index")    public String index(HttpServletRequest request,HttpServletResponse response) {        return "index";    }}

5、redis集成(退出使用,具体请查阅官方文档)

public class SSORedisCaChe implements SSOCache {

StringRedisTemplate stringRedisTemplate = (StringRedisTemplate) WebApplicationContextHelper.getBean("stringRedisTemplate");@Overridepublic Token get(String s, int i) {    if(exists(s)){        String result = null;        ValueOperations operations = stringRedisTemplate.opsForValue();        result = (String)operations.get(s);        result = result.replaceAll("\u0000" , "");        Token token = SSOConfig.getInstance().getParser().parseObject(result, Token.class);        return  token;    }    return null;}@Overridepublic boolean set(String s, Token token, int i) {    boolean result = false;    try {        delete(s);        ValueOperations valueOperations = stringRedisTemplate.opsForValue();        valueOperations.set(s,token.jsonToken(),i);        result = true;    } catch (Exception e) {        e.printStackTrace();    }    return result;}@Overridepublic boolean delete(String s) {    boolean result = false;    try{        if (exists(s)) {            stringRedisTemplate.delete(s);            return  true;        }    }catch (Exception e){        e.printStackTrace();    }    return result;}public boolean exists(final String key) {    return stringRedisTemplate.hasKey(key);}

}

二、客户端集成

1、maven一致
2、web.xml

SSOClientFilter 客户端自定义的过滤器。模仿kisso的过滤器

<!-- kisso -->    <context-param>        <param-name>kissoConfigLocation</param-name>        <param-value>classpath:properties/sso.properties</param-value>    </context-param>    <listener>        <listener-class>com.baomidou.kisso.web.KissoConfigListener</listener-class>    </listener>    <!-- SSOFilter -->    <filter>        <filter-name>SSOClientFilter</filter-name>        <filter-class>com.hc360.yunxin.filter.SSOClientFilter</filter-class>        <init-param>            <param-name>over.url</param-name>            <param-value>/login;/verify;/resources/;/code;/proxylogin;/oklogin;/accountsecurity/mailboxBound;/accountsecurity/mailboxBoundUpdate</param-value>        </init-param>    </filter>    <filter-mapping>        <filter-name>SSOClientFilter</filter-name>        <url-pattern>/*</url-pattern>    </filter-mapping>

3、sso.properties

################ SSOConfig file #################sso.role=业务系统名字 , 拼接使用sso.secretkey=加密秘钥sso.cookie.domain=写入cookie地址sso.login.url=登录url-》跳转SSOsso.logout.url=登出url# crossdomain secretkeysso.authcookie.secretkey=#cachesso.cache.class=缓存sso.cache.expires=失效时间 秒单位# userConfig definedsso.defined.proxyloginurl=sso回复客户端的地址sso.defined.askurl=sso认证地址sso.defined.oklogin=客户端写入cookie的actionsso.defined.clientIndex=客户端自己的主页sso.defined.clientTimeout=自定义超时链接sso.defined.my_private_key=业务系统的私钥sso.defined.my_public_key=业务系统的公钥sso.defined.sso_public_key=sso系统的公钥

4、客户端Controller

@Controllerpublic class SsoController {    @Autowired    private SsoUserService ssoUserService;    protected String redirectTo(String url) {        StringBuffer rto = new StringBuffer("redirect:");        rto.append(url);        return rto.toString();    }    @RequestMapping("/index")    public String index(Model model, HttpServletRequest request,HttpServletResponse response) throws UnsupportedEncodingException {        Token token = SSOHelper.getToken(request);        response.setCharacterEncoding("UTF-8");        request.setCharacterEncoding("UTF-8");        if (token == null) {            /**             * 重定向至代理跨域地址页             */            return redirectTo("http://sso.test.com:8081/kisso_crossdomain_sso/login.html?ReturnURL=http%3A%2F%2Fmy.web.com%3A8082%2F/kisso_crossdomain_my/proxylogin.html");        } else {//            model.addAttribute("userId", token.getUid());            SsoUser SessionssoUser = (SsoUser) request.getSession().getAttribute(UserConstants.LOGIN_USER);            model.addAttribute("user",  ssoUserService.findByID(SessionssoUser.getId()));        }        return "index";    }    /**     * 跨域登录     */    @RequestMapping("/proxylogin")    public String proxylogin(Model model,HttpServletRequest request,HttpServletResponse response) {        /**         *         * 用户自定义配置获取         *         * <p>         * 由于不确定性,kisso 提倡,用户自己定义配置。         * </p>         *         */        SSOProperties prop = SSOConfig.getSSOProperties();        //业务系统私钥签名 authToken 自动设置临时会话 cookie 授权后自动销毁        AuthToken at = SSOHelper.askCiphertext(request, response, prop.get("sso.defined.my_private_key"));        //at.getUuid() 作为 key 设置 authToken 至分布式缓存中,然后 sso 系统二次验证        //askurl 询问 sso 是否登录地址        model.addAttribute("askurl", prop.get("sso.defined.askurl"));        //askTxt 询问 token 密文        model.addAttribute("askData", at.encryptAuthToken());        //my 确定是否登录地址        model.addAttribute("okurl", prop.get("sso.defined.oklogin"));        return "proxylogin";    }    /**     * 跨域登录成功     */    @ResponseBody    @RequestMapping("/oklogin")    public void oklogin( HttpServletRequest request,HttpServletResponse response) {        SSOProperties prop = SSOConfig.getSSOProperties();        //String returl = prop.get("sso.defined.clientTimeout");        String returl =prop.get("sso.logout.url");        /*         * <p>         * 回复密文是否存在         * </p>         * <p>         * SSO 公钥验证回复密文是否正确         * </p>         * <p>         * 设置 业务系统自己的 Cookie         * </p>         */        String replyTxt = request.getParameter("replyTxt");        if (replyTxt != null && !"".equals(replyTxt)) {            AuthToken at = SSOHelper.ok(request, response, replyTxt, prop.get("sso.defined.my_public_key"),                    prop.get("sso.defined.sso_public_key"));            if (at != null) {                returl = prop.get("sso.defined.clientIndex");                SSOToken st = new SSOToken();                st.setUid(at.getUid());                st.setTime(at.getTime());//                st.setData(at.getData());                /*                 * 设置 true 时添加 cookie 同时销毁当前 JSESSIONID 创建信任的 JSESSIONID                 */                SSOHelper.setSSOCookie(request, response, st, true);                //设置完Cookie  设置Session                request.getSession().setAttribute(UserConstants.LOGIN_USER, ssoUserService.findByID(Integer.parseInt(at.getUid())));            }        }        try {            AjaxHelper.outPrint(response, "{\"returl\":\"" + returl + "\"}", "UTF-8");        } catch (IOException e) {            e.printStackTrace();        }    }    /**     * 跨域登录超时     */    @RequestMapping("/timeout")    public String timeout() {        return "timeout";    }    /*     *     * 如果实现 SSOCache 缓存, kisso 自动缓存 token 退出只需要 SSOHelper.clearLogin(request, response);     *     * 自动清理 token 缓存信息, 同时各个系统都会自动退出。 建议这么!!退出更优雅。。。     *     * --------------- 悲剧的开启 ---------------     *     * 如果你不这么干那么您只能挨个不同域退出一遍,最终全站退出。     *     */    @RequestMapping("/logout")    public String logout( HttpServletRequest request,HttpServletResponse response) {        /**         * <p>         * SSO 退出,清空退出状态即可         * </p>         *         * <p>         * 子系统退出 SSOHelper.logout(request, response); 注意 sso.properties 包含 退出到         * SSO 的地址 , 属性 sso.logout.url 的配置         * </p>         */        SSOHelper.clearLogin(request, response);        return "redirect:/index";    }}

5、redis与服务一致

6、客户端发送跨域,请求SSO认证的静态页面

<body><script type="text/javascript">    function proxyLogin(askurl, askData, okurl) {        var killAjax = true;//      setTimeout(function() {//          checkajaxkill();//      }, 30000);        var ajaxCall = jQuery.getJSON(askurl + "?callback=?", {askData:askData}, function(d){            killAjax = false;            if(d.msg == "-1"){                window.location.href = SSOLoginUrl;            }else{                jQuery.post(okurl, {replyTxt:d.msg} , function(e) {                    window.location.href = e.returl;                }, "json");            }        });//      function checkajaxkill(){//          if(killAjax){//              ajaxCall.abort();//              window.location.href = SSOTimeout;//          }//      }    }    proxyLogin("$!{askurl}", "$!{askData}", "$!{okurl}");</script><div align="center" style="margin-top: 180px;">    <img src="resources/img/loading.gif"> 页面正在加载中,请稍候……</div></body>

登录总结流程:

这里写图片描述

1、用户访问业务系统,通过自定义拦截器跳转到SSO系统进行认证。

2、在SSO系统登陆成功后。进行浏览器重定向

3、重定向到业务系统的Controller

4、业务系统的Controller将自己的私钥加密临时会话Cookie信息,发送给SSO进行认证。(认证时需要使用JSONP方式跨域发送请求 KISSO框架处理方式)

5、SSO系统首先会看看自己本地是否有Cookie,如果有的话将加密信息进行解密,拿到信息中业务系统的名字,拼接配置文件的配置。拿到业务系统公钥进行校验。 成功后在使用SSO系统的私钥进行加密信息返回

6、JSONP消息拿到后,使用业务系统的公钥以及SSO系统的公钥校验回传的信息是否正确。无问题后。将本地设置好Cookie uid 并跳转业务系统

登出流程总结:

这里写图片描述

根据KISSO的接口自定义Cache通过集成Redis保存加密Token
通过以下方法进行登出
SSOHelarLogin(request, response);
原理:
将Redis中的数据清除后,其他系统在操作时通过KISSO的过滤器会通过自定义cache读取缓存信息,如数据清除。将清除其他系统本地的Cookie。

原创粉丝点击