• Spring实现CAS登录


    首选简单说明Session

    一、Session

          session 就是一种保存上下文信息的机制,它是面向用户的,每一个SessionId 对应着一个用户,并保存在服务端中。session 主要以cookie 或者URL  重写为基础来实现的,默认使用cookie 来实现,系统会创建一个为JSessionID 的变量输出到cookie 中

         JSessionID 是存储于浏览器内存中,并不是写到硬盘上的,如果我们把浏览器的cookie 禁止, 则web 服务器会采用URL 重写的方式传递SessionId 。我们就可以在地址栏看到sessionId =123123131之类的字符串

          通常JessionID 是不能跨窗口使用的, 当你新开了一个浏览器窗口进入相同的页面的时候,系统会赋予你一个新的sessionId ,这样我们信息共享的目的就达不到了。

    二、机制

           当服务端接收到客户端的请求的时候,首先判断请求里是否包含了JeSsionId的SessionID,如果存在说明已经创建了,直接从内存中拿出来使用,如果查询不到,说明是无效的。

           如果客户端请求不包括sessionId,则为此客户创建一个session 并生成一个为此session相关联的sessionId,这个sesionId 将在本次响应中返回给客户端保存。

          对每次http请求都经理以下步骤进行处理:

            服务端首先查找对应的cookies的值(sessionId)  

            根据sessionId 从服务端的session存储中获取对应的id 的session的数据,进行返回

            如果找不到sessionid  服务端就创建session ,生成sessionid 对应的cookie 写入到响应头中。

          Session 是由服务端生成的, 并且以散列表的是形式存储到内存中

             基于Session的身份认证主要流程化如下: 

              

    因为http的请求是无状态请求的, 所以在web领域 大部分通过这种方式解决

    三、Session 面对的问题

     Session 共享方案: 

           1、session 复制

             session 复制即将不同服务器上的session 数据进行复制,用户登录、修改、注销时,将session 信息同时也复制到其他机器上面去。

      

      这种实现的问题就是实现成本高,维护难度大,并且会存在延迟登录的问题

     2、session 集中存储

     

     集中存储就是将获取的sesion 单独放在一个服务中进行存储, 所有获取session 的统一来这个服务中去取

      这样就避免了同步和维护多套session 的问题, 一般我们都是使用redis 进行集中式存储session 

    四、SSO(单点登录) 的底层原理CAS

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

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

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

              当b.com 打开的时候,发现自己未登陆,于是跳转到oauth.com去登录

             oauth.com登录页面被打开,用户输入账户/密码 登陆成功

             oauth.com 登录成功,种cookie 到oauth.com 域名下

             把sessionId 放入后端的redius ,存放 数据结构, 然后页面重定向到A 系统

             当b.com 重新被打开,发现仍然是未登录,但是有一个ticket 的值

             当b.com 用ticket 的值,到redis 里查到sessionId, 并做session 同步,然后种cooklie 给自己,页面原地重定向

             当b.com 打开自己的页面,此时有了cookie,后台校验登录状态,成功

     五、代码实现

      这里我们以8080 为登录服务器, 8089 为访问服务器。

      

    1. package com.example.demo.entity;
    2. import java.io.Serializable;
    3. /**
    4. * @author zhangyang
    5. * @version 1.0
    6. * @Date 2022/8/6 11:22
    7. * @Description
    8. */
    9. public class UserForm implements Serializable {
    10. private static final long serialVersionUID = 1L;
    11. /**
    12. * 用户姓名
    13. */
    14. private String username;
    15. /**
    16. * 密码
    17. */
    18. private String password;
    19. /**
    20. * 回调的url
    21. */
    22. private String backurl ;
    23. public String getUsername() {
    24. return username;
    25. }
    26. public void setUsername(String username) {
    27. this.username = username;
    28. }
    29. public String getPassword() {
    30. return password;
    31. }
    32. public void setPassword(String password) {
    33. this.password = password;
    34. }
    35. public String getBackurl() {
    36. return backurl;
    37. }
    38. public void setBackurl(String backurl) {
    39. this.backurl = backurl;
    40. }
    41. }

    登录控制器 IndexController

    1. package com.example.demo.controller;
    2. import com.example.demo.entity.UserForm;
    3. import com.example.demo.filter.LoginFilter;
    4. import lombok.extern.slf4j.Slf4j;
    5. import org.springframework.data.redis.core.RedisTemplate;
    6. import org.springframework.stereotype.Controller;
    7. import org.springframework.ui.Model;
    8. import org.springframework.web.bind.annotation.GetMapping;
    9. import org.springframework.web.bind.annotation.ModelAttribute;
    10. import org.springframework.web.bind.annotation.PostMapping;
    11. import org.springframework.web.servlet.ModelAndView;
    12. import javax.annotation.Resource;
    13. import javax.servlet.http.HttpServletRequest;
    14. import javax.servlet.http.HttpServletResponse;
    15. import java.io.IOException;
    16. import java.util.UUID;
    17. import java.util.concurrent.TimeUnit;
    18. /**
    19. * @author zhangyang
    20. * @version 1.0
    21. * @Date 2022/8/6 11:24
    22. * @Description
    23. */
    24. @Slf4j
    25. @Controller
    26. public class IndexController {
    27. @Resource
    28. private RedisTemplate redisTemplate;
    29. @GetMapping("/toLogin")
    30. public String toLogin(Model model, HttpServletRequest request) {
    31. Object userInfo = request.getSession().getAttribute(LoginFilter.USER_INFO);
    32. if (null != userInfo) {
    33. String ticket = UUID.randomUUID().toString();
    34. redisTemplate.opsForValue().set(ticket, userInfo, 2, TimeUnit.SECONDS);
    35. return "redirect:" + request.getParameter("url") + "?ticket=" + ticket;
    36. }
    37. UserForm userForm = new UserForm();
    38. userForm.setUsername("laowang");
    39. userForm.setPassword("laowang");
    40. userForm.setBackurl(request.getParameter("url"));
    41. model.addAttribute("user", userForm);
    42. // 重定向到登录界面
    43. return "login";
    44. }
    45. @PostMapping("/login")
    46. public void login(@ModelAttribute UserForm userForm, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException {
    47. log.info("backUrl:" + userForm.getBackurl());
    48. httpServletRequest.getSession().setAttribute(LoginFilter.USER_INFO, userForm);
    49. // 登录成功,创建用户信息票据
    50. String ticket = UUID.randomUUID().toString();
    51. redisTemplate.opsForValue().set(ticket, userForm, 20, TimeUnit.SECONDS);
    52. // 重定向 回原来的url a.com
    53. if (null == userForm.getBackurl() || userForm.getBackurl().length() == 0) {
    54. httpServletResponse.sendRedirect("/index");
    55. } else {
    56. httpServletResponse.sendRedirect(userForm.getBackurl() + "?ticket=" + ticket);
    57. }
    58. }
    59. @GetMapping("/index")
    60. public ModelAndView index(HttpServletRequest request) {
    61. ModelAndView modelAndView = new ModelAndView();
    62. Object user = request.getSession().getAttribute(LoginFilter.USER_INFO);
    63. UserForm userForm = (UserForm) user;
    64. modelAndView.setViewName("index");
    65. modelAndView.addObject("user", user);
    66. request.getSession().setAttribute("test", "123");
    67. return modelAndView;
    68. }
    69. }

    登录过滤器:

         

    1. package com.example.demo.filter;
    2. import javax.servlet.*;
    3. import javax.servlet.http.HttpServletRequest;
    4. import javax.servlet.http.HttpServletResponse;
    5. import java.io.IOException;
    6. /**
    7. * @author zhangyang
    8. * @version 1.0
    9. * @Date 2022/8/6 11:34
    10. * @Description
    11. */
    12. public class LoginFilter implements Filter {
    13. public static final String USER_INFO = "user";
    14. @Override
    15. public void init(FilterConfig filterConfig) throws ServletException {
    16. }
    17. @Override
    18. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    19. HttpServletRequest request = (HttpServletRequest) servletRequest;
    20. HttpServletResponse response = (HttpServletResponse) servletResponse;
    21. Object userInfo = request.getSession().getAttribute(USER_INFO);
    22. // 如果未登录,则拒绝请求,转向登录界面
    23. String requestUrl = request.getServletPath();
    24. if(!"/toLogin".equals(requestUrl) && !requestUrl.startsWith("/login") && null == userInfo) {
    25. request.getRequestDispatcher("/toLogin").forward(request,response);
    26. return ;
    27. }
    28. filterChain.doFilter(request,servletResponse);
    29. }
    30. @Override
    31. public void destroy() {
    32. }
    33. }

      配置过滤器:

       

    1. package com.example.demo.config;
    2. import com.example.demo.filter.LoginFilter;
    3. import org.springframework.boot.web.servlet.FilterRegistrationBean;
    4. import org.springframework.context.annotation.Bean;
    5. import org.springframework.context.annotation.Configuration;
    6. /**
    7. * @author zhangyang
    8. * @version 1.0
    9. * @Date 2022/8/7 9:51
    10. * @Description
    11. */
    12. @Configuration
    13. public class LoginConfig {
    14. @Bean
    15. public FilterRegistrationBean sessionFilterRegistration() {
    16. FilterRegistrationBean registration = new FilterRegistrationBean();
    17. registration.setFilter(new LoginFilter());
    18. registration.addUrlPatterns("/*");
    19. registration.addInitParameter("paramName","paramValue");
    20. registration.setOrder(1);
    21. return registration;
    22. }
    23. }

      

    1. HTML>
    2. <html xmlns:th="http://www.thymeleaf.org">
    3. <head>
    4. <title>enjoy logintitle>
    5. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    6. head>
    7. <body>
    8. <div text-align="center">
    9. <h1>请登陆h1>
    10. <form action="#" th:action="@{/login}" th:object="${user}" method="post">
    11. <p>用户名: <input type="text" th:field="*{username}" />p>
    12. <p>密 码: <input type="text" th:field="*{password}" />p>
    13. <p><input type="submit" value="Submit" /> <input type="reset" value="Reset" />p>
    14. <input type="text" th:field="*{backurl}" hidden="hidden" />
    15. form>
    16. div>
    17. body>
    18. html>


     

    8089 web 访问端

      

    1. package com.example.demo.filter;
    2. import com.example.demo.entity.UserForm;
    3. import org.springframework.data.redis.core.RedisTemplate;
    4. import javax.servlet.*;
    5. import javax.servlet.http.HttpServletRequest;
    6. import javax.servlet.http.HttpServletResponse;
    7. import java.io.IOException;
    8. /**
    9. * @author zhangyang
    10. * @version 1.0
    11. * @Date 2022/8/7 11:06
    12. * @Description
    13. */
    14. public class SSOFilter implements Filter {
    15. private RedisTemplate redisTemplate;
    16. public static final String USER_INFO = "user";
    17. public SSOFilter(RedisTemplate redisTemplate) {
    18. this.redisTemplate = redisTemplate;
    19. }
    20. @Override
    21. public void init(FilterConfig filterConfig) throws ServletException {
    22. }
    23. @Override
    24. public void destroy() {
    25. }
    26. @Override
    27. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    28. HttpServletRequest request = (HttpServletRequest) servletRequest;
    29. HttpServletResponse response = (HttpServletResponse) servletResponse;
    30. Object userInfo = request.getSession().getAttribute(USER_INFO);
    31. // 如果未登录,则拒绝请求,转向登陆页面
    32. String requestUrl = request.getServletPath();
    33. if (!"/toLogin".equals(requestUrl) && !requestUrl.startsWith("/login") && null == userInfo) {
    34. String ticket = request.getParameter("ticket");
    35. // 如果有票据 则使用票据去尝试拿取用户的信息
    36. if (null != ticket) {
    37. userInfo = redisTemplate.opsForValue().get(ticket);
    38. }
    39. // 如果无法得到用户信息,则去登录界面
    40. if (null == userInfo) {
    41. response.sendRedirect("http://127.0.0.1:8080/toLogin?url=" + request.getRequestURL().toString());
    42. return;
    43. }
    44. /**
    45. * 将用户信息 加载进入session 中
    46. */
    47. UserForm userForm = (UserForm) userInfo;
    48. request.getSession().setAttribute(SSOFilter.USER_INFO, userForm);
    49. redisTemplate.delete(ticket);
    50. }
    51. filterChain.doFilter(request, servletResponse);
    52. }
    53. }

      

    1. package com.example.demo.controller;
    2. import com.example.demo.entity.UserForm;
    3. import com.example.demo.filter.SSOFilter;
    4. import org.springframework.stereotype.Controller;
    5. import org.springframework.web.bind.annotation.GetMapping;
    6. import org.springframework.web.servlet.ModelAndView;
    7. import javax.servlet.http.HttpServletRequest;
    8. /**
    9. * @author zhangyang
    10. * @version 1.0
    11. * @Date 2022/8/7 11:26
    12. * @Description
    13. */
    14. @Controller
    15. public class IndexController {
    16. @GetMapping("/index")
    17. public ModelAndView index(HttpServletRequest httpServletRequest) {
    18. ModelAndView modelAndView = new ModelAndView();
    19. Object userInfo = httpServletRequest.getSession().getAttribute(SSOFilter.USER_INFO);
    20. UserForm userForm = (UserForm) userInfo;
    21. modelAndView.setViewName("index");
    22. modelAndView.addObject("user", userForm);
    23. httpServletRequest.getSession().setAttribute("test", "123");
    24. return modelAndView;
    25. }
    26. }

    配置Filter

    1. package com.example.demo.config;
    2. import com.example.demo.filter.SSOFilter;
    3. import org.springframework.boot.web.servlet.FilterRegistrationBean;
    4. import org.springframework.context.annotation.Bean;
    5. import org.springframework.context.annotation.Configuration;
    6. import org.springframework.data.redis.core.RedisTemplate;
    7. import javax.annotation.Resource;
    8. /**
    9. * @author zhangyang
    10. * @version 1.0
    11. * @Date 2022/8/7 11:35
    12. * @Description
    13. */
    14. @Configuration
    15. public class LoginConfig {
    16. @Resource
    17. private RedisTemplate redisTemplate;
    18. @Bean
    19. public FilterRegistrationBean ssoFilterRegistration() {
    20. FilterRegistrationBean registrationBean = new FilterRegistrationBean();
    21. registrationBean.setFilter(new SSOFilter(redisTemplate));
    22. registrationBean.addUrlPatterns("/*");
    23. registrationBean.addInitParameter("paramName", "paramValue");
    24. registrationBean.setName("ssoFilter");
    25. registrationBean.setOrder(1);
    26. return registrationBean;
    27. }
    28. }

    index.html 

    1. HTML>
    2. <html xmlns:th="http://www.thymeleaf.org">
    3. <head>
    4. <title>enjoy indextitle>
    5. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    6. head>
    7. <body>
    8. <div th:object="${user}">
    9. <h1>cas-website:欢迎你">h1>
    10. div>
    11. body>
    12. html>

    实现效果:

       当地址栏上访问 http://localhost:8089/index  

       这时候被ssoFilter 给拦截到,由于没有cookie 即SSOFilter.LOGIN_INFO 为空,导致重定向到了 http://127.0.0.1:8080/toLogin?url=http://localhost:8089/index  

       在8080 服务器上,访问toLogin 的时候,被Loginfilter 给拦截到,还会去校验是否登录到了8089 上,如果没有登录并且访问的地址不是toLogin,login 这种则会被重定向到toLogin ,

       到达toLogin 请求后,这里封装了UserForm 用于默认显示在页面上。

       然后页面上访问点击  submit 触发login 动作,这时候就会将 user对象存储到redis中,并返回一个ticket 。然后重定向到原来的url 上。

     

     

    当再次访问到http://localhost:8089/index 的时候地址栏上多了一个ticket  ,这时候就又会被ssoFilter给拦截到,但代码中利用ticket 产生了session ,这样8080 就又登录上去了。

     

    六、CAS与OAuth2区别

      

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

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

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

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

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

    ​​​​​​​

  • 相关阅读:
    对北京新发地当时菜品三十天内价格分布式爬取(1)---(获取当时菜品数据并构建请求数据推入redis)
    Linux零基础入门(一)初识Linux
    【Java】泛型类
    罗技鼠标滚轮模式介绍 | 鼠标滚轮异响 - 解决方案
    Linux删除文件与Python代码删除文件命令
    什么企业可以申报高新技术企业
    Leetcode6234-最小公倍数为 K 的子数组数目
    C++ 中的 enum关键字
    Python数据结构:字典(dict)详解
    【日拱一卒行而不辍20220920】自制操作系统
  • 原文地址:https://blog.csdn.net/qq_38345598/article/details/126210641