0
点赞
收藏
分享

微信扫一扫

《果然新鲜》电商项目(39)- SSO单点登录(登录功能完善)


文章目录

  • ​​引言​​
  • ​​1.存在的问题​​
  • ​​2. SSO SessionId 的改造​​
  • ​​3. 融合唯一登录​​
  • ​​4.效果演示​​
  • ​​5.显示登录的用户信息​​
  • ​​总结​​

引言

前面的文章已经把XXL-SSO集成到我们的「淘东电商」项目了,而且把登录界面也移植到了SSO服务,但是登录功能还是存在一些问题的,本文就来讲解一下如何完善这些问题。

1.存在的问题

首先看第一个问题:在登录成功后,查看浏览器登录成功后保存的​​SessionId​​​,可以看到保存到浏览器的​​sessionid​​​为​​27_c11ef89924a4465cbf395bfefcafc63d​​​,注意了前面的“​​27​​”为数据库的用户id,也就是说暴露了用户的id这是不安全的。

浏览器Cookie
《果然新鲜》电商项目(39)- SSO单点登录(登录功能完善)_tomcat
对应数据库用户
《果然新鲜》电商项目(39)- SSO单点登录(登录功能完善)_java_02
第二个问题:我们会发现如果一个用户登录了,如果用其它端,如Android登录,会自动挤掉已经登录的一端,简单的说就是不支持多端唯一登录,但是我们希望能多端能同时登录的。

2. SSO SessionId 的改造

先看​​XXL-SSO​​​生成​​sessionid​​​的代码,在登录接口:
《果然新鲜》电商项目(39)- SSO单点登录(登录功能完善)_spring_03
从上图,可以看到设置了​​​userid​​​为真实的用户id,然后使用帮助类来​​makeSessionId​​。

解决思路,只要把上面的​​userid​​​改为我们想要生成的token就可以了,修改后为如下:
《果然新鲜》电商项目(39)- SSO单点登录(登录功能完善)_spring_04
token如何而来呢?这时候顺便吧多端唯一登录顺便讲解了,请继续往下看。

3. 融合唯一登录

在前面的会员唯一登录中,我们实现了多端的唯一登录,其唯一登录的核心代码如下:

public BaseResponse<JSONObject> login(@RequestBody UserLoginInDTO userLoginInpDTO) {
// 1.验证参数
String mobile = userLoginInpDTO.getMobile();
if (StringUtils.isEmpty(mobile)) {
return setResultError("手机号码不能为空!");
}
String password = userLoginInpDTO.getPassword();
if (StringUtils.isEmpty(password)) {
return setResultError("密码不能为空!");
}
// 判断登陆类型
String loginType = userLoginInpDTO.getLoginType();
if (StringUtils.isEmpty(loginType)) {
return setResultError("登陆类型不能为空!");
}
// 目的是限制范围
if (!(loginType.equals(Constants.MEMBER_LOGIN_TYPE_ANDROID) || loginType.equals(Constants.MEMBER_LOGIN_TYPE_IOS)
|| loginType.equals(Constants.MEMBER_LOGIN_TYPE_PC))) {
return setResultError("登陆类型出现错误!");
}

// 设备信息
String deviceInfor = userLoginInpDTO.getDeviceInfor();
if (StringUtils.isEmpty(deviceInfor)) {
return setResultError("设备信息不能为空!");
}

// 2.对登陆密码实现加密
String newPassWord = MD5Util.MD5(password);
// 3.使用手机号码+密码查询数据库 ,判断用户是否存在
UserDo userDo = userMapper.login(mobile, newPassWord);
if (userDo == null) {
return setResultError("用户名称或者密码错误!");
}
TransactionStatus transactionStatus = null;
try {

// 1.获取用户UserId
Long userId = userDo.getUserId();
// 2.生成用户令牌Key
String keyPrefix = Constants.MEMBER_TOKEN_KEYPREFIX + loginType;

// 5.根据userId+loginType 查询当前登陆类型账号之前是否有登陆过,如果登陆过 清除之前redistoken


UserTokenDo userTokenDo = userTokenMapper.selectByUserIdAndLoginType(userId, loginType);
transactionStatus = manualTransaction.begin();
// // ####开启手动事务
if (userTokenDo != null) {
// 如果登陆过 清除之前redistoken
String oriToken = userTokenDo.getToken();
// 移除Token
generateToken.removeToken(oriToken);
int updateTokenAvailability = userTokenMapper.updateTokenAvailability(oriToken);
if (updateTokenAvailability < 0) {
manualTransaction.rollback(transactionStatus);
return setResultError("系统错误");
}
}

// 4.将用户生成的令牌插入到Token记录表中
UserTokenDo userToken = new UserTokenDo();
userToken.setUserId(userId);
userToken.setLoginType(userLoginInpDTO.getLoginType());
String newToken = generateToken.createToken(keyPrefix, userId + "");
userToken.setToken(newToken);
userToken.setDeviceInfor(deviceInfor);
int result = userTokenMapper.insertUserToken(userToken);
if (!toDaoResult(result)) {
manualTransaction.rollback(transactionStatus);
return setResultError("系统错误!");
}

// #######提交事务
JSONObject data = new JSONObject();
data.put("token", newToken);
manualTransaction.commit(transactionStatus);
return setResultSuccess(data);
} catch (Exception e) {
try {
// 回滚事务
manualTransaction.rollback(transactionStatus);
} catch (Exception e1) {
}
return setResultError("系统错误!");
}
}

