老板:公司系统太多,能不能实现账号互通?(二)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
.cn 域名,1个 12个月
简介: 老板:公司系统太多,能不能实现账号互通?(二)

SSO 的底层原理 CAS

①CAS 实现单点登录流程

我们知道对于完全不同域名的系统,cookie 是无法跨域名共享的,因此 sessionId 在页面端也无法共享,因此需要实现单店登录,就需要启用一个专门用来登录的域名如(ouath.com)来提供所有系统的 sessionId。

当业务系统被打开时,借助中心授权系统进行登录,整体流程如下:

  • 当 b.com 打开时,发现自己未登陆,于是跳转到 ouath.com 去登陆
  • ouath.com 登陆页面被打开,用户输入帐户/密码登陆成功
  • ouath.com 登陆成功,种 cookie 到 ouath.com 域名下
  • 把 sessionid 放入后台 redis,存放<ticket,sesssionid>数据结构,然后页面重定向到 A 系统
  • 当 b.com 重新被打开,发现仍然是未登陆,但是有了一个 ticket 值
  • 当 b.com 用 ticket 值,到 redis 里查到 sessionid,并做 session 同步,然后种 cookie 给自己,页面原地重定向
  • 当 b.com 打开自己页面,此时有了 cookie,后台校验登陆状态,成功

整个交互流程图如下:

微信图片_20220908103609.png

②单点登录流程演示

CAS 登录服务 demo 核心代码如下:

用户实体类:

public class UserForm implements Serializable{
private static final long serialVersionUID = 1L;
private String username;
private String password;
private String backurl;
public String getUsername() {
    return username;
}
public void setUsername(String username) {
    this.username = username;
}
public String getPassword() {
    return password;
}
public void setPassword(String password) {
    this.password = password;
}
public String getBackurl() {
    return backurl;
}
public void setBackurl(String backurl) {
    this.backurl = backurl;
}
}

登录控制器:

@Controller
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("laowang");
    user.setPassword("laowang");
    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 {
    System.out.println("backurl:"+user.getBackurl());
    request.getSession().setAttribute(LoginFilter.USER_INFO,user);
    //登陆成功,创建用户信息票据
    String ticket = UUID.randomUUID().toString();
    redisTemplate.opsForValue().set(ticket,user,20, TimeUnit.SECONDS);
    //重定向,回原url  ---a.com
    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();
    Object user = request.getSession().getAttribute(LoginFilter.USER_INFO);
    UserForm userInfo = (UserForm) user;
    modelAndView.setViewName("index");
    modelAndView.addObject("user", userInfo);
    request.getSession().setAttribute("test","123");
    return modelAndView;
}
}

登录过滤器:

public class LoginFilter implements Filter {
    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)//不是登陆页面
            &amp;&amp; !requestUrl.startsWith("/login")//不是去登陆
            &amp;&amp; null == userInfo) {//不是登陆状态
        request.getRequestDispatcher("/toLogin").forward(request,response);
        return ;
    }
    filterChain.doFilter(request,servletResponse);
}
@Override
public void destroy() {
}
}

配置过滤器:

@Configuration
public class LoginConfig {
//配置filter生效
@Bean
public FilterRegistrationBean sessionFilterRegistration() {
    FilterRegistrationBean registration = new FilterRegistrationBean();
    registration.setFilter(new LoginFilter());
    registration.addUrlPatterns("/*");
    registration.addInitParameter("paramName", "paramValue");
    registration.setName("sessionFilter");
    registration.setOrder(1);
    return registration;
}
}

登录页面:

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>enjoy login</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div text-align="center">
    <h1>请登陆</h1>
    <form action="#" th:action="@{/login}" th:object="${user}" method="post">
        <p>用户名: <input type="text" th:field="*{username}" /></p>
        <p>密  码: <input type="text" th:field="*{password}" /></p>
        <p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
        <input type="text" th:field="*{backurl}" hidden="hidden" />
    </form>
</div>
</body>
</html>
web 系统 demo 核心代码如下:

过滤器:

