SSO——单点登录: 仿CAS

单点登录

大致原理如下:

我们sso客户端访问需要登录的url 或者登录的url 直接跳转到sso服务端的登录页面(如果已经登录则直接跳转到访问的url)

直接访问登录的url,sso客户端的拦截器会将其直接重定向到 sso服务端的登录页面并携带sso客户端访问地址。
访问sso客户端需要登录的url(没有登录的情况下)会重定向到sso服务端单点授权验证逻辑中。如果该用户未登录则重定向到 sso服务端的登录页面并携带sso客户端访问地址。
输入用户名和密码在sso服务端进行登录,如果用户名和密码错误则重新跳转到登陆页面并提示错误信息。

如果用户名和密码正确,则生成 GrantingTicket 并将GrantingTicket 的id 放入cookie中。然后生成 ServiceTicket 并将ServiceTicket 放入GrantingTicket 的缓存map中。

将生成 GrantingTicket放入到缓存中(key:GrantingTicket的id value:GrantingTicket)然后再将ServiceTicket 的id 和对应的GrantingTicket的id放入缓存中。

重定向到sso客户端 并将 ServiceTicket 的id(ticket)拼接到重新向的url中。

sso客户端根据ticket 再次调用sso服务端去获取用户信息。

sso服务端接受到sso客户端 传来ticket后, 根据ticket 获取缓存中的 GrantingTicket的 id,然后再根据GrantingTicket的id 获取GrantingTicket。然后再从GrantingTicket获取用户信息返回给 sso客户端。
8.sso客户端接受到用户信息后将其保存在session中。然后再将ticket 和session的关系存入sso客户端 缓存的map中。

如果sso客户端根据ticket 获取用户失败则返回sso服务端的登录页面并提示非法操作。

单点授权验证

大致原理如下:

当我们访问需要登录url时并且用户session信息为null的情况下 则走单点授权验证。
如果该用户未登录则重定向到 sso服务端的登录页面并携带sso客户端访问地址。后面的就走sso单点登录操作。
如果该用户已经在别的系统已经登录过。sso客户端拦截器拦截到该操作是单点授权验证后会重定向到sso服务端单点授权url。
4.sso服务端拦截器拦截到 单点授权url后 首先获取 cookie中 GrantingTicket的id 。然后根据GrantingTicket的id获取缓存中的GrantingTicket。
sso服务端 生成ServiceTicket 并放入到GrantingTicket的缓存中。
sso服务端 重定向到sso客户端并携带ServiceTicket 的id (ticket)。
sso客户端拦截器拦截到重定向url并获取到ticket 。然后根据ticket从sso服务端获取用户的信息。
sso服务端接受到sso客户端ticket 根据ticket 获取缓存中的 GrantingTicket的id,然后再根据GrantingTicket的id 获取GrantingTicket。然后再从GrantingTicket获取用户信息,返回给客户端。
sso客户端接受到用户的信息后 将信息保存在session中
然后将 ticket 和sesion的关系对应保存在缓存中。

单点退出

sso客户端点击退出按钮,会向sso服务端发起退出登录操作。
sso服务端拦截器拦截到退出的操作后会将进入退出的操作逻辑
sso服务端 第一步先获取sso服务端在存储的GrantingTicket的cookie信息
根据在cookie中存储的GrantingTicket的id 从缓存中获取到GrantingTicket。
然后将sso服务端在存储的GrantingTicket的cookie信息清除。
获取GrantingTicket 缓存的ServiceTicket缓存map信息。
遍历ServiceTicket的Map信息。
通过ServiceTicket的host信息依次调用客户端的退出url并携带ticket。
sso客户端拦截器拦截到退出的操作并获取到ticket.根据之前在客户端缓存的map中根据ticket获取到sesion的信息并调用sess.invalidate();
sso服务端重定向到sso服务端的登录页面。

slo

 

ServiceFilter


 

package cn.zhuoqianmingyue.filter;

import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.util.StringUtils;

import cn.hutool.json.JSONUtil;
import cn.zhuoqianmingyue.cache.TicketCache;
import cn.zhuoqianmingyue.pojo.User;
import cn.zhuoqianmingyue.ticket.GrantingTicket;
import cn.zhuoqianmingyue.ticket.ServiceTicket;

