首选简单说明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 复制即将不同服务器上的session 数据进行复制,用户登录、修改、注销时,将session 信息同时也复制到其他机器上面去。

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

集中存储就是将获取的sesion 单独放在一个服务中进行存储, 所有获取session 的统一来这个服务中去取
这样就避免了同步和维护多套session 的问题, 一般我们都是使用redis 进行集中式存储session
我们知道对于完全不同的域名的系统,cookie 是无法跨域名共享的,因为sessionid 在页面端也无法共享,因此需要实现单点登录,就需要启用一个专门用来登录的域名(oauth.com) 来提供所有系统的sessionId
当业务系统被打开的时候,借助中心授权进行登录,整理流程如下:

当业务系统被打开的时候,借助中心授权系统进行登录,整体流程如下:
当b.com 打开的时候,发现自己未登陆,于是跳转到oauth.com去登录
oauth.com登录页面被打开,用户输入账户/密码 登陆成功
oauth.com 登录成功,种cookie 到oauth.com 域名下
把sessionId 放入后端的redius ,存放
当b.com 重新被打开,发现仍然是未登录,但是有一个ticket 的值
当b.com 用ticket 的值,到redis 里查到sessionId, 并做session 同步,然后种cooklie 给自己,页面原地重定向
当b.com 打开自己的页面,此时有了cookie,后台校验登录状态,成功
这里我们以8080 为登录服务器, 8089 为访问服务器。
- package com.example.demo.entity;
-
- import java.io.Serializable;
-
- /**
- * @author zhangyang
- * @version 1.0
- * @Date 2022/8/6 11:22
- * @Description
- */
- public class UserForm implements Serializable {
-
- private static final long serialVersionUID = 1L;
-
- /**
- * 用户姓名
- */
- private String username;
-
- /**
- * 密码
- */
- private String password;
-
- /**
- * 回调的url
- */
- 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;
- }
-
- }
登录控制器 IndexController
- package com.example.demo.controller;
-
- import com.example.demo.entity.UserForm;
- import com.example.demo.filter.LoginFilter;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.data.redis.core.RedisTemplate;
- import org.springframework.stereotype.Controller;
- import org.springframework.ui.Model;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.ModelAttribute;
- import org.springframework.web.bind.annotation.PostMapping;
- import org.springframework.web.servlet.ModelAndView;
-
- import javax.annotation.Resource;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
- import java.util.UUID;
- import java.util.concurrent.TimeUnit;
-
- /**
- * @author zhangyang
- * @version 1.0
- * @Date 2022/8/6 11:24
- * @Description
- */
- @Slf4j
- @Controller
- public class IndexController {
-
- @Resource
- 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 userForm = new UserForm();
- userForm.setUsername("laowang");
- userForm.setPassword("laowang");
- userForm.setBackurl(request.getParameter("url"));
- model.addAttribute("user", userForm);
- // 重定向到登录界面
- return "login";
- }
-
- @PostMapping("/login")
- public void login(@ModelAttribute UserForm userForm, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException {
- log.info("backUrl:" + userForm.getBackurl());
- httpServletRequest.getSession().setAttribute(LoginFilter.USER_INFO, userForm);
-
- // 登录成功,创建用户信息票据
- String ticket = UUID.randomUUID().toString();
- redisTemplate.opsForValue().set(ticket, userForm, 20, TimeUnit.SECONDS);
- // 重定向 回原来的url a.com
- if (null == userForm.getBackurl() || userForm.getBackurl().length() == 0) {
- httpServletResponse.sendRedirect("/index");
- } else {
- httpServletResponse.sendRedirect(userForm.getBackurl() + "?ticket=" + ticket);
- }
- }
-
-
- @GetMapping("/index")
- public ModelAndView index(HttpServletRequest request) {
- ModelAndView modelAndView = new ModelAndView();
- Object user = request.getSession().getAttribute(LoginFilter.USER_INFO);
- UserForm userForm = (UserForm) user;
- modelAndView.setViewName("index");
- modelAndView.addObject("user", user);
- request.getSession().setAttribute("test", "123");
- return modelAndView;
- }
-
-
- }
登录过滤器:
- package com.example.demo.filter;
-
-
- import javax.servlet.*;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
-
- /**
- * @author zhangyang
- * @version 1.0
- * @Date 2022/8/6 11:34
- * @Description
- */
- 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) {
- request.getRequestDispatcher("/toLogin").forward(request,response);
- return ;
- }
- filterChain.doFilter(request,servletResponse);
- }
-
- @Override
- public void destroy() {
-
- }
- }
配置过滤器:
- package com.example.demo.config;
-
- import com.example.demo.filter.LoginFilter;
- import org.springframework.boot.web.servlet.FilterRegistrationBean;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
-
- /**
- * @author zhangyang
- * @version 1.0
- * @Date 2022/8/7 9:51
- * @Description
- */
- @Configuration
- public class LoginConfig {
-
-
- @Bean
- public FilterRegistrationBean sessionFilterRegistration() {
- FilterRegistrationBean registration = new FilterRegistrationBean();
- registration.setFilter(new LoginFilter());
- registration.addUrlPatterns("/*");
- registration.addInitParameter("paramName","paramValue");
- registration.setOrder(1);
- return registration;
- }
- }
- HTML>
- <html xmlns:th="http://www.thymeleaf.org">
- <head>
- <title>enjoy logintitle>
- <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>
- package com.example.demo.filter;
-
- import com.example.demo.entity.UserForm;
- import org.springframework.data.redis.core.RedisTemplate;
-
- import javax.servlet.*;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
-
- /**
- * @author zhangyang
- * @version 1.0
- * @Date 2022/8/7 11:06
- * @Description
- */
- 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 destroy() {
-
- }
-
- @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) {
- 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 userForm = (UserForm) userInfo;
- request.getSession().setAttribute(SSOFilter.USER_INFO, userForm);
- redisTemplate.delete(ticket);
- }
- filterChain.doFilter(request, servletResponse);
- }
- }
- package com.example.demo.controller;
-
- import com.example.demo.entity.UserForm;
- import com.example.demo.filter.SSOFilter;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.servlet.ModelAndView;
-
- import javax.servlet.http.HttpServletRequest;
-
- /**
- * @author zhangyang
- * @version 1.0
- * @Date 2022/8/7 11:26
- * @Description
- */
- @Controller
- public class IndexController {
-
-
-
- @GetMapping("/index")
- public ModelAndView index(HttpServletRequest httpServletRequest) {
- ModelAndView modelAndView = new ModelAndView();
- Object userInfo = httpServletRequest.getSession().getAttribute(SSOFilter.USER_INFO);
- UserForm userForm = (UserForm) userInfo;
- modelAndView.setViewName("index");
- modelAndView.addObject("user", userForm);
- httpServletRequest.getSession().setAttribute("test", "123");
- return modelAndView;
- }
- }
配置Filter
- package com.example.demo.config;
-
- import com.example.demo.filter.SSOFilter;
- import org.springframework.boot.web.servlet.FilterRegistrationBean;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.data.redis.core.RedisTemplate;
-
- import javax.annotation.Resource;
-
- /**
- * @author zhangyang
- * @version 1.0
- * @Date 2022/8/7 11:35
- * @Description
- */
- @Configuration
- public class LoginConfig {
- @Resource
- private RedisTemplate redisTemplate;
-
-
- @Bean
- public FilterRegistrationBean ssoFilterRegistration() {
- FilterRegistrationBean registrationBean = new FilterRegistrationBean();
- registrationBean.setFilter(new SSOFilter(redisTemplate));
- registrationBean.addUrlPatterns("/*");
- registrationBean.addInitParameter("paramName", "paramValue");
- registrationBean.setName("ssoFilter");
- registrationBean.setOrder(1);
- return registrationBean;
- }
- }
index.html
- HTML>
- <html xmlns:th="http://www.thymeleaf.org">
- <head>
- <title>enjoy indextitle>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
- head>
- <body>
- <div th:object="${user}">
- <h1>cas-website:欢迎你">h1>
- div>
- body>
- 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 就又登录上去了。

OAuth2 : 第三方授权协议,允许用户在不提供账号和密码额的情况下,通过信任的应用进行授权,使其客户端可以访问权限范围内的资源
CAS: 中央认证服务(Central Authentication Service ) 一个基于Kerberos 票据方式实现SSO 单点登录的框架,为Web 应用系统提供了一种可靠的单点登录的解决方法(属于Web SSO)。
CAS 的单点登录时保障客户端的用户资源的安全; OAuth2则是保证服务端的用户资源的安全
CAS 客户端要获取的最终信息是: 这个用户到底有没有权限访问我(CAS客户端) 的资源,OAuth2获取的最终信息是,我(oauth2服务提供方)的用户的资源到底能不能让你(oauth2的客户端)访问
因此,需要统一的账号密码进行身份认证,用CAS,需要授权第三方服务使用我方资源,使用Oauth2