商城项目-分布式高级-07-商城认证-用户登录注册

597 阅读3分钟

认证服务

环境搭建

image-20201214182524197

image-20201214182832361

image-20201214183051014

发送手机验证码

<script type="text/javascript" charset="utf-8">
$(function () {
    $("#sendCode").click(function () {
        // 1.给指定手机发送验证码
        // 2.倒计时 60s
        if ($(this).hasClass("disabled")) {

        } else {
            timeoutChangeStyle();
        }
    });
})

var num = 10;

function timeoutChangeStyle() {
    $("#sendCode").attr("class", "disabled")
    var str = num + "s后再次发送"
    if (num === 0) {
        str = "发送验证码";
        num = 10;
        $("#sendCode").attr("class", "");
    } else {
        // 1.给指定手机发送验证码

                $.get("/sms/sendSms?mobile="+$("#mobile").val())
                // 2.倒计时 60s
                timeoutChangeStyle();
    }
    $("#sendCode").text(str);
    num--
}
</script>

  <a id="sendCode">发送验证码</a>
@RestController
public class LoginController {

    @Autowired
    private SmsFeignService smsFeignService;
    @Autowired
    private StringRedisTemplate redisTemplate;

    @GetMapping("/sms/sendSms")
    public R sendSms(@RequestParam("mobile") String mobile) {
        // 1.接口防刷
        String s = redisTemplate.opsForValue().get(AutoConstant.SMS_CODE_CACHE_PREFIX + mobile);

        // 60s内不能在次发送
        if (s != null && System.currentTimeMillis() - Long.parseLong(s.split("_")[1]) < 60000) {
            return R.error();
        }

        // 生成验证码
        String code = UUID.randomUUID().toString().substring(0, 5) + "_" + System.currentTimeMillis();

        // 2.验证码再次校验  sms:code:13789983260,12345   10min有效期
        redisTemplate.opsForValue().set(AutoConstant.SMS_CODE_CACHE_PREFIX + mobile, code, 10, TimeUnit.MINUTES);
        return smsFeignService.sendSms(mobile, code.substring(0, 5));
    }

}
@RestController
@RequestMapping("/sms")
public class SmsController {
    @Autowired
    private Sms sms;

