范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

SpringCloud系列SSO单点登录

  前言
  作为分布式项目,单点登录是必不可少的,文本基于之前的的博客(猛戳:SpringCloud系列——Zuul 动态路由,SpringBoot系列——Redis)记录Zuul配合Redis实现一个简单的sso单点登录实例
  sso单点登录思路:
  1、访问分布式系统的任意请求,被Zuul的Filter拦截过滤
  2、在run方法里实现过滤规则:cookie有令牌accessToken且作为key存在于Redis,或者访问的是登录页面、登录请求则放行
  3、否则,将重定向到sso-server的登录页面且原先的请求路径作为一个参数;response.sendRedirect("http://localhost:10010/sso-server/sso/loginPage?url=" + url);
  4、登录成功,sso-server生成accessToken,并作为key(用户名+时间戳,这里只是demo,正常项目的令牌应该要更为复杂)存到Redis,value值存用户id作为value(或者直接存储可暴露的部分用户信息也行)设置过期时间(我这里设置3分钟);设置cookie:new Cookie("accessToken",accessToken);,设置maxAge(60*3);、path("/");
  5、sso-server单点登录服务负责校验用户信息、获取用户信息、操作Redis缓存,提供接口,在eureka上注册   代码编写  sso-server
  首先我们创建一个单点登录服务sso-server,并在eureka上注册(创建项目请参考之前的SpringCloud系列博客跟 SpringBoot系列——Redis)
  login.html
  我们这里需要用到页面,要先maven引入thymeleaf                      org.springframework.boot             spring-boot-starter-thymeleaf                     登录页面         
  提供如下接口@RestController @EnableEurekaClient @SpringBootApplication public class SsoServerApplication {      public static void main(String[] args) {         SpringApplication.run(SsoServerApplication.class, args);     }      @Autowired     private StringRedisTemplate template;      /**      * 判断key是否存在      */     @RequestMapping("/redis/hasKey/{key}")     public Boolean hasKey(@PathVariable("key") String key) {         try {             return template.hasKey(key);         } catch (Exception e) {             e.printStackTrace();             return false;         }     }      /**      * 校验用户名密码,成功则返回通行令牌(这里写死huanzi/123456)      */     @RequestMapping("/sso/checkUsernameAndPassword")     private String checkUsernameAndPassword(String username, String password) {         //通行令牌         String flag = null;         if ("huanzi".equals(username) && "123456".equals(password)) {             //用户名+时间戳(这里只是demo,正常项目的令牌应该要更为复杂)             flag = username + System.currentTimeMillis();             //令牌作为key,存用户id作为value(或者直接存储可暴露的部分用户信息也行)设置过期时间(我这里设置3分钟)             template.opsForValue().set(flag, "1", (long) (3 * 60), TimeUnit.SECONDS);         }         return flag;     }      /**      * 跳转登录页面      */     @RequestMapping("/sso/loginPage")     private ModelAndView loginPage(String url) {         ModelAndView modelAndView = new ModelAndView("login");         modelAndView.addObject("url", url);         return modelAndView;     }      /**      * 页面登录      */     @RequestMapping("/sso/login")     private String login(HttpServletResponse response, String username, String password, String url) {         String check = checkUsernameAndPassword(username, password);         if (!StringUtils.isEmpty(check)) {             try {                 Cookie cookie = new Cookie("accessToken", check);                 cookie.setMaxAge(60 * 3);                 //设置域 //                cookie.setDomain("huanzi.cn");                 //设置访问路径                 cookie.setPath("/");                 response.addCookie(cookie);                 //重定向到原先访问的页面                 response.sendRedirect(url);             } catch (IOException e) {                 e.printStackTrace();             }             return null;         }         return "登录失败";     } }  zuul-server
  引入feign,用于调用sso-server服务                              org.springframework.cloud             spring-cloud-starter-openfeign         
  创建SsoFeign.java接口@FeignClient(name = "sso-server", path = "/") public interface SsoFeign {     /**      * 判断key是否存在      */     @RequestMapping("redis/hasKey/{key}")     public Boolean hasKey(@PathVariable("key") String key);  }
  启动类加入@EnableFeignClients注解,否则启动会报错,无法注入SsoFeign对象@EnableZuulProxy @EnableEurekaClient @EnableFeignClients @SpringBootApplication public class ZuulServerApplication {      public static void main(String[] args) {         SpringApplication.run(ZuulServerApplication.class, args);     }      @Bean     public AccessFilter accessFilter() {         return new AccessFilter();     } }
  修改AccessFilter过滤逻辑,注入feign接口,用于调用sso-server检查Redis,修改run方法的过滤逻辑/**  * Zuul过滤器,实现了路由检查  */ public class AccessFilter extends ZuulFilter {      @Autowired     private SsoFeign ssoFeign;      /**      * 通过int值来定义过滤器的执行顺序      */     @Override     public int filterOrder() {         // PreDecoration之前运行         return PRE_DECORATION_FILTER_ORDER - 1;     }      /**      * 过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型:      * public static final String ERROR_TYPE = "error";      * public static final String POST_TYPE = "post";      * public static final String PRE_TYPE = "pre";      * public static final String ROUTE_TYPE = "route";      */     @Override     public String filterType() {         return PRE_TYPE;     }      /**      * 过滤器的具体逻辑      */     @Override     public Object run() {         RequestContext ctx = RequestContext.getCurrentContext();         HttpServletRequest request = ctx.getRequest();         HttpServletResponse response = ctx.getResponse();          //访问路径         String url = request.getRequestURL().toString();          //从cookie里面取值(Zuul丢失Cookie的解决方案:https://blog.csdn.net/lindan1984/article/details/79308396)         String accessToken = request.getParameter("accessToken");         Cookie[] cookies = request.getCookies();         if(null != cookies){             for (Cookie cookie : cookies) {                 if ("accessToken".equals(cookie.getName())) {                     accessToken = cookie.getValue();                 }             }         }         //过滤规则:cookie有令牌且存在于Redis,或者访问的是登录页面、登录请求则放行         if (url.contains("sso-server/sso/loginPage") || url.contains("sso-server/sso/login") || (!StringUtils.isEmpty(accessToken) && ssoFeign.hasKey(accessToken))) {             ctx.setSendZuulResponse(true);             ctx.setResponseStatusCode(200);             return null;         } else {             ctx.setSendZuulResponse(false);             ctx.setResponseStatusCode(401);             //重定向到登录页面             try {                 response.sendRedirect("http://localhost:10010/sso-server/sso/loginPage?url=" + url);             } catch (IOException e) {                 e.printStackTrace();             }             return null;         }     }      /**      * 返回一个boolean类型来判断该过滤器是否要执行      */     @Override     public boolean shouldFilter() {         return true;     } }
  修改配置文件,映射sso-server代理路径,超时时间与丢失cookie的解决zuul.routes.sso-server.path=/sso-server/** zuul.routes.sso-server.service-id=sso-server   zuul.host.socket-timeout-millis=60000 zuul.host.connect-timeout-millis=10000 #Zuul丢失Cookie的解决方案:https://blog.csdn.net/lindan1984/article/details/79308396 zuul.sensitive-headers=  测试效果
  启动eureka、zuul-server、sso-server、config-server、myspringboot、springdatajpa(由两个应用组成,实现了ribbon负载均衡),记得启动我们的RabbitMQ服务和Redis服务!
  刚开始,没有cookie且无Redis的情况下,浏览器访问 http://localhost:10010/myspringboot/feign/ribbon,被zuul-server拦截重定向到sso-server登录页面
  开始登录校验,为了方便演示,我将密码的type改成text
  登录失败,返回提示语
  登录成功,重定向到之前的请求
  cookie的值,以及过期时间
  3分钟后我们再次访问 http://localhost:10010/myspringboot/feign/ribbon,cookie、Redis失效,需要从新登录
  扩展
  我们还缺了重要的一种情况,那就是静态文件的处理,我们先把feign/ribbon接口改一下,并且新增ribbon.html文件    @RequestMapping("/ribbon")     public ModelAndView ribbon() {         return new ModelAndView("ribbon","text","springdatejpa -- 我的端口是:10086") ;     }      @RequestMapping("/ribbon")     public ModelAndView ribbon() {         return new ModelAndView("ribbon","text","springdatejpa -- 我的端口是:10088") ;     }              Ribbon测试       

  处理静态资源   如果我们按照常规去引入项目的静态资源文件,thymeleaf的@{取到的值是http://localhost:10010/,因此会报404 注:这两个工程的静态文件目录如下:      本来想通过Zuul去转发请求,结果还是不行,上网一查发现有人说:zuul我们只用来做服务的转发,不用做页面的转发。页面中包含的静态资源没办法直接通过zuul获取对应的静态资源。   经过考虑,我这里采用读取当前页面文件所在的工程的静态文件,就不经过Zuul了,先在当前工程里声明好baseUrl,通过使用thymeleaf取国际化文件的方法,取到当前页面文件所在工程的baseUrl路径(需要先实现springboot国际化,具体配置请戳之前的博客:SpringBoot系列——i18n国际化),并且各自在自己工程的国际化文件新增:baseUrl=http://localhost:10086baseUrl=http://localhost:10088   ribbon.html做如下修改(两个工程都一样) Ribbon测试

  引入成功   处理API接口   后台API接口是必须要走Zuul的,接上面的页面,我们有一个简单的测试按钮,请求getDataAPI接口   我们先给实现了Ribbon负载均衡的springdatajpa(由两个工程组成)新增连个测试接口 @PostMapping("/getData") public String getData() { return "springdatejpa -- 我的端口是:10086" ; } @PostMapping("/getData") public String getData() { return "springdatejpa -- 我的端口是:10088" ; }   然后给myspringboot工程新增一个Feign接口、以及一个controller接口@FeignClient(name = "springdatejpa", path = "/user/") public interface MyspringbootFeign { //此处省略之前的接口 @PostMapping("/getData") String getData(); } /** * feign调用 */ @PostMapping("feign/getData") String getData(){ return myspringbootFeign.getData(); }   整体效果如下   如果accessToken失效了,这接口将无法访问,需要刷新重新登录   后记   sso单点登录就记录到这里,这里只是实现了单机版的sso,以后在进行升级吧。   问题报错 :我们在sso-server设置cookie后,在zuul-server的run方法里获取不到设置的cookie,去浏览器查看,cookie没有设置成功,Zuul丢失Cookie   解决方案:Zuul丢失Cookie的解决方案:https://blog.csdn.net/lindan1984/article/details/79308396  补充   2019-06-25补充 :不知道大家发现没有,我们之前在Zuul过滤器获取访问路径用的是String url = request.getRequestURL().toString();,这样获取有一个问题,那就是如果url后面有参数(?username=aaa&password=123),这样获取就会丢失这些参数,先给大家演示一下   访问:http://localhost:10010/sso-server/sso/redis/hasKey?username=aaa&pasword=123   跳转登录页面,参数url已经丢失了原先的参数?username=aaa&password=123:http://localhost:10010/sso-server/sso/loginPage?url=http://localhost:10010/sso-server/sso/redis/hasKey   因此我们需要在重定向之前对get请求 的参数进行处理,run方法获取url后还需要设置参数,其他的请求 则直接跳转首页或者固定页面即可 /** * 过滤器的具体逻辑 */ @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); HttpServletResponse response = ctx.getResponse(); //访问路径 StringBuilder url = new StringBuilder(request.getRequestURL().toString()); //从cookie里面取值(Zuul丢失Cookie的解决方案:https://blog.csdn.net/lindan1984/article/details/79308396) String accessToken = request.getParameter("accessToken"); Cookie[] cookies = request.getCookies(); if (null != cookies) { for (Cookie cookie : cookies) { if ("accessToken".equals(cookie.getName())) { accessToken = cookie.getValue(); } } } //过滤规则: //访问的是登录页面、登录请求则放行 if (url.toString().contains("sso-server/sso/loginPage") || url.toString().contains("sso-server/sso/login") || //cookie有令牌且存在于Redis (!StringUtils.isEmpty(accessToken) && ssoFeign.hasKey(accessToken)) ) { ctx.setSendZuulResponse(true); ctx.setResponseStatusCode(200); return null; } else { ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); //如果是get请求处理参数,其他请求统统跳转到首页 String method = request.getMethod(); if("GET".equals(method)){ url.append("?"); Map parameterMap = request.getParameterMap(); Object[] keys = parameterMap.keySet().toArray(); for (int i = 0; i < keys.length; i++) { String key = (String) keys[i]; String value = parameterMap.get(key)[0]; url.append(key).append("=").append(value).append("&"); } //处理末尾的&符合 url.delete(url.length() -1,url.length()); }else{ //首页链接,或者其他固定页面 url = new StringBuilder("XXX"); } //重定向到登录页面 try { response.sendRedirect("http://localhost:10010/sso-server/sso/loginPage?url=" + url); } catch (IOException e) { e.printStackTrace(); } return null; } }   给大家看一下改动后的效果   如果是其他请求   PS:其实这样响应处理一点都不友好,应该做如下约定:后端响应特定状态码(例如:301)时,同时会响应对应的url链接(例如系统首页链接),前端发起post、delete请求等需要进行判断,然后在js进行页面跳转,这样的话用户的体验会更好,系统更加健全  代码开源   代码已经开源、托管到我的GitHub、码云:   GitHub:https://github.com/huanzi-qch/springCloud   码云:https://gitee.com/huanzi-qch/springCloud   版权声明   作者:huanzi-qch   出处:https://www.cnblogs.com/huanzi-qch   若标题中有"转载"字样,则本文版权归原作者所有。若无转载字样,本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利.

宁城县五化镇苹果产业带富一方百姓金秋时节,走进风景秀丽景色宜人的五化镇,红彤彤的苹果金灿灿的梨子随风摇曳,浓浓的果香扑面而来,沁人心脾。宁城县五化镇具有得天独厚的气候条件,是远近闻名的苹果产地。在马营子村的果林里夜游锦江又上新了!国庆去看这5个不可错过的艺术装置国庆假期即将到来,锦江公园又上新了!9月23日,红星新闻记者从成都锦江绿道集团公司获悉,作为全新的蓉城夜游场景夜游锦江沿线又新增了多处艺术装置,文化感艺术感氛围拉满。还有成都人最爱河南济源乡村旅游出彩出新本报记者黄红立通讯员崔斌张化平济源山水好!有诗有乡愁,有景有远方。行走济源,秋色满目,无论到全国唯一一条直通首都北京的太行山国家森林步道徒步,还是到中原海拔第一村水洪池村观云海里的开学季丨走,带你去江汉路逛逛武汉,一座有着大学之城之称的城市。这里,能够满足来自五湖四海的你所有多变的需求。武汉的百万大学生们,准备好迎接新学期了吗?作为一只万能的江豚,我,小浪花!为了开学季的仪式感,特意奉青岛对不起,潍坊烟台,环湾发展决定了我必须把机场修在胶州一种观点认为,青岛把机场放在胶东,白白增加通勤时间成本。原来的流亭机场,早可以赶7点的飞机,晚可以半夜130到达,开车30分钟回市内,一点也不耽误休息。新机场放红岛也比胶州好得多,首秀!2022年荆门园博园首届夜游文化节10月1日起盛大开幕!门票免费送!喜迎国庆,荆门游礼打造夜经济生态圈10月1日7日2022年荆门园博园首届夜游文化节盛大开幕超震撼视觉超狂欢表演超丰富体验超一流的夜游体验2022年国庆荆门园博园注定被瞩目2022年走进蔡正街,带你打开蔡甸老街回忆录每个人记忆里都有一条老街。四季流转,时光更迭,曾经熙熙攘攘的老街大多繁华不再,但老街里那些年代久远的建筑依然散发着鲜活的烟火气。提起蔡正街,老蔡甸人都知道。这个最具老蔡甸特征的区域武汉天兴洲好不好玩?怎样上天兴洲?这里是武汉人的阿拉善大漠英雄会,是秋风阵阵的瓜洲渡,是五颜六色的翡翠湖,是高楼林立的城市之岛,背景是长龙卧波的长江大桥还可以尽量想象一一这里是武汉长江中间的一个小岛一一天兴洲。天兴宝鸡巨资复刻古代景区,风头一时无两,如今陷入经营困境陕西省是我国的旅游大省,地域优势明显,有著名的兵马俑,大唐不夜城,华清池等众多热门景区。西安旅游热度不断上升,景区数量不断增加。这些景点有些受到了旅游者的青睐,但也有部分小众景区却我死后中国会是什么样子?邓小平回答后,毛主席英雄所见略同1973年2月,邓小平被任命为国务院副总理。在此期间,一次,毛主席在与众人谈话过程中,忽然问出了这样一个尖锐且敏感的问题我死后,中国会是什么样子?听到毛主席这样问,在场众人先是一愣艺考校长玩弄上百个女学生其中还有未成年少女娱乐圈到底有多黑?作者曾阳清娱乐圈的腐败和丑恶现象,特别是男女关系,我想如果真的完全暴露出来的,真的是恶臭到极点,是能够毁人三观的。最近发生了两件大新闻,都和娱乐圈有关,掀起了极大的舆论风暴,但是令
出现这些情况要及时就诊!咳嗽一周孩子扛成白肺来源新晚报抱着侥幸心理一直在家硬扛,没想到扛出这么重的病!12岁周周(化名)的家长后悔不已之前因为不太重视加上怕去医院12岁男孩咳嗽一周不就医结果再来到医院时检查发现一侧肺部已经成四连板新华百货去年第四季度受临时停业及其他不利因素影响经营情况未见改观1月4日,新华百货高开高走涨停报收于22。43元,为连续4个交易日涨停。1月4日晚间,新华百货发布风险提示公告称,1月4日,公司股票日换手率为1。45,静态市盈率98。35,动态市如果父母只对孩子履行抚养义务不倾注任何情况会怎么样?我在网上看到很多抱怨父母生活费给的不够,各种辱骂,甚至还有认为父母抚养孩子是法定义务,所以父母的爱都是虚假的,根本是想道德绑架孩子怎么样怎么样的言论,听的让人真的很愤怒。我不知道这中医提醒脾胃受损影响健康,想要健脾养胃,务必做好这3点!脾胃虚弱是一种中医说法,主要包括脾气虚脾阳虚,以及胃阳虚和胃气虚等多种类型,其中脾气虚最为多见。而一旦出现脾胃虚弱现象,会严重影响到人体的正常消化功能,还会使得运化功能失调,很容易上热下寒,中焦淤堵,中医怎么打通中焦?上热下寒,中焦淤堵,中医怎么打通中焦?一到冬季,各大养生号都建议大家在冬令进补,温阳补养,于是很多人入冬后就开始吃好的,补好的。然而,总有人说,为什么每次我补了以后口腔长溃疡脸上冒今年过年羽绒服流行穿长不穿短,保暖显气质,小个子也能穿羽绒服已经是大家每年冬天都必买的一件单品了,因为有着很好的保暖效果,再加上款式什么的也非常的多,很受人们的喜欢,而之前很流行短款的版型,今年你就可以尝试长款的版型了,比短款更合适。62岁郎平变胖了!一家5口美国跨年,饭菜清淡,71岁教授老公健硕中国的综合国力不断上升,体育事业也在蓬勃发展,像乒乓球举重跳水等强项一直保持着优势,而在田径冰雪项目等短板上也在不断加强,辉煌的体育历史中也诞生了很多伟大的体育人物,外界常常会评出围炉烤火冬天该有的模样围炉煮茶,昏昏灯火话平生围炉而坐,慢火煮茶,感受人间至味是清欢。郁达夫说凡在北国过过冬天的人,总都会围炉煮茗,或吃煊羊肉剥花生米饮白干。其实在南方,亦有此景。围炉煮茶,烤红薯,烤瓜NBA本赛季5大2人组!杜欧力压字母哥排第2,谁是第一名?NBA是一个超级球星驱动的联盟,而随着联盟的不断进步,今年的NBA,更是满天繁星涌现了许许多多的青年球星,在过去多年来从未像现在这样明显。随着如今NBA人才的广泛涌现,拥有全明星级男性机器人问世,女性体验后嫌弃男友?为什么呢?不得不说,性别为男的机器人一上市,让无数女性顾客爱不释手,你知道为什么吗?(此处已添加小程序,请到今日头条客户端查看)甚至有些女性用户直言它比正牌男友好用!只从外表看得话,男机器人这3种天然青霉素食物,隔三差五吃一次,身体健康过一冬在寒冷的冬季,病菌的繁殖和传播速度都加快了,特别是现今这个特殊时期,更不容大家松懈,外部的防护和消杀不能忘。为了抵御疾病,大家不仅要多加运动,适当多吃一些能增强抵抗力的食物也是很有