SpringBoot(十六)使用Filter过滤器实现单点登录

384 阅读3分钟

Java有两个安全框架shiro和Security,这两个玩意儿都是可以实现单点登录的。Shiro相对简单,Security相对复杂。这两个后期我们也会学到,但是叭,在我要求没那么复杂的情况下,使用filter过滤器就可以满足我们的需求了。

 

一:单点登录实现逻辑

在用户表user中添加一个字段login_ticket,在每次登录的时候,生成一个唯一字符串存储在session中和数据库的login_ticket字段中。

创建一个登录验证中间件,每次请求都需要经过这个中间件再转发至controller中。

在登录验证中间件中先判断是否登录,没有登录直接放行。

如果登陆了,进入单点登录验证逻辑,首先通过session获取当前登录用户的ID和login_ticket,使用用户ID去数据库中查询登录用户信息。

对比session中的login_ticket和数据库中的login_ticket是否一致,二者一致,说明当前登录用户有效,直接放行即可。如果不一致,说明当前账号有其他人在别处登录。清空session,返回登录失效即可。

 

二:Springboot使用filter实现单点登录

1 :在config目录下创建FilterConfig.java, 注册为Spring组件,这样Spring Boot就会自动将其加入到Filter链中。代码如下:

package com.springbootblog.config;
 
 import com.springbootblog.filter.FontendFilter;
 import org.springframework.boot.web.servlet.FilterRegistrationBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.filter.CharacterEncodingFilter;
 
 /**
  * filter配置
  */
 @Configuration
 public class FilterConfig {
     @Bean
     public FilterRegistrationBean<FontendFilter> registrationBean(){
         CharacterEncodingFilter filter = new CharacterEncodingFilter();
         /*将文本过滤器类注册到容器中*/
         filter.setEncoding("utf-8");
         filter.setForceEncoding(true);
         FilterRegistrationBean<FontendFilter> filterRegistrationBean = new FilterRegistrationBean<>();
         filterRegistrationBean.setFilter(new FontendFilter());
         filterRegistrationBean.addUrlPatterns("/*");  //setUrlPatterns() 一次性将映射关系配置进去
         filterRegistrationBean.setOrder(1);
         filterRegistrationBean.setName("filter");
         return filterRegistrationBean;
     }
 }

2 :在filter目录下创建FontendFIlter.java文件,代码如下:

package com.springbootblog.filter;
 
 import com.springbootblog.service.fontend.LoginService;
 import org.springframework.web.context.WebApplicationContext;
 import org.springframework.web.context.support.WebApplicationContextUtils;
 
 import javax.servlet.*;
 import javax.servlet.annotation.WebFilter;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.util.Map;
 
 
 @WebFilter(urlPatterns = "/java/*", filterName = "FontendFilter")
 public class FontendFilter implements Filter
 {
     private LoginService loginService;
 
     @Override
     public void init(FilterConfig filterConfig) throws ServletException
     {
         //过滤器注入service,原因:过滤器执行的时候,service还没有被实例化注入
         ServletContext sc = filterConfig.getServletContext();
         WebApplicationContext cxt = WebApplicationContextUtils.getWebApplicationContext(sc);
         if(cxt != null)
         {
             if (loginService == null)
             {
                 // 注入service类
                 loginService = cxt.getBean(LoginService.class);
             }
         }
     }
 
     @Override
     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException
     {
         // System.out.println("FontendFilter.doFilter");
         // servletRequest.setCharacterEncoding("utf-8");
         // 强转
         HttpServletRequest request = (HttpServletRequest)servletRequest;
         HttpServletResponse response = (HttpServletResponse)servletResponse;
 
         // ================================================================================
         // 单点登录逻辑
         try
         {
             Map<String, Object> result = loginService.checkSingleLogin(request);
             if((int)result.get("code") < 0)
             {
                 // 设置前端状态码和内容类型 设置响应状态码为 200。
                 response.setStatus(HttpServletResponse.SC_OK);
                 // 设置响应内容类型
                 response.setContentType("application/json");
                 response.setCharacterEncoding("UTF-8");
 
                 // 直接写入响应体
                 String jsonResponse = "{"code": "-150","msg": "您的账号在另一地点登录!"}";
                 //System.out.println("filter-map:"+jsonResponse);
                 response.getWriter().write(jsonResponse);
                 return;
             }
         }
         catch (Exception e)
         {
             System.out.println("Filter 出错了");
             throw new RuntimeException(e);
         }//*/
         // 继续执行下边的代码
         filterChain.doFilter(servletRequest,servletResponse);
     }
 
     @Override
     public void destroy() {}
 }

这里解释下上方代码中需要注意的几个问题:

1 :按照我在第一部分中的逻辑,实现单点登录是需要使用数据库查询的,因此我将数据库查询这一部分封装到service中。但是过滤器执行的时候,service还没有被实例化注入,因此这里我们需要通过使用如下的代码将service注入到filter中:

private LoginService loginService;
 
 @Override
 public void init(FilterConfig filterConfig) throws ServletException
 {
     //过滤器注入service,原因:过滤器执行的时候,service还没有被实例化注入
     ServletContext sc = filterConfig.getServletContext();
     WebApplicationContext cxt = WebApplicationContextUtils.getWebApplicationContext(sc);
     if(cxt != null)
     {
         if (loginService == null)
         {
             // 注入service类
             loginService = cxt.getBean(LoginService.class);
         }
     }
 }

而不是直接使用下方代码注入

@Autowired
 private FooterService footerService;

 

2:在Spring Boot中,想在Filter中直接返回数据到前端,你可以使HttpServletResponse对象来设置响应状态码、头部信息和响应正文。

// 设置前端状态码和内容类型 设置响应状态码为 200。
 response.setStatus(HttpServletResponse.SC_OK);
 // 设置响应内容类型
 response.setContentType("application/json");
 response.setCharacterEncoding("UTF-8");
 
 // 直接写入响应体
 String jsonResponse = "{"code": "-150","msg": "您的账号在另一地点登录!"}";
 //System.out.println("filter-map:"+jsonResponse);
 response.getWriter().write(jsonResponse);
 return;

 

在上方中,FontendFilter实现了Filter接口,并在doFilter方法中提前终止了请求-响应循环,直接向客户端返回了JSON格式的响应数据。

 

以上大概就是我使用filter过滤器实现单点登录的基本流程。

 

有好的建议请在下方输入你的评论。