代码的核心思想是通过会员的​​token​​表去控制,保证多个端(Android、IOS、H5等)登录成功,token表只能允许一个用户一个端登录只能有一条数据有效。

同时里面的代码token不仅放到了数据库,还托管到了​​Redis​​​服务器,因为​​XXL-SSO​​​框架已经帮我们做好了​​Redis​​​部分,所以这里的​​Redis​​不用处理了。最后改写的代码如下(注意加了事务):

@Transactional
public BaseResponse<UserLoginInOutDTO> ssoLogin(@RequestBody UserLoginInDTO userLoginInpDTO) {
// 1.验证参数
String mobile = userLoginInpDTO.getMobile();
if (StringUtils.isEmpty(mobile)) {
return setSSOResultError("手机号码不能为空!");
}
String password = userLoginInpDTO.getPassword();
if (StringUtils.isEmpty(password)) {
return setSSOResultError("密码不能为空!");
}
// 判断登陆类型
String loginType = userLoginInpDTO.getLoginType();
if (StringUtils.isEmpty(loginType)) {
return setSSOResultError("登陆类型不能为空!");
}
// 目的是限制范围
if (!(loginType.equals(Constants.MEMBER_LOGIN_TYPE_ANDROID) || loginType.equals(Constants.MEMBER_LOGIN_TYPE_IOS)
|| loginType.equals(Constants.MEMBER_LOGIN_TYPE_PC))) {
return setSSOResultError("登陆类型出现错误!");
}

// 设备信息
String deviceInfor = userLoginInpDTO.getDeviceInfor();
if (StringUtils.isEmpty(deviceInfor)) {
return setSSOResultError("设备信息不能为空!");
}
// 2.对登陆密码实现加密
String newPassWord = MD5Util.MD5(password);
// 3.使用手机号码+密码查询数据库 ,判断用户是否存在
UserDo userDo = userMapper.login(mobile, newPassWord);
if (userDo == null) {
return setSSOResultError("用户名称或者密码错误!");
}

//token操作

Long userId = userDo.getUserId();
// 2.生成用户令牌Key
String keyPrefix = Constants.MEMBER_TOKEN_KEYPREFIX + loginType;
UserTokenDo userTokenDo = userTokenMapper.selectByUserIdAndLoginType(userId, loginType);
if (userTokenDo != null) {
// 如果登陆过 清除之前redistoken
String oriToken = userTokenDo.getToken();
userTokenMapper.updateTokenAvailability(oriToken);
}

// 4.将用户生成的令牌插入到Token记录表中
UserTokenDo userToken = new UserTokenDo();
userToken.setUserId(userId);
userToken.setLoginType(userLoginInpDTO.getLoginType());
String newToken = keyPrefix + UUID.randomUUID().toString().replace("-", "");
userToken.setToken(newToken);
userToken.setDeviceInfor(deviceInfor);
userTokenMapper.insertUserToken(userToken);

userDo.setToken(newToken);

return setSSOResultSuccess(BeanUtils.doToDto(userDo, UserLoginInOutDTO.class));
}

看倒数第二行代码,把生成的token设置进了userDo,顺便解决了第一个问题里token获取的问题了。

4.效果演示

登录成功后,可以看到浏览器Cookie和Redis不再保存用户的id了:

Cookie
《果然新鲜》电商项目(39)- SSO单点登录(登录功能完善)_java_05
《果然新鲜》电商项目(39)- SSO单点登录(登录功能完善)_spring_06
Redis
《果然新鲜》电商项目(39)- SSO单点登录(登录功能完善)_tomcat_07
同时token表里,如下:
《果然新鲜》电商项目(39)- SSO单点登录(登录功能完善)_spring_08

5.显示登录的用户信息

@RequestMapping(value = {"/","/index.html"})
public String index(HttpServletRequest request, HttpServletResponse response, Model model) {
XxlSsoUser xxlUser = (XxlSsoUser) request.getAttribute(Conf.SSO_USER);
if (xxlUser != null && StringUtils.isNotEmpty(xxlUser.getUserid())) {
// 2.调用会员服务接口,查询会员用户信息
BaseResponse<UserOutDTO> userInfo = memberServiceFeign.getInfo(xxlUser.getUserid());
if (isSuccess(userInfo)) {
UserOutDTO data = userInfo.getData();
if (data != null) {
String mobile = data.getMobile();
// 对手机号码实现脱敏
String desensMobile = mobile.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
model.addAttribute("desensMobile", desensMobile);
}

}
}
return INDEX_FTL;
}

《果然新鲜》电商项目(39)- SSO单点登录(登录功能完善)_hibernate_09

总结

本文主要讲解登录功能的完善,完善了安全性和多端登录唯一性的问题。


举报

相关推荐

单点登录(sso)

0 条评论