# second-shop **Repository Path**: codergjw/second-shop ## Basic Information - **Project Name**: second-shop - **Description**: 随着大学生购买力的增强,产品的升级 换代更新加快,大学校园存在着大量的闲置物品,如书籍、衣物、电子产品等。该平台基于小程序提供前台使用,基于Vue + Springboot 来实现后台管理平台。 - **Primary Language**: Java - **License**: GPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-09-21 - **Last Updated**: 2023-07-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: 二手平台, 小程序 ## README # 校园快转 ## 1、短信登录 使用shrio框架实现多种登录策略,提高系统安全性和提高用户体验性。使用 Hash 代替 String 存储用户信息,节约了内存并便于单字段的修改,同时使用 expire 来设定验证码的过期策略。 为了提高用户的体验感和项目的健壮性使用了多种登录策略 => 多Realm认证 - 短信登录 - 密码登录 ### 1.1、短信登录优缺点 **优点** : - 确保资料的真实性 - 防止密码泄露,提高账户安全性 - 防止恶意编程进行注册 - 保证真实性 - 降低系统数据库的负载 **缺点** : - 登录过程可能比较繁琐 - 短信验证码可能造成及时性不稳定 - 明文发送 可能存在验证码短信的泄露 - 可能被用户的手机拦截 - 更换手机需要验证短信 可能收不到 所以我们使用了多种登录策略来实现登录功能。 ### 1.2、登录流程 使用Shiro来实现多重策略来登录。 1、Controller层定义三个方法来作为接收方法 发送验证码方法 ```java /** * 验证码操作业务 点击获取验证码按钮 * @param userPhone * @return */ @RequestMapping(value = "/phone/code", method = RequestMethod.POST) public ResultEntity sendCode(@RequestParam("userPhone") String userPhone) { // 这里通过 默认添加数据来实现 // 判断当前账号是否存在 if (userService.checkUserByUserPhone(userPhone)) { // 账号存在 return ResultEntity.error(500, "该手机号已存在!"); } // 判断当前账号是否超过五次 if (redisService.hasKey("time:" + userPhone)) { // 存在该账号说明已经发生过一次请求验证 // 判断请求是否超过5次 大于等于5次就不能再发生请求 第二天刷新 if (redisService.getInteger("time:" + userPhone) >= 5) { return ResultEntity.error("你的手机号当前超过验证次数,请你第二天再执行请求!"); } // 如果没有超过则再进行操作 } else { // 当前还未进行短信验证 设置当前剩余秒的timeout redisService.setInteger("time:" + userPhone, 0, DateUtils.getDaysOfDay()); } // 发送短信验证 String code = ""; try { code = msgService.send(userPhone); } catch (Exception e) { e.printStackTrace(); return ResultEntity.error(500, "服务器异常!发送短信验证失败!"); } // 实现一次自增 设置自增步长 redisService.incr("time:" + userPhone, 1); // 将code存储到redis中 有效期为5分钟 如果在5分钟内重新点击会覆盖原有的 同时刷新有效期 redisService.setString("code:" + userPhone, code, 5 * 60); return ResultEntity.ok(); } ``` 使用Redis来存储验证码 同时使用Redis的过期策略 来实现用户获取验证码并且设定有效期。 同时还进行验证码的次数锁定,如果在一个周期内发送验证码次数过多则将该手机号的发送验证码服务冻结。 为什么需要对验证码服务进行冻结? 统计验证码服务可以对恶意攻击请求实现有效的控制。设置有效期则是可以提高用户的体验感,因为如果由于网络问题或者用户没有收到验证码短信,则当有效期外可以重新发送请求获取验证码。 短信登录方法 ```java /** * 手机验证码登录 */ @RequestMapping("/login-phone") public ResultEntity loginByPhone(@RequestBody Map map){ String phone = (String)map.get("phone"); String code = (String)map.get("code"); System.out.println(phone+"---->"+code); UserToken token = new UserToken(null ,phone.toCharArray(),LoginType.USER_PHONE.getType(), code); System.out.println("token---->"+token); return shiroLogin(token,LoginType.USER_PHONE); } public ResultEntity shiroLogin(UserToken token,LoginType loginType){ UserEntity user = null; String userName = null; String phone = null; try { // 登录不在该处处理,交由shiro处理 Subject subject = SecurityUtils.getSubject(); System.out.println("subject-------->"+subject); if(LoginType.USER_PASSWORD.equals(loginType)){ userName = token.getUsername(); user = userService.getUserByAccountDB(userName); }else if(LoginType.USER_PHONE.equals(loginType)){ phone = token.getUsername(); user = userService.getUserByAccountDB(phone); } System.out.println(phone+"================="+userName); // 出现异常 subject.login(token); if (subject.isAuthenticated() && user!=null) { JSON json = new JSONObject(); ((JSONObject) json).put("token", subject.getSession().getId()); ((JSONObject) json).put("user",user); return ResultEntity.ok().put("data", json); }else{ return ResultEntity.error(); } } catch (Exception e) { return ResultEntity.error(); } } ``` 用户将手机号和验证码发送到后端,后端将相关数据封装到UserToken类中同时将登录发送也存储到UserToken中,然后再获取到Subject主体来执行相关的权限和认证功能。 Realm 策略模式 自定义多Realm登录策略 ```java /** * 自定义多realm登录策略 */ public class MyModularRealmAuthenticator extends ModularRealmAuthenticator { @Override protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { // 判断getRealms()是否返回为空 assertRealmsConfigured(); // 所有Realm Collection realms = getRealms(); // 登录类型对应的所有Realm HashMap realmHashMap = new HashMap<>(realms.size()); for (Realm realm : realms) { realmHashMap.put(realm.getName(), realm); } UserToken token = (UserToken) authenticationToken; System.out.println("token------>"+token); // 登录类型 String loginType = token.getLoginType(); if (realmHashMap.get(loginType) != null) { return doSingleRealmAuthentication(realmHashMap.get(loginType), token); } else { return doMultiRealmAuthentication(realms, token); } } } ``` ## 2、订单管理 ## 3、业务优化 ## 4、安全拦截