300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > 唯一登录 联合登录 单点登录——唯一登录

唯一登录 联合登录 单点登录——唯一登录

时间:2023-07-24 09:41:38

相关推荐

唯一登录 联合登录 单点登录——唯一登录

一、理论基础

(1)

理解redis和token的使用使用token主要是为了安全,还有就是可以让安卓、IOS、PC等多端登录。用户每一个端登陆成功之后,会对应生成一个token令牌(临时且唯一)存放在redis中作为redis的key ,value 作为userid存储;根据userId+loginType 查询当前登陆类型账号之前是否有登陆过,如果登陆过 清除之前redis token。如果是PC端,token存放在PC端 的cookie ;如果在安卓 或者IOS端,token 存放在本地文件中。当前存在那些问题? ——用户如果退出或者修改密码、忘记密码的情况 下,需要对token状态进行标识以下,防止被篡改

(3)@Transactional注解属于声明式事务 还是 编程式事务?

答:声明式事务。加该注解不能控制redis事务,所以涉及到redis的事务要自定义方法,使用编程式事务,在begin和commit之间即需要控制数据库事务也需要控制redis事务;调用begin方式时会同时开始数据库事务和redis事务,commit、callback时也是同时的。(编程式事务相对于声明式事务唯一的优点就是事务粒度更精细)

(2)Redis删除Token与数据库状态Token如何保持一致?

答:redis本身也是支持事务的,只需要让redis事务和数据库事务保持一致,双方都是同步的即可(答案在下文)

二、代码实战

2.1、唯一登录实战

例子:会员登录(防止重复登录)

(1)登陆唯一登录表设计

