当前位置:首页 > 科技  > 软件

告别繁琐操作,实现一次登录产品互通

来源: 责编: 时间:2024-09-10 09:50:55 25观看
导读最近开发新产品,然后老板说我们现在系统太多了,每次切换系统登录太麻烦了,能不能做个优化,同一账号互通掉。作为一个资深架构狮,老板的要求肯定要满足,安排!一个公司产品矩阵比较丰富的时候,用户在不同系统之间来回切换,固然对

最近开发新产品,然后老板说我们现在系统太多了,每次切换系统登录太麻烦了,能不能做个优化,同一账号互通掉。作为一个资深架构狮,老板的要求肯定要满足,安排!mOd28资讯网——每日最新资讯28at.com

一个公司产品矩阵比较丰富的时候,用户在不同系统之间来回切换,固然对产品用户体验上较差,并且增加用户密码管理成本。也没有很好地利用内部流量进行用户打通,并且每个产品的独立体系会导致产品安全度下降。mOd28资讯网——每日最新资讯28at.com

因此实现集团产品的单点登录对用户使用体验以及效率提升有很大的帮助。那么如何实现统一认证呢?我们先了解一下传统的身份验证方式。mOd28资讯网——每日最新资讯28at.com

1 传统Session机制及身份认证方案

1.1 Cookie与服务器的交互

图片图片mOd28资讯网——每日最新资讯28at.com

众所周知,http是无状态的协议,因此客户每次通过浏览器访问web页面,请求到服务端时,服务器都会新建线程,打开新的会话,而且服务器也不会自动维护客户的上下文信息。比如我们现在要实现一个电商内的购物车功能,要怎么才能知道哪些购物车请求对应的是来自同一个客户的请求呢?mOd28资讯网——每日最新资讯28at.com

图片图片mOd28资讯网——每日最新资讯28at.com

因为 http 请求是无状态请求,所以在 Web 领域,大部分都是通过这种方式解决。但是这么做有什么问题呢?我们接着看mOd28资讯网——每日最新资讯28at.com

2 集群环境下的 Session 困境及解决方案

图片图片mOd28资讯网——每日最新资讯28at.com

随着技术的发展,用户流量增大,单个服务器已经不能满足系统的需要了,分布式架构开始流行。通常都会把系统部署在多台服务器上,通过负载均衡把请求分发到其中的一台服务器上,这样很可能同一个用户的请求被分发到不同的服务器上,因为 session 是保存在服务器上的,那么很有可能第一次请求访问的 A 服务器,创建了 session,但是第二次访问到了 B 服务器,这时就会出现取不到 session 的情况。mOd28资讯网——每日最新资讯28at.com

我们知道,Session 一般是用来存会话全局的用户信息(不仅仅是登陆方面的问题),用来简化/加速后续的业务请求。mOd28资讯网——每日最新资讯28at.com

传统的 session 由服务器端生成并存储,当应用进行分布式集群部署的时候,如何保证不同服务器上 session 信息能够共享呢?mOd28资讯网——每日最新资讯28at.com

2.1 Session共享方案

Session共享一般有两种思路mOd28资讯网——每日最新资讯28at.com

  • session复制
  • session集中存储
2.1.1 session复制

session复制即将不同服务器上 session 数据进行复制,用户登录,修改,注销时,将session信息同时也复制到其他机器上面去mOd28资讯网——每日最新资讯28at.com

图片图片mOd28资讯网——每日最新资讯28at.com

这种实现的问题就是实现成本高,维护难度大,并且会存在延迟登问题。mOd28资讯网——每日最新资讯28at.com

2.1.2 session集中存储

图片图片mOd28资讯网——每日最新资讯28at.com

集中存储就是将获取session单独放在一个服务中进行存储,所有获取session的统一来这个服务中去取。这样就避免了同步和维护多套session的问题。一般我们都是使用redis进行集中式存储session。mOd28资讯网——每日最新资讯28at.com

3 多服务下的登陆困境及SSO方案

3.1 SSO的产生背景

图片图片mOd28资讯网——每日最新资讯28at.com

如果企业做大了之后,一般都有很多的业务支持系统为其提供相应的管理和 IT 服务,按照传统的验证方式访问多系统,每个单独的系统都会有自己的安全体系和身份认证系统。进入每个系统都需要进行登录,获取session,再通过session访问对应系统资源。mOd28资讯网——每日最新资讯28at.com

这样的局面不仅给管理上带来了很大的困难,对客户来说也极不友好,那么如何让客户只需登陆一次,就可以进入多个系统,而不需要重新登录呢?mOd28资讯网——每日最新资讯28at.com

图片图片mOd28资讯网——每日最新资讯28at.com

“单点登录”就是专为解决此类问题的。其大致思想流程如下:通过一个 ticket 进行串接各系统间的用户信息mOd28资讯网——每日最新资讯28at.com

3.2 SSO的底层原理 CAS

3.2.1 CAS实现单点登录流程

我们知道对于完全不同域名的系统,cookie 是无法跨域名共享的,因此 sessionId 在页面端也无法共享,因此需要实现单店登录,就需要启用一个专门用来登录的域名如(ouath.com)来提供所有系统的sessionId。当业务系统被打开时,借助中心授权系统进行登录,整体流程如下:mOd28资讯网——每日最新资讯28at.com

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

整个交互流程图如下:mOd28资讯网——每日最新资讯28at.com

图片图片mOd28资讯网——每日最新资讯28at.com

3.2.2 单点登录流程演示

3.2.2.1 CAS登录服务demo核心代码mOd28资讯网——每日最新资讯28at.com

  • 用户实体类
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;}}
  • 登录控制器
@Controllerpublic 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 {}@Overridepublic 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);}@Overridepublic void destroy() {}}
  • 配置过滤器
@Configurationpublic class LoginConfig {//配置filter生效@Beanpublic 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>

3.2.2.2 web系统demo核心代码mOd28资讯网——每日最新资讯28at.com

  • 过滤器
public class SSOFilter implements Filter {    private RedisTemplate redisTemplate;public static final String USER_INFO = "user";public SSOFilter(RedisTemplate redisTemplate){    this.redisTemplate = redisTemplate;}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic 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);        }        //无法得到用户信息,则去登陆页面        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);}@Overridepublic void destroy() {}}
  • 控制器
@Controllerpublic 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>

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

OAuth2: 三方授权协议,允许用户在不提供账号密码的情况下,通过信任的应用进行授权,使其客户端可以访问权限范围内的资源。mOd28资讯网——每日最新资讯28at.com

CAS: 中央认证服务(Central Authentication Service),一个基于Kerberos票据方式实现SSO单点登录的框架,为Web 应用系统提供一种可靠的单点登录解决方法(属于 Web SSO )。mOd28资讯网——每日最新资讯28at.com

  • CAS的单点登录时保障客户端的用户资源的安全,OAuth2则是保障服务端的用户资源的安全 。
  • CAS客户端要获取的最终信息是,这个用户到底有没有权限访问我(CAS客户端)的资源。OAuth2获取的最终信息是,我(oauth2服务提供方)的用户的资源到底能不能让你(oauth2的客户端)访问。

因此,需要统一的账号密码进行身份认证,用CAS;需要授权第三方服务使用我方资源,使用OAuth2;mOd28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-112778-0.html告别繁琐操作,实现一次登录产品互通

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 定时任务数量爆炸?Netty教你如何应对百万级挑战

下一篇: PHP异步非阻塞MySQL客户端连接池

标签:
  • 热门焦点
Top