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。
- SSO搭建(框架KISSO)
- SSO 中间件 kisso
- java sso 基于 cookie 实现方案 kisso
- 基于 Cookie 的 SSO 中间件 kisso
- CAS实现单点登录(sso)搭建流程 服务器端搭建
- SSO(一) cas搭建
- cas sso搭建
- 【SSO单点系列】(1):CAS4.0 环境的搭建
- 【SSO单点系列】(1):CAS4.0 环境的搭建
- 【SSO单点系列】(1):CAS4.0 环境的搭建
- 学习淘淘商城第八十二课(SSO工程搭建)
- CAS SSO NET client 搭建
- cas搭建sso单点登陆
- SSO之CAS单点登录详细搭建教程(转载!学习! 转载!学习! 转载!学习!)
- cas与tomcat简单搭建SSO
- cas与tomcat简单搭建SSO
- (一)SSO之CAS框架通俗原理
- (一)SSO之CAS框架通俗原理
- lucas定理
- LIBSVM与LIBLINEAR
- ubuntu调整分辨率
- Android布局----详解
- 1.7-Java-冒泡排序
- SSO搭建(框架KISSO)
- Jmeter连接SqlServer数据库进行压力测试
- 归并排序
- python中的__new__概念(工厂
- java新手上路(五):线程,多线程
- Uva514——Rails
- HDU1201 18岁的生日
- Spring Boot整合jpa,Shiro进行权限管理
- 用学习曲线 learning curve 来判别过拟合问题