public class SSOServerFilter implements Filter{
	//认证中心服务登录地址
	private final String LOGINURL = "http://localhost:8080/ssoServer/login/login";
	//认证中心服务cookieName
	private final String COOKIENAME = "renzhengzhongxindecookie";
	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
			FilterChain filterChain) throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest )servletRequest;
		HttpServletResponse response = (HttpServletResponse )servletResponse;
		
		String uri = request.getRequestURI();

		if(uri.endsWith("sso/validateTicket")){
			//service请求校验ticket。既stId。
			String ticket = request.getParameter("ticket");
			String service = request.getParameter("service");
			//获取gt
			String gtId = TicketCache.getGTByST(ticket);
			GrantingTicket grantingTicket = TicketCache.getGrantingTicket(gtId);
			//根据gt获取对应user(此处可以用redis代替TicketCache,将gtid与gt的对应关系上传上去)
			User user = grantingTicket.getUser();
			String jsonStr = JSONUtil.toJsonStr(user);
			
			//判断是否未过期
			ServiceTicket serviceTicket = grantingTicket.getServiceTicketMap().get(ticket);
			boolean expired = serviceTicket.isExpired();
			if(expired){
				//未过期,则校验一次后过期,并返回用户信息
				serviceTicket.setExpired(false);
				response.getOutputStream().write(jsonStr.getBytes("UTF-8"));
				return;
			}else{
				//已过期,通知原service,ticket过期
				response.sendRedirect(LOGINURL+"?srevice="+service+"&ERRORMSG=ticket已经过期!");
				return;
			}
			
			
		}else if(uri.endsWith("sso/signin")){
			//登录页面登录
			String loginName = request.getParameter("loginName");
			String password = request.getParameter("password");
			String service = request.getParameter("service");
			if("zongx".equals(loginName)){
				//用户名,密码验证通过。登陆成功则创建
				//1、创建GrantingTicket
				GrantingTicket gt = new GrantingTicket();
				//1.1、设置GrantingTicket id
				UUID randomUUID = UUID.randomUUID();
				String gtId = randomUUID.toString();
				gt.setId(gtId);
				//1.2、设置User
				User user = new User();
				user.setUserId(1l);
				user.setLoginName("zongx");
				user.setPassword("123456");
				gt.setUser(user);

				//2、构建返回用户的cookie信息(以便下次携带)
				Cookie cookie = new Cookie(COOKIENAME, gtId);
				cookie.setMaxAge(-1);	
				cookie.setPath("/");
				response.addCookie(cookie);
				//3、在TicketCache中进行缓存,使用concurrenthashmap GT_CACHE_MAP 保存gtid 与 gt映射
				TicketCache.putGrantingTicket(gtId, gt);

				//4、创建ServiceTicket
				ServiceTicket st = new ServiceTicket();
				//4.1、设置ServiceTicket id
				String stId = UUID.randomUUID().toString();
				st.setId(stId);
				//4.2、保存service,既是通过那个客户端访问过来的请求
				st.setHost(service);
				//5、在gt的serviceTicketMap中保存,stId 与 st的映射关系
				ConcurrentHashMap<String, ServiceTicket> serviceTicketMap = gt.getServiceTicketMap();
				serviceTicketMap.put(stId, st);
				//6、在TicketCache中进行缓存,使用concurrenthashmap ST_GT_CACHE_MAP 保存stId 与 gtId映射
				TicketCache.putSTAndGT(stId, gtId);
				//7、返回用户重定向为来请求的服务地址(service),并携带stId
				response.sendRedirect(service+"?ticket="+st.getId());
				return;
			}else{
				//用户登录没有验证通过,则报错
				response.sendRedirect(LOGINURL+"?srevice="+service+"&ERRORMSG=登录名或者密码错误");
				return;
			}
		}else if(uri.endsWith("sso/authentication")){
			//如果客户端无法通过session与ticket判断是否登录,则会重定向到请求authentication
			//1、通过浏览器cookie中gtid获取,TicketCache中的gt
			String gtId = getSSOCookie(request,response);
			String service =  request.getParameter("service");
			GrantingTicket gt = null;
			if(gtId != null){
				gt = TicketCache.getGrantingTicket(gtId);
			}
			//2、如果没有则返回给服务
			if(StringUtils.isEmpty(service) || null == gtId || null == gt){
				response.sendRedirect(LOGINURL+"?srevice="+service);
				return;
			}
			//3、生成st并返回service
			ServiceTicket st = new ServiceTicket();
			String stId = UUID.randomUUID().toString();
			st.setId(stId);
			st.setHost(service);
			ConcurrentHashMap<String, ServiceTicket> serviceTicketMap = gt.getServiceTicketMap();
			serviceTicketMap.put(stId, st);
			TicketCache.putSTAndGT(stId, gt.getId());
			response.sendRedirect(service+"?ticket="+st.getId());
			return;
		}else if(uri.endsWith("sso/signout")){
			String gtCookie = getSSOCookie(request, response);
			String service = request.getParameter("service");
			ssoLogout(request,response,gtCookie);
			response.sendRedirect(LOGINURL+"?service="+service);
			return;
		}
		
		filterChain.doFilter(servletRequest, servletResponse);	
	}
	
	/**
	 * 单点退出
	 * @param request
	 * @param response
	 * @param chain
	 * @throws IOException
	 * @throws ServletException
	 */
	public void ssoLogout(HttpServletRequest request,HttpServletResponse response, String gtCookie) {
		// 清除cookie值
		removeSSOCookie(request,response, COOKIENAME);
		GrantingTicket gt = TicketCache.getGrantingTicket(gtCookie);
		if (null != gt) {
			ConcurrentHashMap<String, ServiceTicket> serviceTicketMap = gt
					.getServiceTicketMap();

			Collection<ServiceTicket> stes = serviceTicketMap.values();
			ServiceTicket serviceTicket = null;
			//String host = null;
			for (Iterator<ServiceTicket> iterator = stes.iterator(); iterator
					.hasNext();) {
				serviceTicket = (ServiceTicket) iterator.next();
				noticeClientLogout(serviceTicket);
				TicketCache.removeServiceTicket(serviceTicket.getId());
			}
			
		}
		TicketCache.removeGrantingTicket(gtCookie);
	}
	/**
	 * 获得sso的cookie值
	 * @param response
	 * @param ticket
	 */
	public String getSSOCookie(HttpServletRequest request,HttpServletResponse response)
	{
		Cookie[] cookies = request.getCookies();
		String gtCookie = null;
		if(null != cookies)
		{
			Cookie cookie = null;
			for (int i = 0; i < cookies.length; i++) {
				cookie = cookies[i];
				if(COOKIENAME.equals(cookie.getName()))
				{
					gtCookie = cookie.getValue();
					break;
				}
			}
		}
		return gtCookie;
	}
	public void removeSSOCookie(HttpServletRequest request,HttpServletResponse response, String name) {
		Cookie[] cookies = request.getCookies();
		if (null == cookies) {
			return;
		}

		for (Cookie cookie : cookies) {
			if (cookie.getName().equals(name)) {
				cookie.setValue(null);
				cookie.setMaxAge(0);// 立即销毁cookie
				cookie.setPath("/");
				response.addCookie(cookie);
				break;
			}
		}
	}
	
	/**
	 * 通知退出
	 * @param ticket
	 */
	public static void noticeClientLogout(ServiceTicket ticket){
			String logoutURL= ticket.getHost()+"?requestLogout="+ticket.getId();
			System.out.println("========logoutURL=="+logoutURL);
			clientLogout(logoutURL);
			return;
	}
	/**
	 * 通知客户端退出
	 * @param url
	 */
	public static void clientLogout(String url){
    	CloseableHttpClient  httpClient=HttpClients.createDefault();  
		HttpPost httppost = new HttpPost(url); 
		try {
			CloseableHttpResponse execute = httpClient.execute(httppost);
			execute.close();
		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally{
			try {
				httpClient.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
    }
}

ClientFilter

package cn.zhuoqianmingyue.filter;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.util.StringUtils;

import cn.hutool.json.JSONUtil;
import cn.zhuoqianmingyue.pojo.User;

public class SSOClientFilter implements Filter{
	private final String SUCESSURL = "/index/index";
	private final String SSOSERVERLOGINURL = "http://localhost:8080/ssoServer/login/login";
	private final String SSOSERVERAUTHENTICATIONURL = "http://localhost:8080/ssoServer/sso/authentication";
	public static ConcurrentHashMap<String,HttpSession> TICKET_SESSION_CACHE = new ConcurrentHashMap<String,HttpSession>();
	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
			FilterChain filterChain) throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest )servletRequest;
		HttpServletResponse response = (HttpServletResponse )servletResponse;
		HttpSession session = request.getSession();
		String path = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath();
		String uri = request.getRequestURI();
		
		//被动单点退出
		String requestLogoutTicket = request.getParameter("requestLogout");
		if(!StringUtils.isEmpty(requestLogoutTicket)){
			ssoLogout(requestLogoutTicket);
			response.sendRedirect(SSOSERVERLOGINURL+"?service="+path);
			return;
		}
		//1、如果包含ticket,则直接根据ticket(stid)去认证中心获取用户信息
		String ticket = request.getParameter("ticket");
		if(!StringUtils.isEmpty(ticket)){
			User user = getUserByTicket(ticket);
			if(user == null){
				response.sendRedirect(SSOSERVERLOGINURL+"ERRORMSG="+"非法访问");
				return;
			}else{
				//如果已经完成了ticket校验,则将user放到本地session
				session.setAttribute("LOGIN_INFO", user);
				response.sendRedirect(path+SUCESSURL);
				TICKET_SESSION_CACHE.put(ticket, session);
				return;
			}
			
		}
		//如果没有包含ticket请求,直接从本地session中取出用户
		User user = (User)session.getAttribute("LOGIN_INFO");
		if(uri.endsWith("/login")){
			response.sendRedirect(SSOSERVERLOGINURL+"?service="+path);
			return;
		}
		if(null != user || uri.endsWith("css") || uri.endsWith("js")){
			//能够取出用户直接放行
			filterChain.doFilter(servletRequest, servletResponse);
			return;
		}else{
			//取不出来,去用户中心认证
			response.sendRedirect(SSOSERVERAUTHENTICATIONURL+"?service="+path);
			return;
		}
	}
	
	private void ssoLogout(String ticket) {
		if(TICKET_SESSION_CACHE.containsKey(ticket)){
			HttpSession sess = TICKET_SESSION_CACHE.get(ticket);
			if(null != sess){
				sess.invalidate();
			}
			TICKET_SESSION_CACHE.remove(ticket);
		}
	}
	/**
	 * @Description: 向认证中心校验ticket
	 * @Author: zongx
	 * @Date: 2020/5/14
	 * @Param: ticket
	 * @return cn.zhuoqianmingyue.pojo.User
	*/
	private User getUserByTicket(String ticket)  {

		User user = null;
		CloseableHttpResponse closeableHttpResponse = null;
		try {
			CloseableHttpClient httpClient = HttpClients.createDefault();
			URI uriHttp = uriHttp = new URI("http://localhost:8080/ssoServer/sso/validateTicket");
			URIBuilder uriBuilder = new URIBuilder(uriHttp);
			uriBuilder.setParameter("ticket", ticket);
			URI uriParma  = uriBuilder.build();
			HttpGet httpGet = new HttpGet(uriParma);
			//执行请求访问
			closeableHttpResponse = httpClient.execute(httpGet);
			//获取返回HTTP状态码
			int satausCode = closeableHttpResponse.getStatusLine().getStatusCode();
			if(satausCode == 200 ){
				HttpEntity entity = closeableHttpResponse.getEntity();
				String content = EntityUtils.toString(entity,"UTF-8");
				user = JSONUtil.toBean(content, User.class);
				EntityUtils.consume(entity);
			}
		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}finally{
			try {
				closeableHttpResponse.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return user;
	}
}

具体工程:

https://download.csdn.net/download/u010365717/12419420

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值