单点登录,就是通过一个用户认证系统,打通所有关联子系统的认证,从而达到安全高效访问子系统的目的。而session共享是实现的单点登录的一种方式。
一、真伪单点登录
何为真单点,何为伪单点呢?其实理解起来也很简单。
伪单点,举个例子,比如一般中小公司里的内部系统,这类系统一般只有一套用户认证和权限系统,各子系统之间也都是相互信任的,对用户的验证方式都是统一的,这类就是伪单点;
像两个比较大型的系统之间的整合,比如A公司的a系统要整合到B公司的b系统中,两个系统之前肯定不是统一的验证方式,信任什么的更不用提了,这类单点就稍微复杂一些,也就是真单点。
二、如何解决单点问题
关于解决单点问题,其实有比较成熟的策略了,适合各种简单或复杂的业务场景。
1、简单些的,比如,使用shiro进行会话管理;
2、服务器间的session复制;
3、shiro+cas实现;
4、使用专业的SSO产品。
一般来说,shiro方式实现就可以了,大型企业或需要实现真单点的,一般可能会使用到专业的SSO产品。
三、实现原理
![fc855e751eef4021ddf306489f050edc.png](https://i-blog.csdnimg.cn/blog_migrate/75614faac42b68d5000228bc640de3c0.jpeg)
从上图可以看出,客户端在访问系统时:
1、要先通过认证服务器,认证服务器在用户登陆后,会颁发一个ticket;
2、客户端拿到ticket后,再去访问子系统;
3、若子系统无法识别,或检测到非法,会再次转到认证服务器,让客户端再次登录。
客户端get到ticket后,在到达每个子系统之前,都会被检测一下ticket,也就是检查用户的登录信息。
四、实现案例
假设我们有三个子系统:Project1;Project2,Project 3,此时,访问每个子系统都会有自己的会话。只要他们之间互认了sessionID,我们就能任意在个子系统之间穿梭访问。
![9d2cb93668ed3d29a69770103d883bde.png](https://i-blog.csdnimg.cn/blog_migrate/77ae1672d724d042cc6d1a05ea3c9c31.jpeg)
这是SSO的一张流程图。比较详细的描述了用户从登陆页面到访问用户中心、订单系统的过程,采用了redis保存会话的共享机制,也是当前比较常用的一套实现方案。
该方案具有明显的好处:redis存取速度快,不会网出现多个节点session复制的问题,且效率高。
查询数据校验:
@Service
public class UserRegisterServiceImpl implements UserRegisterService {
@Autowired
private TbUserMapper userMapper;
@Override
public SystemResult checkInfo(String value, String type) throws Exception {
boolean result = false;
//type为类型,可选参数1、2、3分别代表username、phone、email
if ("1".equals(type)) {
result = checkUserName(value);
} else if ("2".equals(type)) {
result = checkPhone(value);
} else if ("3".equals(type)) {
result = checkEmail(value);
}
//返回结果
if (result) {
return TaotaoResult.ok(result);
}
throw new DataException("此数值已经存在");
}
private boolean checkUserName(String userName) throws Exception {
//创建查询条件
TbUserExample example = new TbUserExample();
Criteria criteria = example.createCriteria();
criteria.andUsernameEqualTo(userName);
List<TbUser> list = userMapper.selectByExample(example);
//判断结果中是否存在
if (list == null || list.isEmpty()) {
return true;
}
return false;
}
private boolean checkPhone(String phone) throws Exception {
//创建查询条件
TbUserExample example = new TbUserExample();
Criteria criteria = example.createCriteria();
criteria.andPhoneEqualTo(phone);
List<TbUser> list = userMapper.selectByExample(example);
//判断结果中是否存在
if (list == null || list.isEmpty()) {
return true;
}
return false;
}
private boolean checkEmail(String email) throws Exception {
//创建查询条件
TbUserExample example = new TbUserExample();
Criteria criteria = example.createCriteria();
criteria.andEmailEqualTo(email);
List<TbUser> list = userMapper.selectByExample(example);
//判断结果中是否存在
if (list == null || list.isEmpty()) {
return true;
}
return false;
}
}
用户注册实现:
@Override
public SystemResult register(TbUser user) throws Exception {
//有效性验证
if (StringUtils.isBlank(user.getUsername())) {
throw new DataException("用户名不能为空");
}
if (StringUtils.isBlank(user.getPassword())) {
throw new DataException("密码不能为空");
}
if (StringUtils.isBlank(user.getPhone())) {
throw new DataException("手机不能为空");
}
//转换md5
user.setPassword(DigestUtils.md5DigestAsHex(user.getPassword().getBytes()));
//完善user信息
user.setCreated(new Date());
user.setUpdated(new Date());
//添加到数据库
userMapper.insert(user);
return SystemResult.ok();
}
登录实现:
@Service
public class UserLoginServiceImpl implements UserLoginService {
@Autowired
private TbUserMapper userMapper;
@Autowired
private JedisCluster jedisCluster;
@Value("${USER_TOKEN_KEY}")
private String USER_TOKEN_KEY;
@Value("${SESSION_EXPIRE_TIME}")
private Integer SESSION_EXPIRE_TIME;
@Override
public SystemResult login(String username, String password) throws Exception {
//根据用户名查询用户信息
TbUserExample example = new TbUserExample();
Criteria criteria = example.createCriteria();
criteria.andUsernameEqualTo(username);
List<TbUser> list = userMapper.selectByExample(example);
if (null == list || list.isEmpty()) {
throw new DataException("用户不存在");
}
//核对密码
TbUser user = list.get(0);
if(!DigestUtils.md5DigestAsHex(password.getBytes()).equals(user.getPassword())){
throw new DataException("密码错误");
}
//登录成功,把用户信息写入redis
//生成一个用户token
String token = UUID.randomUUID().toString();
jedisCluster.set(USER_TOKEN_KEY + ":" + token, JsonUtils.objectToJson(user));
//设置session过期时间
jedisCluster.expire(USER_TOKEN_KEY + ":" + token, SESSION_EXPIRE_TIME);
return SystemResult.ok(token);
}
}
用户查询:
@Service
public class UserTokenServiceImpl implements UserTokenService {
@Autowired
private JedisCluster jedisCluster;
@Value("${USER_TOKEN_KEY}")
private String USER_TOKEN_KEY;
@Value("${SESSION_EXPIRE_TIME}")
private Integer SESSION_EXPIRE_TIME;
/**
* 根据token取用户信息
*/
@Override
public SystemResult getUserByToken(String token) throws Exception {
//从redis中取用户信息
String userJson = jedisCluster.get(USER_TOKEN_KEY + ":" + token);
if (StringUtils.isBlank(userJson)) {
throw new DataException("该用户已过期");
}
//把json转换成user对象
TbUser user = JsonUtils.jsonToPojo(userJson, TbUser.class);
//更新用户有效期
jedisCluster.expire(USER_TOKEN_KEY + ":" + token, SESSION_EXPIRE_TIME);
return SystemResult.ok(user);
}
}
后续登录拦截器的配置,省略
————————————————
版权声明:本文为CSDN博主「追着蜗牛打」的原创文章, 非本人所有。
原文链接:https://blog.csdn.net/u010191243/article/details/74015463