SpringCloud实现SSO 单点登录的示例代码

时间:2021-05-19

前言

作为分布式项目,单点登录是必不可少的,文本基于之前的的博客(猛戳: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

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency><!DOCTYPE html><html xmlns:th="http://"); //设置访问路径 cookie.setPath("/"); response.addCookie(cookie); //重定向到原先访问的页面 response.sendRedirect(url); } catch (IOException e) { e.printStackTrace(); } return null; } return "登录失败"; }}

zuul-server

引入feign,用于调用sso-server服务

<!-- feign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>

创建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@SpringBootApplicationpublic 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"); for (Cookie cookie : request.getCookies()) { 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-serverzuul.host.socket-timeout-millis=60000zuul.host.connect-timeout-millis=10000#Zuul丢失Cookie的解决方案:https://blog.csdn.net/lindan1984/article/details/79308396zuul.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失效,需要从新登录

后记

sso单点登录就记录到这里,这里只是实现了单机版的sso,以后在进行升级吧。

问题报错:我们在sso-server设置cookie后,在zuul-server的run方法里获取不到设置的cookie,去浏览器查看,cookie没有设置成功,Zuul丢失Cookie

解决方案

我们是使用spring cloud zuul作为api-gateway实践中,发现默认zuul会过滤掉cookie等header信息,有些业务场景需要传递这些信息该怎么处理呢?

处理方式 在api-gateway的application.properties文件中添加 zuul.sensitive-headers=

问题原因

负责根据ServiceId来路由的RibbonRoutingFilter在route之前会调用ProxyRequestHelper的buildZuulRequestHeaders(request)来重新组装一个新的Header。

在buildZuulRequestHeaders方法中会对RequsetHeader中的每一项调用isIncludedHeader(name)来判断当前项是否应该留在新的Header中,如下图,如果当前项在IGNORED_HEADERS(需要忽略的信息)中,就不会在新header中保留。

PreDecorationFilter过滤器会调用ProxyRequestHelper的addIgnoredHeaders方法把敏感信息(ZuulProperties的sensitiveHeaders属性)添加到请求上下文的IGNORED_HEADERS中

sensitiveHeaders的默认值初始值是"Cookie", "Set-Cookie", "Authorization"这三项,可以看到Cookie被列为了敏感信息,所以不会放到新header中

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。

相关文章