    /**
     * 发送短信验证码
     *
     * @param mobile 移动
     * @param code   代码
     * @return {@link R}
     */
    @GetMapping("/sendSms")
    public R sendSms(@RequestParam("mobile") String mobile,@RequestParam("code")  String code) {
        sms.sendSms(mobile, code);
        return R.ok();
    }
}
@Component
public class Sms {
    public  void sendSms(String mobile, String code) {
        String host = "http://dingxin.market.alicloudapi.com";
        String path = "/dx/sendSms";
        String method = "POST";
        String appcode = "25429856ccf4401eb7260bceb3fa65d9";
        Map<String, String> headers = new HashMap<String, String>();
        //最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
        headers.put("Authorization", "APPCODE " + appcode);
        Map<String, String> querys = new HashMap<String, String>();
        querys.put("mobile", mobile);
        querys.put("param", "code:" + code);
        querys.put("tpl_id", "TP1711063");
        Map<String, String> bodys = new HashMap<String, String>();

        try {
            HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
            System.out.println(response.toString());
            //获取response的body
            //System.out.println(EntityUtils.toString(response.getEntity()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

请求和页面的相互映射

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        /**
         * 去登录页面
         *
         *  @GetMapping("/login.html")
         *  public String loginHtml () {
         *  return "login";
         *}
         */
        registry.addViewController("/login.html").setViewName("login");
        registry.addViewController("/reg.html").setViewName("reg");
    }
}

用户注册功能

认证服务注册接口

/**
 * 注册用户
 *
 * @return {@link String}
 */
@PostMapping("/register")
public String register(@Valid UserRegisterVo vo, BindingResult result) {
    String goUrl = "redirect:/login.html";

    if (result.hasErrors()) {
        goUrl = "redirect:http://localhost:7777/reg.html";
    } else {
        // 1.校验验证码
        String code = vo.getCode();
        String mobile = vo.getMobile();
        String redisCode = redisTemplate.opsForValue().get(AutoConstant.SMS_CODE_CACHE_PREFIX + mobile);
        if (StringUtils.isNoneBlank(redisCode) && code.equals(redisCode)) {
            goUrl = "redirect:http://localhost:7777/reg.html";
        }else {
            // 2.验证码正确
            redisTemplate.delete(AutoConstant.SMS_CODE_CACHE_PREFIX + mobile);      // 删除验证码
            // 3.调用用户服务保存用户信息
            memberFeignService.register(vo);
        }
    }
    // 重定向到登录页面
    return goUrl;
}

远程调用用户服务注册用户

/**
 * 注册
 *
 * @return {@link R}
 */
@PostMapping("/register")
public R register(@RequestBody UserRegisterVo vo) {
    try {
        memberService.register(vo);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return R.ok();
}

@Override
public void register(UserRegisterVo vo) {
    // 检查用户是否唯一
    checkMobile(vo.getMobile());
    checkUsername(vo.getUsername());

    // 保存
    MemberEntity memberEntity = MemberEntity.builder()
        .username(vo.getUsername())
        .password(vo.getPassword())
        .mobile(vo.getMobile())
        .levelId(1L)
        .build();
    save(memberEntity);
}

@Override
public boolean checkMobile(String mobile) {
    getEnt(mobile, "mobile");
    return true;
}

public void getEnt(String check, String field) {
    MemberEntity one = getOne(new QueryWrapper<MemberEntity>().eq(field, check));
    if (one != null) {
        throw new UserInfoExistException(one);
    }
}

public class UserInfoExistException extends RuntimeException {
    public UserInfoExistException(MemberEntity member) {
        super(check(member));
    }

    public static String check(MemberEntity member) {
        String res = "";
        if (member.getUsername() != null) {
            res = "用户名已经存在";
        } else if (member.getMobile() != null) {
            res = "手机号码已经存在";
        } else if (member.getEmail() != null) {
            res = "邮箱已经存在";
        }
        return res;
    }
}



@Data
public class UserRegisterVo {

    @NotEmpty(message = "用户名不能为空")
    @Length(min = 6,max = 18,message = "用户名必须在6-18位之间")
    private String username;
    @NotEmpty(message = "密码不能为空")
    @Length(min = 6,max = 18,message = "密码必须在6-18位之间")
    private String password;
    @NotEmpty(message = "手机号不能为空")
    @Pattern(regexp = "^[1][0-9]{10}$",message = "手机号格式不正确")
    private String mobile;
    @NotEmpty(message = "验证码不能为空")
    private String code;
}

密码加密

image-20201216175102764

image-20201216180958966

功能测试

image-20201216182555045

image-20201216182602945

image-20201216185127008

image-20201216185133376

image-20201216185138590

用户登录

@PostMapping("/login")
public String login(UserLoginVo vo) {

    // 远程
    R login = memberFeignService.login(vo);
    if (login.getCode() != 0) {
        return "redirect:/login.html";
    }
    return  "redirect:http://localhost:12000";
}

image-20201216192041495

@PostMapping("/login")
public R login(@RequestBody UserLoginVo vo) {
    MemberEntity member = memberService.login(vo);
    if (member == null) {
        return R.error("登录失败");
    }
    return R.ok();
}



@Override
public MemberEntity login(UserLoginVo vo) {
    String loginName = vo.getLoginName();
    String rawPasswd = vo.getPassword();
    MemberEntity dbMember = getOne(new QueryWrapper<MemberEntity>()
                                   .eq("username", loginName)
                                   .or()
                                   .eq("mobile", loginName)
                                   .or()
                                   .eq("email", loginName)
                                  );
    if (dbMember != null) {
        // 密码匹配
        String encryptedPassword = dbMember.getPassword();
        if (new BCryptPasswordEncoder().matches(rawPasswd, encryptedPassword)) {
            return dbMember;
        }
    }
    return null;
}

社交登录

OAuth2.0

image-20201217185328812

todo

记录登录状态-分布式session

统一存储

image-20201220182551399

<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

image-20201220184233085

image-20201220184242243

image-20201220185215550

image-20201220185223964

修改问题

image-20201220193702610

image-20201220193716948

image-20201220193730874

image-20201220193748417

自定义session配置

@Configuration
public class SessionConfig {

    @Bean
    public RedisSerializer<Object>redisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }

    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer cookie = new DefaultCookieSerializer();
        cookie.setDomainName("localhost"); // cookie作用域,由于我没有搭建域名服务器,所以localhost就可以了
        return cookie;
    }
}

原理

image-20201221172059220

@Override
public HttpSessionWrapper getSession(boolean create) {
   HttpSessionWrapper currentSession = getCurrentSession();
   if (currentSession != null) {
      return currentSession;
   }
   S requestedSession = getRequestedSession();
   if (requestedSession != null) {
      if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
         requestedSession.setLastAccessedTime(Instant.now());
         this.requestedSessionIdValid = true;
         currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
         currentSession.markNotNew();
         setCurrentSession(currentSession);
         return currentSession;
      }
   }
   else {
      // This is an invalid session id. No need to ask again if
      // request.getSession is invoked for the duration of this request
      if (SESSION_LOGGER.isDebugEnabled()) {
         SESSION_LOGGER.debug(
               "No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
      }
      setAttribute(INVALID_SESSION_ID_ATTR, "true");
   }
   if (!create) {
      return null;
   }
   if (SESSION_LOGGER.isDebugEnabled()) {
      SESSION_LOGGER.debug(
            "A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
                  + SESSION_LOGGER_NAME,
            new RuntimeException("For debugging purposes only (not an error)"));
   }
   S session = SessionRepositoryFilter.this.sessionRepository.createSession();
   session.setLastAccessedTime(Instant.now());
   currentSession = new HttpSessionWrapper(session, getServletContext());
   setCurrentSession(currentSession);
   return currentSession;
}

单点登录

  1. 一处登录,处处登录。
  2. 统一session的名字