在上一篇搭建基础框架基础上,实现一下简单的单点登录。
简单思路:用户访问客户端时发现用户未登录,跳转到服务器的统一登录中心,在统一登录中心登录后,从服务器自动跳回客户端的访问地址,此时客户端发现用户已经登录,即可访问。
准备:redis 数据库, 上一篇中利用简单基础框架搭建的项目(可命名为 sso_client 和 sso_server 两个项目)
一 。 sso_server 服务端
1.既然要用spring-session 代替系统自带的session 和 利用 redis 存储session的信息,那么首先还是要导入依赖,在 sso_client 和 sso_server pom中加入 :
<!-- spring-session 替代 原有session-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>
<!-- redis 连接redis-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- redis 自动存取数据-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.在 sso_client 和 sso_server 的配置文件中加入配置信息 ,将sso_server的端口改为 8088, sso_client 端口为 8089
#redis配置:
spring.session.store-type=redis
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
#是否开启基于session的统一登录
session.mode=remote
#sso统一登录地址 (本地host文件中加入 127.0.0.1 buc.sso.com)
sso.address=http://buc.sso.com:8088/ssoLogin.htm
3. 在 sso_server 统一登录中心中 加入 cookie的 配置文件,存储session对象信息的 Operator 文件 , 统一登录中心的实现方法 LoginController ,统一登录中心 vm文件 四个文件
代码分别如下:
1) cookie 配置 主要配置了统一的cookie 域名
@Configuration
public class CookieConfiguration {
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setDomainName("sso.com");
serializer.setCookiePath("/");
return serializer;
}
}
2 ) Operator 文件 注意要实现序列化,利用redis 存储信息时要 对key,value 进行序列化与反序列化
public class Operator implements Serializable {
/** */
private static final long serialVersionUID = 1L;
private long id;
private String operatorId;
private String operatorName;
..... get/set方法
}
3 ) 统一登录中心实现方法 ,主要为登录页面,及登录的校验方法
@Controller
public class LoginController {
private Logger logger = LoggerFactory.getLogger(LoginController.class);
@RequestMapping(value = "/ssoLogin.htm", method = RequestMethod.GET)
public String login(HttpServletRequest request, HttpServletResponse response) {
return "login";
}
@RequestMapping(value = "/ssoLogin.json", method = RequestMethod.POST)
@ResponseBody
public ModelMap login(String userName, String password, HttpServletRequest request) {
ModelMap model = new ModelMap();
if ("zlf".equals(userName) && "123456".equals(password)) {
HttpSession session = request.getSession();
Operator operator = new Operator();
operator.setOperatorId("111");
operator.setOperatorName(userName);
session.setAttribute("OPERATOR_SESSION_ID", operator);
logger.info("----共享session-----");
model.addAttribute("code", "true");
model.addAttribute("message", "登录成功");
return model;
} else {
model.addAttribute("code", "false");
model.addAttribute("message", "账号或者密码有误");
return model;
}
}
}
4 ) login.vm
<!DOCTYPE html>
<html>
<head>
<title>测试sso统一登录中心</title>
</head>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js" ></script>
<body>
<div class="container">
<!-- 登录框 -->
<div class="login">
<div class="usernameDiv">
<input class="input username" type="text" value="" />
</div>
<div class="passwordDiv">
<input class="input pwd" type="password" value=""/>
</div>
<div class="loginButton">
<input class="inputLogin" type="button" value="登 录" />
</div>
</div>
</div>
</body>
<script type="text/javascript">
$(document).ready(function(){
//登录的点击事件
$(".inputLogin").click(function(){
var username = $(".username").val();
var password = $(".pwd").val();
var callbackURL = "";
$.post("/ssoLogin.json",
{
"userName": username,
"password": password
},
function(data,status){
callbackURL = getUrlParam("callbackURL");
if(data.code=="true"){
window.location.href=callbackURL;
}else{
alert("账号或密码错误!");
}
});
});
//function方法
function getUrlParam(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); //构造一个含有目标参数的正则表达式对象
var r = window.location.search.substr(1).match(reg); //匹配目标参数
if (r != null) return unescape(r[2]); return null; //返回参数值
}
});
</script>
</html>
二 。 sso_client 客户端
1. 加入与sso_server相同的 pom依赖 和配置文件后 , 添加 如下文件 ,其中 CookieConfiguration 与 sso_server中相同, 添加的Operator 与sso_server相同,且其就是sso_server中的 类文件( 此处我踩了个坑,如果Operator 包名为 test_sso_client_content 客户端自己的 Operator 文件,那么会出现 Operator 对象的反序列化的问题 , 网上看了很多办法,有 在SpringBootApplication中加入 bean 修改 redis 的key,value 序列化方法,有自定义系列化工具的 , 但好像都没有用 , 如果有人发现可以,欢迎指导。),
SessionConfiguration 和 SSOConfiguration 文件代码如下 :
@Configuration
public class SSoConfiguration {
@Autowired
private Environment env;
@Bean
@ConditionalOnProperty(name = "session.mode", havingValue = "remote")
public FilterRegistrationBean getFilterRegistrationBean() {
String mode = env.getProperty("session.mode");
String SSO_SERVER_URL = env.getProperty("sso.address");
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
if (StringUtils.equals(mode, "remote")) {
//共享session
//自定义过滤器
SessionConfiguration myFilter = new SessionConfiguration();
filterRegistrationBean.setFilter(myFilter);
//设置参数
filterRegistrationBean.addInitParameter("SSO_SERVER_URL", SSO_SERVER_URL);
//过滤地址
List<String> urlPatterns = new ArrayList<String>();
urlPatterns.add("/*");
filterRegistrationBean.setUrlPatterns(urlPatterns);
}
return filterRegistrationBean;
}
}
@EnableRedisHttpSession
public class SessionConfiguration implements Filter {
public static Logger logger = LoggerFactory.getLogger(SessionConfiguration.class);
private String sso_server_url = "";
/**
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.sso_server_url = filterConfig.getInitParameter("SSO_SERVER_URL");
if (StringUtils.isBlank(sso_server_url)) {
throw new IllegalArgumentException("SSOFilter中SSO_SERVER_URL参数初始化异常");
}
}
/**
* @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
*/
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String urL = request.getRequestURL().toString();
HttpSession session = request.getSession();
if (session != null && session.getAttribute("OPERATOR_SESSION_ID") != null) {
chain.doFilter(req, res);
} else {
//客户端发现未登录重定向
//跳转至sso认证中心
String callbackURL = request.getRequestURL().toString();
StringBuilder url = new StringBuilder();
url.append(sso_server_url).append("?callbackURL=").append(callbackURL);
response.sendRedirect(url.toString());
}
}
/**
* @see javax.servlet.Filter#destroy()
*/
@Override
public void destroy() {
}
}
三。测试
1. 启动本地redis ,连接本地redis
2.启动sso_server 和sso_client ,浏览器中访问 客户端地址 http://buc.sso.com:8089/query/querySample.htm 跳转到服务器统一登录中心
在redis中执行 keys * 查看发现已经存入了浏览器的 session ,接着输入hgetall "存储的键值" 发现没有我们要的 SESSION_OPERATOR_ID,说明还没有用户登录信息。
3. 输入账号密码: zlf 123456 ,跳转回来客户端并且成功访问客户端
再次输入 keys * 查看 键值 ,输入hgetall "键值" ,发现redis 中已经存入了登录信息
4. 关闭浏览器.重新输入地址: http://buc.sso.com:8089/query/querySample.htm,发现不用登录,直接进入客户端,因为session 信息存在redis 中 默认有效30分钟
四。总结:
1. 如果在 sso_client 和 sso_server 中分别建立 Operator 对象,会出现redis 数据反序列化问题.
需要在sso_client 中 使用 sso_server 的 Operator
2. 遇到问题如果百度不到相似的经历,没有人出现过这种问题,那多半是自己粗心大意 代码写的不对,(纯属个人意见)
最后,觉得还过得去的请点个赞,欢迎大家交流指正!