自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

告別繁瑣操作,實現(xiàn)一次登錄產品互通

開發(fā) 前端
一個公司產品矩陣比較豐富的時候,用戶在不同系統(tǒng)之間來回切換,固然對產品用戶體驗上較差,并且增加用戶密碼管理成本。也沒有很好地利用內部流量進行用戶打通,并且每個產品的獨立體系會導致產品安全度下降。

最近開發(fā)新產品,然后老板說我們現(xiàn)在系統(tǒng)太多了,每次切換系統(tǒng)登錄太麻煩了,能不能做個優(yōu)化,同一賬號互通掉。作為一個資深架構獅,老板的要求肯定要滿足,安排!

一個公司產品矩陣比較豐富的時候,用戶在不同系統(tǒng)之間來回切換,固然對產品用戶體驗上較差,并且增加用戶密碼管理成本。也沒有很好地利用內部流量進行用戶打通,并且每個產品的獨立體系會導致產品安全度下降。

因此實現(xiàn)集團產品的單點登錄對用戶使用體驗以及效率提升有很大的幫助。那么如何實現(xiàn)統(tǒng)一認證呢?我們先了解一下傳統(tǒng)的身份驗證方式。

1 傳統(tǒng)Session機制及身份認證方案

1.1 Cookie與服務器的交互

圖片圖片

眾所周知,http是無狀態(tài)的協(xié)議,因此客戶每次通過瀏覽器訪問web頁面,請求到服務端時,服務器都會新建線程,打開新的會話,而且服務器也不會自動維護客戶的上下文信息。比如我們現(xiàn)在要實現(xiàn)一個電商內的購物車功能,要怎么才能知道哪些購物車請求對應的是來自同一個客戶的請求呢?

圖片圖片

因此出現(xiàn)了session這個概念,session 就是一種保存上下文信息的機制,他是面向用戶的,每一個SessionID 對應著一個用戶,并且保存在服務端中。session主要以cookie 或 URL 重寫為基礎的來實現(xiàn)的,默認使用 cookie 來實現(xiàn),系統(tǒng)會創(chuàng)造一個名為JSESSIONID的變量輸出到cookie中。

JSESSIONID 是存儲于瀏覽器內存中的,并不是寫到硬盤上的,如果我們把瀏覽器的cookie 禁止,則 web 服務器會采用 URL 重寫的方式傳遞 Sessionid,我們就可以在地址欄看到 sessinotallow=KWJHUG6JJM65HS2K6 之類的字符串。

通常 JSESSIONID 是不能跨窗口使用的,當你新開了一個瀏覽器窗口進入相同頁面時,系統(tǒng)會賦予你一個新的sessionid,這樣我們信息共享的目的就達不到了。

1.2 服務器端的session的機制

當服務端收到客戶端的請求時候,首先判斷請求里是否包含了JSESSIONID的sessionId,如果存在說明已經創(chuàng)建過了,直接從內存中拿出來使用,如果查詢不到,說明是無效的。

如果客戶請求不包含sessionid,則為此客戶創(chuàng)建一個session并且生成一個與此session相關聯(lián)的sessionid,這個sessionid將在本次響應中返回給客戶端保存。

對每次http請求,都經歷以下步驟處理:

  • 服務端首先查找對應的cookie的值(sessionid)。
  • 根據sessionid,從服務器端session存儲中獲取對應id的session數據,進行返回。
  • 如果找不到sessionid,服務器端就創(chuàng)建session,生成sessionid對應的cookie,寫入到響應頭中。

session是由服務端生成的,并且以散列表的形式保存在內存中

1.3 基于 session 的身份認證流程

基于seesion的身份認證主要流程如下:

圖片圖片

因為 http 請求是無狀態(tài)請求,所以在 Web 領域,大部分都是通過這種方式解決。但是這么做有什么問題呢?我們接著看

2 集群環(huán)境下的 Session 困境及解決方案

圖片圖片

隨著技術的發(fā)展,用戶流量增大,單個服務器已經不能滿足系統(tǒng)的需要了,分布式架構開始流行。通常都會把系統(tǒng)部署在多臺服務器上,通過負載均衡把請求分發(fā)到其中的一臺服務器上,這樣很可能同一個用戶的請求被分發(fā)到不同的服務器上,因為 session 是保存在服務器上的,那么很有可能第一次請求訪問的 A 服務器,創(chuàng)建了 session,但是第二次訪問到了 B 服務器,這時就會出現(xiàn)取不到 session 的情況。

我們知道,Session 一般是用來存會話全局的用戶信息(不僅僅是登陸方面的問題),用來簡化/加速后續(xù)的業(yè)務請求。

傳統(tǒng)的 session 由服務器端生成并存儲,當應用進行分布式集群部署的時候,如何保證不同服務器上 session 信息能夠共享呢?

2.1 Session共享方案

Session共享一般有兩種思路

  • session復制
  • session集中存儲
2.1.1 session復制

session復制即將不同服務器上 session 數據進行復制,用戶登錄,修改,注銷時,將session信息同時也復制到其他機器上面去

圖片圖片

這種實現(xiàn)的問題就是實現(xiàn)成本高,維護難度大,并且會存在延遲登問題。

2.1.2 session集中存儲

圖片圖片