public class SSOFilter implements Filter {
    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)//不是登陆页面
            &amp;&amp; !requestUrl.startsWith("/login")//不是去登陆
            &amp;&amp; null == userInfo) {//不是登陆状态
        String ticket = request.getParameter("ticket");
        //有票据,则使用票据去尝试拿取用户信息
        if (null != ticket){
            userInfo = redisTemplate.opsForValue().get(ticket);
        }
        //无法得到用户信息,则去登陆页面
        if (null == userInfo){
            response.sendRedirect("http://127.0.0.1:8080/toLogin?url="+request.getRequestURL().toString());
            return ;
        }
        /**
         * 将用户信息,加载进session中
         */
        UserForm user = (UserForm) userInfo;
        request.getSession().setAttribute(SSOFilter.USER_INFO,user);
        redisTemplate.delete(ticket);
    }
    filterChain.doFilter(request,servletResponse);
}
@Override
public void destroy() {
}
}

控制器:

@Controller
public class IndexController {
    @Autowired
    private RedisTemplate redisTemplate;
@GetMapping("/index")
public ModelAndView index(HttpServletRequest request) {
    ModelAndView modelAndView = new ModelAndView();
    Object userInfo = request.getSession().getAttribute(SSOFilter.USER_INFO);
    UserForm user = (UserForm) userInfo;
    modelAndView.setViewName("index");
    modelAndView.addObject("user", user);
    request.getSession().setAttribute("test","123");
    return modelAndView;
}
}

首页:

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>enjoy index</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div th:object="${user}">
    <h1>cas-website:欢迎你"></h1>
</div>
</body>
</html>

③CAS 的单点登录和 OAuth2 的区别

OAuth2: 三方授权协议,允许用户在不提供账号密码的情况下,通过信任的应用进行授权,使其客户端可以访问权限范围内的资源。

CAS: 中央认证服务(Central Authentication Service),一个基于 Kerberos 票据方式实现 SSO 单点登录的框架,为 Web 应用系统提供一种可靠的单点登录解决方法(属于 Web SSO )。

CAS 的单点登录时保障客户端的用户资源的安全 ;OAuth2 则是保障服务端的用户资源的安全 。

CAS 客户端要获取的最终信息是,这个用户到底有没有权限访问我(CAS 客户端)的资源;OAuth2 获取的最终信息是,我(oauth2 服务提供方)的用户的资源到底能不能让你(oauth2 的客户端)访问。

因此,需要统一的账号密码进行身份认证,用 CAS;需要授权第三方服务使用我方资源,使用 OAuth2。

好了,不知道大家对 SSO 是否有了更深刻的理解,欢迎留言。

相关文章
|
安全 关系型数据库 MySQL
公司内部搭建企业论坛,并实现在外也可以访问
公司内部搭建企业论坛,并实现在外也可以访问
186 0
|
网络协议 数据安全/隐私保护 Windows
当不在公司时,如何在外远程登录公司内网OA系统?
当不在公司时,如何在外远程登录公司内网OA系统?
323 0
|
6月前
希望阿里的小伙伴在控制台的易用性多上点心,每次问客服好像都是外包人员,啥也不会
希望阿里的小伙伴在控制台的易用性多上点心,每次问客服好像都是外包人员,啥也不会
142 2
|
算法
机房合作-策略模式实现用户充值
机房合作-策略模式实现用户充值
69 1
|
网络协议 应用服务中间件 数据库
如何在出差期间远程访问企业ERP系统?内网穿透解决您的难题!
如何在出差期间远程访问企业ERP系统?内网穿透解决您的难题!
171 0
|
网络协议 应用服务中间件 数据库
当你出差在外时,怎样轻松访问远程访问企业局域网象过河ERP系统?
当你出差在外时,怎样轻松访问远程访问企业局域网象过河ERP系统?
101 0
|
SQL JSON 前端开发
校园外卖点餐系统——Day02【员工管理业务开发】
校园外卖点餐系统——Day02【员工管理业务开发】
149 0
校园外卖点餐系统——Day02【员工管理业务开发】
|
存储 消息中间件 安全
老板:公司系统太多,能不能实现账号互通?(一)
最近开发新产品,然后老板说我们现在系统太多了,每次切换系统登录太麻烦了,能不能做个优化,同一账号互通掉。作为一个资深架构狮,老板的要求肯定要满足,安排!
老板:公司系统太多,能不能实现账号互通?(一)
好好编程-物流项目20【客户管理-删除客户】
好好编程-物流项目20【客户管理-删除客户】
好好编程-物流项目20【客户管理-删除客户】