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过滤器实现单点登录的基本流程。
有好的建议请在下方输入你的评论。