最近開發(fā)新產(chǎn)品,然后老板說我們現(xiàn)在系統(tǒng)太多了,每次切換系統(tǒng)登錄太麻煩了,能不能做個優(yōu)化,同一賬號互通掉。作為一個資深架構(gòu)獅,老板的要求肯定要滿足,安排!
一個公司產(chǎn)品矩陣比較豐富的時候,用戶在不同系統(tǒng)之間來回切換,固然對產(chǎn)品用戶體驗(yàn)上較差,并且增加用戶密碼管理成本。也沒有很好地利用內(nèi)部流量進(jìn)行用戶打通,并且每個產(chǎn)品的獨(dú)立體系會導(dǎo)致產(chǎn)品安全度下降。
因此實(shí)現(xiàn)集團(tuán)產(chǎn)品的單點(diǎn)登錄對用戶使用體驗(yàn)以及效率提升有很大的幫助。那么如何實(shí)現(xiàn)統(tǒng)一認(rèn)證呢?我們先了解一下傳統(tǒng)的身份驗(yàn)證方式。
圖片
眾所周知,http是無狀態(tài)的協(xié)議,因此客戶每次通過瀏覽器訪問web頁面,請求到服務(wù)端時,服務(wù)器都會新建線程,打開新的會話,而且服務(wù)器也不會自動維護(hù)客戶的上下文信息。比如我們現(xiàn)在要實(shí)現(xiàn)一個電商內(nèi)的購物車功能,要怎么才能知道哪些購物車請求對應(yīng)的是來自同一個客戶的請求呢?
圖片
因?yàn)?http 請求是無狀態(tài)請求,所以在 Web 領(lǐng)域,大部分都是通過這種方式解決。但是這么做有什么問題呢?我們接著看
圖片
隨著技術(shù)的發(fā)展,用戶流量增大,單個服務(wù)器已經(jīng)不能滿足系統(tǒng)的需要了,分布式架構(gòu)開始流行。通常都會把系統(tǒng)部署在多臺服務(wù)器上,通過負(fù)載均衡把請求分發(fā)到其中的一臺服務(wù)器上,這樣很可能同一個用戶的請求被分發(fā)到不同的服務(wù)器上,因?yàn)?session 是保存在服務(wù)器上的,那么很有可能第一次請求訪問的 A 服務(wù)器,創(chuàng)建了 session,但是第二次訪問到了 B 服務(wù)器,這時就會出現(xiàn)取不到 session 的情況。
我們知道,Session 一般是用來存會話全局的用戶信息(不僅僅是登陸方面的問題),用來簡化/加速后續(xù)的業(yè)務(wù)請求。
傳統(tǒng)的 session 由服務(wù)器端生成并存儲,當(dāng)應(yīng)用進(jìn)行分布式集群部署的時候,如何保證不同服務(wù)器上 session 信息能夠共享呢?
Session共享一般有兩種思路
session復(fù)制即將不同服務(wù)器上 session 數(shù)據(jù)進(jìn)行復(fù)制,用戶登錄,修改,注銷時,將session信息同時也復(fù)制到其他機(jī)器上面去
圖片
這種實(shí)現(xiàn)的問題就是實(shí)現(xiàn)成本高,維護(hù)難度大,并且會存在延遲登問題。
圖片
集中存儲就是將獲取session單獨(dú)放在一個服務(wù)中進(jìn)行存儲,所有獲取session的統(tǒng)一來這個服務(wù)中去取。這樣就避免了同步和維護(hù)多套session的問題。一般我們都是使用redis進(jìn)行集中式存儲session。
圖片
如果企業(yè)做大了之后,一般都有很多的業(yè)務(wù)支持系統(tǒng)為其提供相應(yīng)的管理和 IT 服務(wù),按照傳統(tǒng)的驗(yàn)證方式訪問多系統(tǒng),每個單獨(dú)的系統(tǒng)都會有自己的安全體系和身份認(rèn)證系統(tǒng)。進(jìn)入每個系統(tǒng)都需要進(jìn)行登錄,獲取session,再通過session訪問對應(yīng)系統(tǒng)資源。
這樣的局面不僅給管理上帶來了很大的困難,對客戶來說也極不友好,那么如何讓客戶只需登陸一次,就可以進(jìn)入多個系統(tǒng),而不需要重新登錄呢?
圖片
“單點(diǎn)登錄”就是專為解決此類問題的。其大致思想流程如下:通過一個 ticket 進(jìn)行串接各系統(tǒng)間的用戶信息
我們知道對于完全不同域名的系統(tǒng),cookie 是無法跨域名共享的,因此 sessionId 在頁面端也無法共享,因此需要實(shí)現(xiàn)單店登錄,就需要啟用一個專門用來登錄的域名如(ouath.com)來提供所有系統(tǒng)的sessionId。當(dāng)業(yè)務(wù)系統(tǒng)被打開時,借助中心授權(quán)系統(tǒng)進(jìn)行登錄,整體流程如下:
整個交互流程圖如下:
圖片
3.2.2.1 CAS登錄服務(wù)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;}}@Controllerpublic 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)建用戶信息票據(jù) 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);; //如果未登陸,則拒絕請求,轉(zhuǎn)向登陸頁面 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);}@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系統(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;}@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);; //如果未登陸,則拒絕請求,轉(zhuǎn)向登陸頁面 String requestUrl = request.getServletPath(); if (!"/toLogin".equals(requestUrl)//不是登陸頁面 && !requestUrl.startsWith("/login")//不是去登陸 && null == userInfo) {//不是登陸狀態(tài) String ticket = request.getParameter("ticket"); //有票據(jù),則使用票據(jù)去嘗試拿取用戶信息 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 ; } /** * 將用戶信息,加載進(jìn)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>OAuth2: 三方授權(quán)協(xié)議,允許用戶在不提供賬號密碼的情況下,通過信任的應(yīng)用進(jìn)行授權(quán),使其客戶端可以訪問權(quán)限范圍內(nèi)的資源。
CAS: 中央認(rèn)證服務(wù)(Central Authentication Service),一個基于Kerberos票據(jù)方式實(shí)現(xiàn)SSO單點(diǎn)登錄的框架,為Web 應(yīng)用系統(tǒng)提供一種可靠的單點(diǎn)登錄解決方法(屬于 Web SSO )。
因此,需要統(tǒng)一的賬號密碼進(jìn)行身份認(rèn)證,用CAS;需要授權(quán)第三方服務(wù)使用我方資源,使用OAuth2;
本文鏈接:http://m.www897cc.com/showinfo-26-112778-0.html告別繁瑣操作,實(shí)現(xiàn)一次登錄產(chǎn)品互通
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 定時任務(wù)數(shù)量爆炸?Netty教你如何應(yīng)對百萬級挑戰(zhàn)
下一篇: PHP異步非阻塞MySQL客戶端連接池