登陆测试
- sso-session
- login接口
- LoginFilter过滤
- 不拦截/toLogin和/login,其余的路径都要拦截
- 通过LoginConfig类将一个LoginFilter配置到FilterRegistrationBean上
- 显示cookie插件,editthiscookie
分布式环境下的难点
- 1.用户登陆了tomcat1,不要重复去登陆tomcat2
- 2.用户登陆tomcat1生成的用户信息,tomcat2如何知道–校验权限
- 用一个单独的服务去保存session信息,可以通过redis
- 3.用户在tomcat1注销后,tomcat3要及时得到注销
用redis保存session
- 1.第一次request.getSession时,去redis内取值
- 2.response返回数据时,推送session到redis内
- 3.成熟的框架,spring-session
怎么修改request和response
-
SessionFilter-MyRequestWrapper
-
session_support项目,重置servlet中的request和response,LoginFilter.doFilter方法里面通过filterChanin.doFilter(myRequestWrapper,serviceResponse)
-
package com.enjoy.session; import com.enjoy.utils.CookieBasedSession; import org.springframework.data.redis.core.RedisTemplate; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.util.HashMap; import java.util.Map; import java.util.UUID; /** * Created by Peter on 2018/8/15. * request包装类 */ public class MyRequestWrapper extends HttpServletRequestWrapper { private volatile boolean committed = false; private String uuid = UUID.randomUUID().toString(); private MySession session; private RedisTemplate redisTemplate; public MyRequestWrapper(HttpServletRequest request,RedisTemplate redisTemplate) { super(request); this.redisTemplate = redisTemplate; } /** * 提交session内值到redis */ public void commitSession() { if (committed) { return; } committed = true; MySession session = this.getSession(); if (session != null && null != session.getAttrs()) { redisTemplate.opsForHash().putAll(session.getId(),session.getAttrs()); } } /** * 创建新session * @return */ public MySession createSession() { String sessionId = CookieBasedSession.getRequestedSessionId(this);//从页面传来的 Map<String,Object> attr ; if (null != sessionId){ attr = redisTemplate.opsForHash().entries(sessionId); } else { System.out.println("create session by rId:"+uuid); sessionId = UUID.randomUUID().toString(); attr = new HashMap<>(); } //session成员变量持有 session = new MySession(); session.setId(sessionId); session.setAttrs(attr); return session; } /** * 取session * @return */ public MySession getSession() { return this.getSession(true); } /** * 取session * @return */ public MySession getSession(boolean create) { if (null != session){ return session; } return this.createSession(); } /** * 是否已登陆 * @return */ public boolean isLogin(){ Object user = getSession().getAttribute(SessionFilter.USER_INFO); return null != user; } }
-
redis中用hash存的
-
自己也要自定义一个MySession,其中有一个attrs的字段,是一个Map<String,Object>
-
测试session共享
-
启动sessionA和sessionB
-
在pom文件中引入
<dependency> <artifactId>session_support</artifactId> <groupId>com.enjoy</groupId> <version>1.0</version> </dependency>
-
新增一个配置类
package com.enjoy.config; import com.enjoy.session.SessionFilter; 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; /** * Created by Peter on 2018/8/15. */ @Configuration public class SessionConfig { //配置filter生效 @Bean public FilterRegistrationBean sessionFilterRegistration(SessionFilter sessionFilter) { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(sessionFilter); registration.addUrlPatterns("/*"); registration.addInitParameter("paramName", "paramValue"); registration.setName("sessionFilter"); registration.setOrder(1); return registration; } //定义过滤器组件 @Bean public SessionFilter sessionFilter(RedisTemplate redisTemplate){ SessionFilter sessionFilter = new SessionFilter(); sessionFilter.setRedisTemplate(redisTemplate); return sessionFilter; } }
-
针对不是通过网关路由进来的,而是每个子服务都有自己域名的需要保证有共有的父域名,此时两个页面共享一个session,关键是需要共享一个sessionId,sessionId放在顶级域名下,sessionA和sessionB的二级域名请求时会自动带上父域名cookie里的sessionId信息
- 同理对于APP而言,tokenId也跟这个sessionId可以做一样的处理
-
把cookie设置到顶级域名
-
package com.enjoy.utils; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; public class CookieBasedSession{ public static final String COOKIE_NAME_SESSION = "psession"; public static String getRequestedSessionId(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); if (cookies == null) { return null; } for (Cookie cookie : cookies) { if (cookie == null) { continue; } if (!COOKIE_NAME_SESSION.equalsIgnoreCase(cookie.getName())) { continue; } return cookie.getValue(); } return null; } public static void onNewSession(HttpServletRequest request, HttpServletResponse response) { HttpSession session = request.getSession(); String sessionId = session.getId(); Cookie cookie = new Cookie(COOKIE_NAME_SESSION, sessionId); cookie.setHttpOnly(true); cookie.setPath(request.getContextPath() + "/"); cookie.setDomain("dev.com"); cookie.setMaxAge(Integer.MAX_VALUE); response.addCookie(cookie); } }
-
一级域名dev.com和二级域名a.dev.com
- 但是跨域了解决不了了
-
-
单点登录–不使用session共享
- 通过页面重定向,把前端身份信息id进行互传
- 1.A已经在a.com页面正常访问(已登录状态)
- 2.A访问b.com页面,发现未登录,于是跳转login.cas.com去登陆,此时,在cas.com域名下有cookie
- 3.login.cas.com服务经过校验,发现此cookie是有效的,直接返回success.cas.com页面和ticket
- 4.success.cas.com页面发生302跳转,转回b.com页面并携带ticket值
- 5.b.com服务器探测到此请求未登录,于是后端发ticket到cas.com校验,校验通过,则b.com种cookie到b.com域名下
测试sso-cas项目
-
cas-login
-
loginFilter不变
-
在/login接口方法下生成一个ticket,保存下来,不仅是redis,然后重定向回原页面,并带上ticket值
-
@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); } }
-
-
-
登录过程
- 1.a.com登录,发现是未登录
- 2.会跳转到cas.com,自己输入信息登录成功,此网站后台有用户信息(ticket=userInfo)
- 3.跳转回a.com?ticket=xxx
- 4.b.com进页面,发现是未登录
- 5.跳转cas.com(本来就是登录的),此网站后台有用户信息(ticket-userInfo),不用用户登录填信息
- 6.跳转回b.com?ticket=xxx
-
cas-website
-
SSOFilter需要增加逻辑,通过ticket去拿userInfo
-
看url上面是有ticket,如果有,从之前存储的地方取出用户信息,然后就不用登录了,并把用户信息set到session里面
-
package com.enjoy.utils; import org.springframework.data.redis.core.RedisTemplate; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; 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) {//不是登陆状态 String ticket = request.getParameter("ticket"); //有票据,则使用票据去尝试拿取用户信息 if (null != ticket){ userInfo = redisTemplate.opsForValue().get(ticket); } //无法得到用户信息,则去登陆页面 if (null == userInfo){ response.sendRedirect("http://cas.com:8080/toLogin?url="+request.getRequestURL().toString()); return ; } /** * 将用户信息,加载进session中 */ request.getSession().setAttribute(SSOFilter.USER_INFO,userInfo); redisTemplate.delete(ticket); } filterChain.doFilter(request,servletResponse); } @Override public void destroy() { } }
-
觉得请求上带着ticketId不安全,一是可以redis中带失效时间,二是doFilter接口中取出用户信息就删除,只让登录一次
-
-
-
cas-website2
-
package com.enjoy.utils; import org.springframework.data.redis.core.RedisTemplate; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; 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) {//不是登陆状态 String ticket = request.getParameter("ticket"); //有票据,则使用票据去尝试拿取用户信息 if (null != ticket){ userInfo = redisTemplate.opsForValue().get(ticket); } //无法得到用户信息,则去登陆页面 if (null == userInfo){ response.sendRedirect("http://cas.com:8080/toLogin?url="+request.getRequestURL().toString()); return ; } /** * 将用户信息,加载进session中 */ request.getSession().setAttribute(SSOFilter.USER_INFO,userInfo); } filterChain.doFilter(request,servletResponse); } @Override public void destroy() { } }
-