CREATE TABLE `user_token` (`id` int(11) NOT NULL AUTO_INCREMENT,`token` varchar(255) DEFAULT NULL,`login_type` varchar(255) CHARACTER SET utf8 DEFAULT NULL,`device_infor` varchar(255) DEFAULT NULL,`is_availability` int(2) DEFAULT NULL,`user_id` int(11) DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=2

(2)Mapper

public interface UserMapper {int register(UserDo userDo);UserDo existMobile(@Param("mobile") String mobile);UserDo login(@Param("mobile") String mobile, @Param("password") String password);UserDo findByUserId(@Param("userId") Long userId);}

public interface UserTokenMapper {UserTokenDo selectByUserIdAndLoginType(@Param("userId") Long userId, @Param("loginType") String loginType);int updateTokenAvailability(@Param("userId") Long userId, @Param("loginType") String loginType);int insertUserToken(UserTokenDo userTokenDo);}

(3)Dao

@Datapublic class UserTokenDo extends BaseDo {/*** id*/private Long id;/*** 用户token*/private String token;/*** 登陆类型*/private String loginType;/*** 设备信息*/private String deviceInfor;/*** 用户userId*/private Long userId;/*** 注册时间*/private Date createTime;/*** 修改时间**/private Date updateTime;}

@Datapublic class BaseDo {/*** 注册时间*/private Date createTime;/*** 修改时间**/private Date updateTime;/*** id*/private Long id;/*** 是否可用 0可用 1不可用*/private Long isAvailability;}

(4)生成token的工具类:GenerateToken

根据token获取redis中的value值

@Componentpublic class GenerateToken {@Autowiredprivate RedisUtil redisUtil;/*** 生成令牌* * @param prefix* 令牌key前缀* @param redisValue* redis存放的值* @return 返回token*/public String createToken(String keyPrefix, String redisValue) {return createToken(keyPrefix, redisValue, null);}/*** 生成令牌* * @param prefix* 令牌key前缀* @param redisValue* redis存放的值* @param time* 有效期* @return 返回token*/public String createToken(String keyPrefix, String redisValue, Long time) {if (StringUtils.isEmpty(redisValue)) {new Exception("redisValue Not nul");}String token = keyPrefix + UUID.randomUUID().toString().replace("-", "");redisUtil.setString(token, redisValue, time);return token;}/*** 根据token获取redis中的value值* * @param token* @return*/public String getToken(String token) {if (StringUtils.isEmpty(token)) {return null;}String value = redisUtil.getString(token);return value;}/*** 移除token* * @param token* @return*/public Boolean removeToken(String token) {if (StringUtils.isEmpty(token)) {return null;}return redisUtil.delKey(token);}}

(5)新增常量信息

// tokenString MEMBER_TOKEN_KEYPREFIX = "mayikt.member.login";// 安卓的登陆类型String MEMBER_LOGIN_TYPE_ANDROID = "Android";// IOS的登陆类型String MEMBER_LOGIN_TYPE_IOS = "IOS";// PC的登陆类型String MEMBER_LOGIN_TYPE_PC = "PC";// 登陆超时时间 有效期 90天Long MEMBRE_LOGIN_TOKEN_TIME = 77776000L;

(6)唯一登陆接口实现

DTO:

@Data@ApiModel(value = "用户登陆参数")public class UserLoginInpDTO {/*** 手机号码*/@ApiModelProperty(value = "手机号码")private String mobile;/*** 密码*/@ApiModelProperty(value = "密码")private String password;/*** 登陆类型 PC、Android 、IOS*/@ApiModelProperty(value = "登陆类型")private String loginType;/*** 设备信息*/@ApiModelProperty(value = "设备信息")private String deviceInfor;}

Service:

@Api(tags = "用户登陆服务接口")public interface MemberLoginService {/*** 用户登陆接口* * @param userEntity* @return*/@PostMapping("/login")@ApiOperation(value = "会员用户登陆信息接口")BaseResponse<JSONObject> login(@RequestBody UserLoginInpDTO userLoginInpDTO);}

Service的impl:

@RestControllerpublic class MemberLoginServiceImpl extends BaseApiService<JSONObject> implements MemberLoginService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate GenerateToken generateToken;@Autowiredprivate UserTokenMapper userTokenMapper;@Autowiredprivate RedisDataSoureceTransaction redisDataSoureceTransaction;@Autowiredprivate RedisUtil redisUtil;public BaseResponse<JSONObject> login(@RequestBody UserLoginInpDTO 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("用户名称或者密码错误!");}// 用户登陆Token Session 区别// 用户每一个端登陆成功之后,会对应生成一个token令牌(临时且唯一)存放在redis中作为rediskey value useridTransactionStatus transactionStatus = null;try {// 4.获取useridLong userId = userDo.getUserId();// 5.根据userId+loginType 查询当前登陆类型账号之前是否有登陆过,如果登陆过 清除之前redistokenUserTokenDo userTokenDo = userTokenMapper.selectByUserIdAndLoginType(userId, loginType);//开启以下事务transactionStatus = redisDataSoureceTransaction.begin();if (userTokenDo != null) {// 如果登陆过 清除之前redistokenString token = userTokenDo.getToken();// 如果开启redis事务的话,删除的时候 方法会返回falseBoolean removeToken = generateToken.removeToken(token);// 把该token的状态改为1int updateTokenAvailability = userTokenMapper.updateTokenAvailability(token);if (!toDaoResult(updateTokenAvailability)) {return setResultError("系统错误!");}}// .生成对应用户令牌存放在redis中// 1.插入新的tokenUserTokenDo userToken = new UserTokenDo();userToken.setUserId(userId);userToken.setLoginType(userLoginInpDTO.getLoginType());String keyPrefix = Constants.MEMBER_TOKEN_KEYPREFIX + loginType;String newToken = generateToken.createToken(keyPrefix, userId + "");userToken.setToken(newToken);userToken.setDeviceInfor(deviceInfor);int insertUserToken = userTokenMapper.insertUserToken(userToken);if (!toDaoResult(insertUserToken)) {redisDataSoureceTransaction.rollback(transactionStatus);return setResultError("系统错误!");}JSONObject data = new JSONObject();data.put("token", newToken);//没有任何问题才mit(transactionStatus);return setResultSuccess(data);} catch (Exception e) {try {//有问题则回滚redisDataSoureceTransaction.rollback(transactionStatus);} catch (Exception e2) {// TODO: handle exception}return setResultError("系统错误!");}}// 查询用户信息的话如何实现? redis 与数据库如何保证一致问题@Overridepublic BaseResponse<JSONObject> delToken(String token) {if (StringUtils.isEmpty(token)) {return setResultError("token不能为空!");}Boolean delKey = redisUtil.delKey(token);return delKey ? setResultSuccess("删除成功") : setResultError("删除失败!");}// redis 的值如何与数据库的值保持是一致性问题// @Transactional 不能控制redis的事务// redis 中是否存在事务 肯定是肯定是存在事务// 自定义方法 使用编程事务 begin(既需要控制数据库的事务也需要控制redis) commit}

疑问:查询用户信息的话如何实现? redis 与数据库如何保证一致问题?

参考:/RuiKe1400360107/article/details/103706472

以下代码其实可以封装成一个注解:

//开启一下事务

transactionStatus = redisDataSoureceTransaction.begin();

......

//没有任何问题才commit

mit(transactionStatus);

return setResultSuccess(data);

} catch (Exception e) {

try {

//有问题则回滚

redisDataSoureceTransaction.rollback(transactionStatus);

} catch (Exception e2) {

// TODO: handle exception

}

return setResultError("系统错误!");

Redis与 DataSource 事务封装:

@Component@Scope(ConfigurableListableBeanFactory.SCOPE_PROTOTYPE)public class RedisDataSoureceTransaction {@Autowiredprivate RedisUtil redisUtil;/*** 数据源事务管理器*/@Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;/*** 开始事务 采用默认传播行为* * @return*/public TransactionStatus begin() {// 手动begin数据库事务// 1.开启数据库的事务 事务传播行为TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());// 2.开启redis事务redisUtil.begin();return transaction;}/*** 提交事务* * @param transactionStatus* 事务传播行为* @throws Exception*/public void commit(TransactionStatus transactionStatus) throws Exception {if (transactionStatus == null) {throw new Exception("transactionStatus is null");}// 支持Redis与数据库事务同时提交mit(transactionStatus);}/*** 回滚事务* * @param transactionStatus* @throws Exception*/public void rollback(TransactionStatus transactionStatus) throws Exception {if (transactionStatus == null) {throw new Exception("transactionStatus is null");}// 1.回滚数据库事务 redis事务和数据库的事务同时回滚dataSourceTransactionManager.rollback(transactionStatus);// // 2.回滚redis事务// redisUtil.discard();}// 如果redis的值与数据库的值保持不一致话}

(7)根据Token查询用户信息

使用token向redis中查询userId

@RestControllerpublic class MemberServiceImpl extends BaseApiService<UserOutDTO> implements MemberService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate GenerateToken generateToken;@Overridepublic BaseResponse<UserOutDTO> existMobile(String mobile) {// 1.验证参数if (StringUtils.isEmpty(mobile)) {return setResultError("手机号码不能为空!");}// 2.根据手机号码查询用户信息 单独定义code 表示是用户信息不存在把UserDo userEntity = userMapper.existMobile(mobile);if (userEntity == null) {return setResultError(Constants.HTTP_RES_CODE_EXISTMOBILE_203, "用户信息不存在!");}// 3.将do转换成dtoreturn setResultSuccess(MeiteBeanUtils.doToDto(userEntity, UserOutDTO.class));}@Overridepublic BaseResponse<UserOutDTO> getInfo(String token) {// 1.验证token参数if (StringUtils.isEmpty(token)) {return setResultError("token不能为空!");}// 2.使用token查询redis 中的userIdString redisUserId = generateToken.getToken(token);if (StringUtils.isEmpty(redisUserId)) {return setResultError("token已经失效或者token错误!");}// 3.使用userID查询 数据库用户信息Long userId = TypeCastHelper.toLong(redisUserId);UserDo userDo = userMapper.findByUserId(userId);if (userDo == null) {return setResultError("用户不存在!");}// 可重构,将代码放入在BaseApiServicereturn setResultSuccess(MeiteBeanUtils.doToDto(userDo, UserOutDTO.class));}// token存放在PC端 cookie token 存放在安卓 或者IOS端 存放在本地文件中// 当前存在那些问题? 用户如果退出或者修改密码、忘记密码的情况 对token状态进行标识// token 如何防止伪造 真正其实很难防御伪造 尽量实现在安全体系 xss 只能在一些某些业务模块上加上必须验证本人操作}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。