SpringCloud之SSO 单点登录(附源码)

点击上方[全栈开发者社区]右上角[...][设为星标⭐]

640?

♪ 点击上方绿标 收听SpringCloud之SSO 单点登录

sso单点登录思路:

1、访问分布式系统的任意请求,被ZuulFilter拦截过滤

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,正常项目的令牌应该要更为复杂)存到Redisvalue值存用户id作为value(或者直接存储可暴露的部分用户信息也行)设置过期时间(我这里设置3分钟);设置cookienew Cookie("accessToken",accessToken);,设置maxAge(60*3);path("/");

5sso-server单点登录服务负责校验用户信息、获取用户信息、操作Redis缓存,提供接口,在eureka上注册


sso-server

  首先我们创建一个单点登录服务sso-server,并在eureka上注册

640?wx_fmt=png

login.html

  我们这里需要用到页面,要先maven引入thymeleaf

 
 
<dependency>	
            <groupId>org.springframework.boot</groupId>	
            <artifactId>spring-boot-starter-thymeleaf</artifactId>	
        </dependency>
 
 
<!DOCTYPE html>	
<html xmlns:th="http://www.thymeleaf.org">	
<head>	
    <meta charset="UTF-8">	
    <title>登录页面</title>	
</head>	
<body>	
    <form action="/sso-server/sso/login" method="post">	
        <input name="url" type="hidden" th:value="${url}"/>	
        用户名:<input name="username" type="text"/>	
        密码:<input name="password" type="password"/>	
        <input value="登录" type="submit"/>	
    </form>	
</body>	
</html>
 
 
@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服务

 
 
 <!-- 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	
@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=

测试效果

  启动eurekazuul-serversso-serverconfig-servermyspringbootspringdatajpa(由两个应用组成,实现了ribbon负载均衡),记得启动我们的RabbitMQ服务和Redis服务!

640?wx_fmt=gif

代码开源:

代码已经开源、托管到我的GitHub、码云:

GitHubhttps://github.com/huanzi-qch/springCloud

码云:https://gitee.com/huanzi-qch/springCloud

留言打卡第15天。

觉得本文对你有帮助?请分享给更多人

关注「全栈开发者社区」加星标,提升全栈技能


本公众号会不定期给大家发福利,包括送书、学习资源等,敬请期待吧!

如果感觉推送内容不错,不妨右下角点个在看转发朋友圈或收藏,感谢支持。


好文章,我在看❤️

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值