简介
较大的企业内部,一般都有很多的业务支持系统为其提供相应的管理和 IT 服务。通常来说,每个单独的系统都会有自己的安全体系和身份认证系统。进入每个系统都需要进行登录,这样的局面不仅给管理上带来了很大的困难,对客户来说也极不友好。那么如何让客户只需登陆一次,就可以进入多个系统,而不需要重新登录呢。“单点登录”就是专为解决此类问题的。其大致思想流程如下:通过一个 ticket 进行串接各系统间的用户信息
实现思路
业务服务设计
在每一个需要身份认证的服务中,定义一个SSOFilter用于拦截非登录请求。对于每个拦截的请求,会先从当前请求的Session中确认是否能够拿到用户信息,拿不到用户信息又会确认当前请求中是否携带ticket票据这个参数,如果携带就会尝试从Redis中根据该票据拿到用户信息。如果最终都获取不到用户信息就会被重定向到SSO登录服务的登录页面进行登录处理
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); } //无法得到用户信息,则去CAS服务的登陆页面 if (null == userInfo){ response.sendRedirect("http://cas.com:8080/toLogin?url="+request.getRequestURL().toString()); return ; } /** * 将用户信息,加载进session中 */ request.getSession().setAttribute(SSOFilter.USER_INFO,userInfo); //登录成功需要将ticket从redis中删除 redisTemplate.delete(ticket); } filterChain.doFilter(request,servletResponse); } @Override public void destroy() { }
}
SSO服务设计
在SSO登录服务中,只需要简单定义一个Filter,进行拦截非登录请求,然后确认当前请求的Session中是否能够拿到用户信息,如果能拿到用户信息,那么就是登录状态,否则,认定当前请求无效,将请求转发到登录页面即可
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() { }
}
在SSO登录处理过程中,当请求已认证登录成功后,会先生成一个ticket票据,并将ticket票据和用户信息存放到Redis中,然后重定向回原先请求服务的Url,并携带上ticket票据参数
public class IndexController {
@Autowired
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 user = new UserForm(); user.setUsername("username"); user.setPassword("password"); 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 { request.getSession().setAttribute(LoginFilter.USER_INFO,user); //登陆成功,创建用户信息票据 String ticket = UUID.randomUUID().toString(); //将ticket和用户信息写入到redis中 redisTemplate.opsForValue().set(ticket,user,20, TimeUnit.SECONDS); //重定向,回原请求的url,并携带ticket信息 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(); modelAndView.setViewName("index"); modelAndView.addObject("user", request.getSession().getAttribute(LoginFilter.USER_INFO)); request.getSession().setAttribute("test","123"); return modelAndView; }
}
单点登录流程
1.用户访问服务A某个页面时,服务A发现自己未登录,重定向到CAS单点登录服务,CAS服务也发现未登录,则跳转到相应的登录页面
2.用户输入用户名和密码登录成功后,CAS服务进行认证,将登录状态记录CAS服务的session中,并写入当前CAS服务域名下的Cookie中
3.CAS服务登录完成后会生成一个Ticket票据,并将Ticket和用户信息记录到Redis中,之后再重定向回服务A,同时将Ticket作为参数传递给服务A
4.服务A拿到Ticket后,从Redis中进行查找,查询Ticket对应的用户信息,之后服务A再将登录状态写入session并设置到服务A域名下的Cookie中
5.至此,单点登录就完成了,之后再访问服务A时,服务A就是登录状态的
6.当有一个新的服务B被用户访问时,服务B发现自己也未登录,此时也重定向到CAS单点登录服务,但是此时CAS服务发现已经登录了,此时就不需要进行登录认证
7.CAS服务会生成一个Ticket票据,并将Ticket和用户信息记录到Redis中,之后再重定向回服务B,同时将Ticket作为参数传递给服务B
8.服务B拿到Ticket后,从Redis中进行查找,查询Ticket对应的用户信息,之后服务B再将登录状态写入session并设置到服务B域名下的Cookie中
9.因此服务B不需要进行登录过程,就能完成用户登录认证
参考
https://blog.csdn.net/wzljiayou/article/details/109550565 *****
https://www.cnblogs.com/zh94/p/8352943.html *** 没看,觉得不错