1.什么是单点登陆
同一家公司不同应用使用同一套登陆系统叫做单点登陆系统
2.单点登陆的引出
比如QQ号可以登陆很多游戏,而且这些游戏都是腾讯公司的,所以不再开发新的登陆模块,QQ就可以登陆多个应用.在写的过程中,我们注重用户体验,考虑用户体验的时候我们的用户名和密码的判断都是很精准的。没有用户名或者密码字样的出现。
3.代码实现
/**
* 登陆
* @param username 帐号
* @param password 密码
* @param ReturnUrl 回调地址
* @param request 浏览器--服务器
* @param response 服务器--浏览器
* @param model springmvc模型
* @return 请求转发至登陆页面或重定向到回调地址
*/
@RequestMapping(value = "/Login.aspx", method = RequestMethod.POST)
public String login(String username, String password, String ReturnUrl, HttpServletRequest request,HttpServletResponse response, Model model) {
// 用户名不能为空
if (null != username) {
// 密码不能为空
if (null != password) {
// 用户名必须正确
Buyer buyer = buyerService.selectBuyerByUserName(username);
if (buyer != null) {
// 密码必须正确
if (buyer.getPassword().equals(encodingPassword(password))) {
// TODO 密码不正确3次之后出现 验证码 不能为空 验证码 必须正确
// 保存用户信息到远程session 单点登陆解决方案
sessionProviderService.setAttributeForUserName
(RequestUtils.getCSESSIONID(request,response),buyer.getUsername());
return "redirect:" + ReturnUrl;
} else {
model.addAttribute("error", "用户名必须正确");
}
} else {
model.addAttribute("error", "用户名必须正确");
}
} else {
model.addAttribute("error", "密码不能为空");
}
} else {
model.addAttribute("error", "用户名不能为空");
}
return "login";
}
同样在分布式架构中 在数据库中查询用户名也不是一个好的选择 ,因为不再是一个数据库,后期会搭建数据库集群,数据的存放是根据id存放的,在数据库中直接查询 用户名必须正确 buyerService.selectBuyerByUserName(username); 这一条SQL语句是十分浪费性能的,用户姓名是一个varchar字段,首先是不能建立比较好的索引策略,同时前台也无法传入正确的用户id。我们应该根据用户的id去查询用户,这样只会查询相应的数据库而不会全库扫描。
用户名查询解决方案
@Override
public Buyer selectBuyerByUserName(String username) {
Buyer buyer = null;
// redis 中查找用户id
String id = jedis.hget("user", username);
if (id != username) {
// 用户查找对象
buyer = buyerDao.selectByPrimaryKey(Long.parseLong(id));
}
return buyer;
}
redis 中存放的是用户的类型是哈希 key 是用户名称 value 是用户的id 因为redis 是单线程并且是一个nosql数据库,查询速度非常的快,因此将用户存入redis 是在用户注册时候进行的。
redis 中命令如下 hmset username zhangsan 1
/**
* 令牌工具类
* @author ZhuPengWei
* @date 2017年11月7日
*/
public class RequestUtils {
/**
* 生成令牌
* @param request 浏览器-服务器
* @param response 服务器-浏览器
* @return 令牌
*/
public static String getCSESSIONID(HttpServletRequest request, HttpServletResponse response) {
// 浏览器中有没有对应cookie CSSESIONID
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
if (Constant.COOKIE_CSSESIONID.equals(cookie.getName())) {
// 存在cookie
return cookie.getValue();
}
}
// 没有 创建一个令牌
String csession = UUID.randomUUID().toString().replaceAll("-", "");
Cookie cookie = new Cookie(Constant.COOKIE_CSSESIONID, csession);
cookie.setPath("/");
// 0立即失效 -1关闭浏览器失效
cookie.setMaxAge(-1);
// 写在浏览器之中
response.addCookie(cookie);
return csession;
}
}
和大多公司单点登录解决方案一样,模拟request.getSession(),做出了一个假的session 存入了cookie中,
这样 服务器可以通过这个令牌在redis中获取数据或者设置数据了
/**
* Session业务层接口
* 提供session
* 获取session
* @author ZhuPengWei
* @date 2017年11月7日
*/
public interface SessionProviderService {
/**
* 设置远程session
* @param key 令牌
* @param userName 用户名
*/
public void setAttributeForUserName(String key, String userName);
/**
* 从redis中取出用户名
* @param key 令牌
*/
public String getAttributeUserNameByRedis(String key);
}
package open.shopping.service.user;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import open.shopping.common.web.Constant;
import redis.clients.jedis.Jedis;
/**
* Session业务层实现类
* 提供session
* 获取session
* @author ZhuPengWei
* @date 2017年11月7日
*/
@Service("sessionProviderService")
public class SessionProviderServiceImpl implements SessionProviderService {
@Autowired
private Jedis jedis;
@Override
public void setAttributeForUserName(String key, String userName) {
jedis.set(Constant.USER_SESSION + key, userName);
// 设置存活时间为30分钟
jedis.expire(Constant.USER_SESSION + key, 60 * 30);
jedis.close();
}
@Override
public String getAttributeUserNameByRedis(String key) {
String userName = jedis.get(Constant.USER_SESSION + key);
if (null != userName) {
// 设置存活时间为30分钟
jedis.expire(Constant.USER_SESSION + key, 60 * 30);
}
jedis.close();
return userName;
}
}
是不是觉得单点登录其实也很简单。