前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >微服务之单点登录 SSO 详解

微服务之单点登录 SSO 详解

作者头像
Freedom123
发布2024-03-29 10:44:18
1300
发布2024-03-29 10:44:18
举报
文章被收录于专栏:DevOpsDevOps

简介

较大的企业内部,一般都有很多的业务支持系统为其提供相应的管理和 IT 服务。通常来说,每个单独的系统都会有自己的安全体系和身份认证系统。进入每个系统都需要进行登录,这样的局面不仅给管理上带来了很大的困难,对客户来说也极不友好。那么如何让客户只需登陆一次,就可以进入多个系统,而不需要重新登录呢。“单点登录”就是专为解决此类问题的。其大致思想流程如下:通过一个 ticket 进行串接各系统间的用户信息

实现思路

业务服务设计

在每一个需要身份认证的服务中,定义一个SSOFilter用于拦截非登录请求。对于每个拦截的请求,会先从当前请求的Session中确认是否能够拿到用户信息,拿不到用户信息又会确认当前请求中是否携带ticket票据这个参数,如果携带就会尝试从Redis中根据该票据拿到用户信息。如果最终都获取不到用户信息就会被重定向到SSO登录服务的登录页面进行登录处理

代码语言:javascript
复制
    private RedisTemplate redisTemplate;

    public static final String USER_INFO = "user";

    public SSOFilter(RedisTemplate redisTemplate){
        this.redisTemplate = redisTemplate;
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest,
                         ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;

        Object userInfo = request.getSession().getAttribute(USER_INFO);;

        //如果未登陆,则拒绝请求,转向登陆页面
        String requestUrl = request.getServletPath();
        if (!"/toLogin".equals(requestUrl)
                && !requestUrl.startsWith("/login")
                && null == userInfo) {

            String ticket = request.getParameter("ticket");
            //有票据,则使用票据去尝试拿取用户信息
            if (null != ticket){
                userInfo = redisTemplate.opsForValue().get(ticket);
            }
            //无法得到用户信息,则去CAS服务的登陆页面
            if (null == userInfo){
                response.sendRedirect("http://cas.com:8080/toLogin?url="+request.getRequestURL().toString());
                return ;
            }

            /**
             * 将用户信息,加载进session中
             */
            request.getSession().setAttribute(SSOFilter.USER_INFO,userInfo);
            //登录成功需要将ticket从redis中删除
            redisTemplate.delete(ticket);
        }

        filterChain.doFilter(request,servletResponse);
    }

    @Override
    public void destroy() {

    }

}
SSO服务设计

在SSO登录服务中,只需要简单定义一个Filter,进行拦截非登录请求,然后确认当前请求的Session中是否能够拿到用户信息,如果能拿到用户信息,那么就是登录状态,否则,认定当前请求无效,将请求转发到登录页面即可

代码语言:javascript
复制
    public static final String USER_INFO = "user";
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest,
                         ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;

        Object userInfo = request.getSession().getAttribute(USER_INFO);;

       //非登陆页面并且不是登陆状态
        String requestUrl = request.getServletPath();
        if (!"/toLogin".equals(requestUrl)
                && !requestUrl.startsWith("/login")
                && null == userInfo) {
            //则拒绝当前请求,请求转发到登陆页面 
            request.getRequestDispatcher("/toLogin").forward(request,response);
            return ;
        }

        filterChain.doFilter(request,servletResponse);
    }

    @Override
    public void destroy() {

    }

}

在SSO登录处理过程中,当请求已认证登录成功后,会先生成一个ticket票据,并将ticket票据和用户信息存放到Redis中,然后重定向回原先请求服务的Url,并携带上ticket票据参数

代码语言:javascript
复制
public class IndexController {
    @Autowired
    private RedisTemplate redisTemplate;

    @GetMapping("/toLogin")
    public String toLogin(Model model,HttpServletRequest request) {
        Object userInfo = request.getSession().getAttribute(LoginFilter.USER_INFO);
        //不为空,则是已登陆状态
        if (null != userInfo){
            String ticket = UUID.randomUUID().toString();
            redisTemplate.opsForValue().set(ticket,userInfo,2, TimeUnit.SECONDS);
            return "redirect:"+request.getParameter("url")+"?ticket="+ticket;
        }
        UserForm user = new UserForm();
        user.setUsername("username");
        user.setPassword("password");
        user.setBackurl(request.getParameter("url"));
        model.addAttribute("user", user);

        return "login";
    }

    @PostMapping("/login")
    public void login(@ModelAttribute UserForm user,HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException {
        request.getSession().setAttribute(LoginFilter.USER_INFO,user);

        //登陆成功,创建用户信息票据
        String ticket = UUID.randomUUID().toString();
        //将ticket和用户信息写入到redis中
        redisTemplate.opsForValue().set(ticket,user,20, TimeUnit.SECONDS);
        //重定向,回原请求的url,并携带ticket信息
        if (null == user.getBackurl() || user.getBackurl().length()==0){
            response.sendRedirect("/index");
        } else {
            response.sendRedirect(user.getBackurl()+"?ticket="+ticket);
        }
    }

    @GetMapping("/index")
    public ModelAndView index(HttpServletRequest request) {
        ModelAndView modelAndView = new ModelAndView();

        modelAndView.setViewName("index");
        modelAndView.addObject("user", request.getSession().getAttribute(LoginFilter.USER_INFO));

        request.getSession().setAttribute("test","123");
        return modelAndView;
    }
}
单点登录流程

1.用户访问服务A某个页面时,服务A发现自己未登录,重定向到CAS单点登录服务,CAS服务也发现未登录,则跳转到相应的登录页面 2.用户输入用户名和密码登录成功后,CAS服务进行认证,将登录状态记录CAS服务的session中,并写入当前CAS服务域名下的Cookie中 3.CAS服务登录完成后会生成一个Ticket票据,并将Ticket和用户信息记录到Redis中,之后再重定向回服务A,同时将Ticket作为参数传递给服务A 4.服务A拿到Ticket后,从Redis中进行查找,查询Ticket对应的用户信息,之后服务A再将登录状态写入session并设置到服务A域名下的Cookie中 5.至此,单点登录就完成了,之后再访问服务A时,服务A就是登录状态的 6.当有一个新的服务B被用户访问时,服务B发现自己也未登录,此时也重定向到CAS单点登录服务,但是此时CAS服务发现已经登录了,此时就不需要进行登录认证 7.CAS服务会生成一个Ticket票据,并将Ticket和用户信息记录到Redis中,之后再重定向回服务B,同时将Ticket作为参数传递给服务B 8.服务B拿到Ticket后,从Redis中进行查找,查询Ticket对应的用户信息,之后服务B再将登录状态写入session并设置到服务B域名下的Cookie中 9.因此服务B不需要进行登录过程,就能完成用户登录认证

参考

https://blog.csdn.net/wzljiayou/article/details/109550565 ***** https://www.cnblogs.com/zh94/p/8352943.html *** 没看,觉得不错

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-03-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • 实现思路
    • 业务服务设计
      • SSO服务设计
        • 单点登录流程
        • 参考
        相关产品与服务
        云数据库 Redis
        腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档