集中存儲就是將獲取session單獨放在一個服務中進行存儲,所有獲取session的統(tǒng)一來這個服務中去取。這樣就避免了同步和維護多套session的問題。一般我們都是使用redis進行集中式存儲session。

3 多服務下的登陸困境及SSO方案

3.1 SSO的產生背景

圖片圖片

如果企業(yè)做大了之后,一般都有很多的業(yè)務支持系統(tǒng)為其提供相應的管理和 IT 服務,按照傳統(tǒng)的驗證方式訪問多系統(tǒng),每個單獨的系統(tǒng)都會有自己的安全體系和身份認證系統(tǒng)。進入每個系統(tǒng)都需要進行登錄,獲取session,再通過session訪問對應系統(tǒng)資源。

這樣的局面不僅給管理上帶來了很大的困難,對客戶來說也極不友好,那么如何讓客戶只需登陸一次,就可以進入多個系統(tǒng),而不需要重新登錄呢?

圖片圖片

“單點登錄”就是專為解決此類問題的。其大致思想流程如下:通過一個 ticket 進行串接各系統(tǒng)間的用戶信息

3.2 SSO的底層原理 CAS

3.2.1 CAS實現(xiàn)單點登錄流程

我們知道對于完全不同域名的系統(tǒng),cookie 是無法跨域名共享的,因此 sessionId 在頁面端也無法共享,因此需要實現(xiàn)單店登錄,就需要啟用一個專門用來登錄的域名如(ouath.com)來提供所有系統(tǒng)的sessionId。當業(yè)務系統(tǒng)被打開時,借助中心授權系統(tǒng)進行登錄,整體流程如下:

  1. 當b.com打開時,發(fā)現(xiàn)自己未登陸,于是跳轉到ouath.com去登陸
  2. ouath.com登陸頁面被打開,用戶輸入帳戶/密碼登陸成功
  3. ouath.com登陸成功,種 cookie 到ouath.com域名下
  4. 把 sessionid 放入后臺redis,存放<ticket,sesssionid>數據結構,然后頁面重定向到A系統(tǒng)
  5. 當b.com重新被打開,發(fā)現(xiàn)仍然是未登陸,但是有了一個 ticket值
  6. 當b.com用ticket 值,到 redis 里查到 sessionid,并做 session 同步,然后種cookie給自己,頁面原地重定向
  7. 當b.com打開自己頁面,此時有了 cookie,后臺校驗登陸狀態(tài),成功

整個交互流程圖如下:

圖片圖片

3.2.2 單點登錄流程演示

3.2.2.1 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);
    //不為空,則是已登陸狀態(tài)
    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);

    //登陸成功,創(chuàng)建用戶信息票據
    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)//不是登陸頁面
            && !requestUrl.startsWith("/login")//不是去登陸
            && null == userInfo) {//不是登陸狀態(tài)

        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>

3.2.2.2 web系統(tǒng)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)//不是登陸頁面
            && !requestUrl.startsWith("/login")//不是去登陸
            && null == userInfo) {//不是登陸狀態(tài)

        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>

3.2.3 CAS的單點登錄和OAuth2的區(qū)別

OAuth2: 三方授權協(xié)議,允許用戶在不提供賬號密碼的情況下,通過信任的應用進行授權,使其客戶端可以訪問權限范圍內的資源。

CAS: 中央認證服務(Central Authentication Service),一個基于Kerberos票據方式實現(xiàn)SSO單點登錄的框架,為Web 應用系統(tǒng)提供一種可靠的單點登錄解決方法(屬于 Web SSO )。

  • CAS的單點登錄時保障客戶端的用戶資源的安全,OAuth2則是保障服務端的用戶資源的安全 。
  • CAS客戶端要獲取的最終信息是,這個用戶到底有沒有權限訪問我(CAS客戶端)的資源。OAuth2獲取的最終信息是,我(oauth2服務提供方)的用戶的資源到底能不能讓你(oauth2的客戶端)訪問。

因此,需要統(tǒng)一的賬號密碼進行身份認證,用CAS;需要授權第三方服務使用我方資源,使用OAuth2;

責任編輯:武曉燕 來源: 一安未來
相關推薦

2024-10-16 16:30:14

ShellShell腳本

2023-09-13 15:09:35

軟件開發(fā)數字化進程

2024-03-11 06:00:00

OptunaPython超參數優(yōu)化

2012-11-03 17:05:30

Windows 8開發(fā)微軟

2011-06-28 10:41:50

DBA

2025-01-24 07:44:31

LinuxsystemdAnsible

2021-12-27 10:08:16

Python編程語言

2020-10-24 13:50:59

Python編程語言

2024-08-23 08:50:51

監(jiān)控Python開發(fā)

2025-04-27 03:00:00

Spring集成測試

2020-10-18 12:53:29

黑科技網站軟件

2020-03-18 13:07:16

華為

2017-01-23 12:40:45

設計演講報表數據

2020-03-10 07:51:35

面試諷刺標準

2017-02-28 11:13:36

華為

2013-02-01 14:58:44

Android開發(fā)退出程序

2014-03-06 15:16:18

安全管理linux安全

2012-08-28 09:21:59

Ajax查錯經歷Web
點贊
收藏

51CTO技術棧公眾號