【项目学习】谷粒商城学习记录7 - 认证服务

【项目学习】谷粒商城学习记录7 - 认证服务


一、环境搭建 & 准备工作

1.1. 创建新模块


1.2. 配置依赖 pom.xml文件

  • 引入common模块,排除gulimall-common包的mybatis-plus
  • 将模块添加到注册中心
    • 添加配置信息
    • 添加服务发现注解
  • 启动类添加远程调用注解 @EnableFeignClients
  • 测试:服务成功加入nacos
  • 关闭thtmeleaf缓存
    spring.thymeleaf.cache=false
    

1.3. 静态页面搭建

  • 将注册页面和登录页面放在服务资源目录下
    • 登录页面改名login.html, 注册页面改名reg.html
  • host文件新增域名地址
  • 登录、注册页面动静分离,将静态资源移到nginx下
  • 修改页面内的路径



1.4. 网关配置

  • gateway配置文件中添加
    # 认证服务
    - id: gulimall_auth_route
      uri: lb://gulimall-auth-server
      predicates:
        - Host=auth.gulimall.com
    

1.5. 网页转发

  • 首先修改首页和登录页,注册页的相互跳转地址

    • login.html页面修改
    • gulimall.html首页页面修改
  • 创建controller.LoginController.class

    @Controller
    public class LoginController {
    
        @GetMapping("/login.html")
        public String loginPage() {
            return "login";
        }
    
        @GetMapping("/reg.html")
        public String regPage() {
            return "reg";
        }
    }
    
  • 修改登录页跳转注册页地址

  • 修改注册页跳转登录页地址

二、实现发送验证码功能

2.1. 发送验证码后倒计时

  • 首先将发送键写成a标签,并设置id
  • 编写js代码
    $(function() {
    	$("#sendCode").click(function(){
    		//1、给指定手机号发送验证码
    		//2、倒计时
    		if($(this).hasClass("disabled")) {
    			//正在倒计时
    		} else {
    			timeoutChangeStyle();
    		}
    	})
    })
    
    var num = 60;
    function timeoutChangeStyle() {
    	$("#sendCode").attr("class","disabled");
    	if(num == 0) {
    		$("#sendCode").text("发送验证码");
    		num = 60;
    		$("#sendCode").attr("class","");
    	} else {
    		var str = num + "s 后再次发送";
    		$("#sendCode").text(str);
    		setTimeout("timeoutChangeStyle()",1000);
    	}
    	num --;
    }
    

2.2. 实现页面渲染

  • 创建config.GulimallWebConfig.class
    public class GulimallWebConfig implements WebMvcConfigurer {
    
        public void addViewControllers(ViewControllerRegistry registry) {
            registry.addViewController("/login.html").setViewName("login");
            registry.addViewController("/reg.html").setViewName("reg");
        }
    }
    

2.3. 配置邮箱验证码(使用的qq邮箱)

  • 参考博客: springboot实现邮箱验证码功能, SpringBoot 发送邮箱验证码(HTML模板)
  • 首先开启邮箱POP3/IMAP/SMTP/Exchange/CardDAV 服务,并获得授权码
  • 在gulimall-third-party服务中添加maven依赖 (其中thymeleaf是用于渲染验证码邮件页面的)
    <!-- 邮箱服务 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
    <!--thymeleaf依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
  • 添加配置信息
    spring:
      mail:
        host: smtp.qq.com
        username: qq号@qq.com
        password: 授权码
        default-encoding: utf-8
    
  • 创建component.MailComponent组件,实现如下
    @ConfigurationProperties(prefix = "spring.mail")
    @Data
    @Component
    public class MailComponent {
    
        private String host;
        private String username;
        private String password;
        private String defaultEncoding;
    
    
        @Autowired
        private JavaMailSender javaMailSender;
    
        @Autowired
        TemplateEngine templateEngine;
    
        public void sendMessageToEmail(String email,String code) {
            Context context = new Context(); // 引入Template的Context
            // 设置模板中的变量(分割验证码)
            context.setVariable("verifyCode", Arrays.asList(code.split("")));
            // 第一个参数为模板的名称(html不用写全路径)
            String process = templateEngine.process("EmailVerificationCode.html", context); // 这里不用写全路径
            MimeMessage mimeMessage = javaMailSender.createMimeMessage();
            try {
                MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
                helper.setSubject("【Gulimall】验证码"); // 邮件的标题
                helper.setFrom(username); // 发送者
                helper.setTo(email); // 接收者
                helper.setSentDate(new Date()); // 时间
                helper.setText(process, true); // 第二个参数true表示这是一个html文本
            } catch (MessagingException e) {
                System.out.println("error");
            }
            javaMailSender.send(mimeMessage);
        }
    }
    
  • 实现controller.MailSendController 用于第三方调用
    @RestController
    @RequestMapping(value = "/mail")
    public class MailSendController {
    
        @Autowired
        private MailComponent mailComponent;
    
        @GetMapping(value = "/sendCode")
        public R sendMessageToEmalin(@RequestParam("email") String email, @RequestParam("code") String code) {
            mailComponent.sendMessageToEmail(email, code);
            return R.ok();
        }
    
    }
    
  • 验证码邮件页面EmailVerificationCode.html放在template下面,代码如下:
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>邮箱验证码</title>
        <style>
            table {
                width: 700px;
                margin: 0 auto;
            }
    
            #top {
                width: 700px;
                border-bottom: 1px solid #ccc;
                margin: 0 auto 30px;
            }
    
            #top table {
                font: 12px Tahoma, Arial, 宋体;
                height: 40px;
            }
    
            #content {
                width: 680px;
                padding: 0 10px;
                margin: 0 auto;
            }
    
            #content_top {
                line-height: 1.5;
                font-size: 14px;
                margin-bottom: 25px;
                color: #4d4d4d;
            }
    
            #content_top strong {
                display: block;
                margin-bottom: 15px;
            }
    
            #content_top strong span {
                color: #f60;
                font-size: 16px;
            }
    
            #verificationCode {
                color: #f60;
                font-size: 24px;
            }
    
            #content_bottom {
                margin-bottom: 30px;
            }
    
            #content_bottom small {
                display: block;
                margin-bottom: 20px;
                font-size: 12px;
                color: #747474;
            }
    
            #bottom {
                width: 700px;
                margin: 0 auto;
            }
    
            #bottom div {
                padding: 10px 10px 0;
                border-top: 1px solid #ccc;
                color: #747474;
                margin-bottom: 20px;
                line-height: 1.3em;
                font-size: 12px;
            }
    
            #content_top strong span {
                font-size: 18px;
                color: #FE4F70;
            }
    
            #sign {
                text-align: right;
                font-size: 18px;
                color: #FE4F70;
                font-weight: bold;
            }
    
            #verificationCode {
                height: 100px;
                width: 680px;
                text-align: center;
                margin: 30px 0;
            }
    
            #verificationCode div {
                height: 100px;
                width: 680px;
    
            }
    
            .button {
                color: #FE4F70;
                margin-left: 10px;
                height: 80px;
                width: 80px;
                resize: none;
                font-size: 42px;
                border: none;
                outline: none;
                padding: 10px 15px;
                background: #ededed;
                text-align: center;
                border-radius: 17px;
                box-shadow: 6px 6px 12px #cccccc,
                -6px -6px 12px #ffffff;
            }
    
            .button:hover {
                box-shadow: inset 6px 6px 4px #d1d1d1,
                inset -6px -6px 4px #ffffff;
            }
    
        </style>
    </head>
    <body>
    <table>
        <tbody>
        <tr>
            <td>
                <div id="top">
                    <table>
                        <tbody><tr><td></td></tr></tbody>
                    </table>
                </div>
    
                <div id="content">
                    <div id="content_top">
                        <strong>尊敬的用户:您好!</strong>
                        <strong>
                            您正在进行<span>注册账号</span>操作,请在验证码中输入以下验证码完成操作:
                        </strong>
                        <div id="verificationCode">
                            <button class="button" th:each="a:${verifyCode}">[[${a}]]</button>
                        </div>
                    </div>
                    <div id="content_bottom">
                        <small>
                            注意:此操作可能会修改您的密码、登录邮箱或绑定手机。如非本人操作,请及时登录并修改密码以保证帐户安全
                            <br>(工作人员不会向你索取此验证码,请勿泄漏!)
                        </small>
                    </div>
                </div>
                <div id="bottom">
                    <div>
                        <p>此为系统邮件,请勿回复<br>
                            请保管好您的邮箱,避免账号被他人盗用
                        </p>
                        <p id="sign">——wwh</p>
                    </div>
                </div>
            </td>
        </tr>
        </tbody>
    </table>
    </body>
    
  • 先测试一下:
    @Autowired
    private MailComponent mailComponent;
    
    @Test
    void contextLoads() {
        mailComponent.sendMessageToEmail("目标邮箱", "123456");
    }
    
    效果如下:
  • postman测试:

    测试结果:

2.4. 整合邮箱验证码功能

  • 在auth-server服务中实现远程服务 feign.ThirdPartFeignService
    @FeignClient("gulimall-third-party")
    public interface ThirdPartFeignService {
        @GetMapping(value = "/mail/sendCode")
        public R sendMessageToEmalin(@RequestParam("email") String email, @RequestParam("code") String code);
    }
    
  • 在LoginController中实现发送验证码调用方法
    @Controller
    public class LoginController {
    
        @Autowired
        ThirdPartFeignService thirdPartFeignService;
    
        @GetMapping("/mail/sendcode")
        public R sendCode(@RequestParam("email") String email) {
            String code = UUID.randomUUID().toString().substring(0, 5);
            thirdPartFeignService.sendMessageToEmalin(email, code);
            return R.ok();
        }
    }
    
  • 修改reg.html页面,点击后发送验证码
    • 首先先把短信验证改为邮箱验证
    • 接着完成调用方法发送请求部分
  • 测试:

2.5. 验证码的再次校验

  • 一般放入redis
  • 引入依赖:
    <!-- redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  • 添加redis配置信息
    # redis
    spring.redis.host=服务器地址
    spring.redis.port=端口号
    
  • 保存验证码信息
    • 首先在LoginController中注入StringRedisTemplate

      @Autowired
      StringRedisTemplate redisTemplate;
      
    • 在BizCodeEnume中添加

      MAIL_CODE_EXCEPTION(10002, "验证码获取频率太高,稍后试试")
      
    • 修改一下reg.html页面,发送出错后提示

    • sendCode()代码如下:

      @ResponseBody
      @GetMapping("/mail/sendcode")
      public R sendCode(@RequestParam("email") String email) {
      
          //1、接口防刷
          String redisCode = redisTemplate.opsForValue().get(AuthServerConstant.MAIL_CODE_CACHE_PREFIX + email);
          if(!StringUtils.isEmpty(redisCode)) {
              long l = Long.parseLong(redisCode.split("_")[1]);
              if (System.currentTimeMillis() - l < 60000) {
                  //60s不能再发
                  return R.error(BizCodeEnume.MAIL_CODE_EXCEPTION.getCode(), BizCodeEnume.MAIL_CODE_EXCEPTION.getMsg());
              }
          }
      
          String code = UUID.randomUUID().toString().substring(0, 5 );
      
          //2、验证码再次校验
          redisTemplate.opsForValue().set(AuthServerConstant.MAIL_CODE_CACHE_PREFIX + email, code, 60, TimeUnit.SECONDS);
      
          thirdPartFeignService.sendMessageToEmalin(email, code);
          return R.ok();
      }
      
  • 测试:
    • 第一次发送验证码
    • 再次发送验证码

三、实现页面一些其他功能

3.1、修改reg.html信息

<div class="tips" style="color: red" th:text="${errors != null ? (#maps.containsKey(errors, 'userName') ? errors.userName : '') : ''}">

<div class="tips" style="color: red" th:text="${errors != null ? (#maps.containsKey(errors, 'password') ? errors.userName : '') : ''}">


<div class="tips" style="color: red" th:text="${errors != null ? (#maps.containsKey(errors, 'email') ? errors.userName : '') : ''}">

<div class="tips" style="color: red" th:text="${errors != null ? (#maps.containsKey(errors, 'code') ? errors.userName : '') : ''}">

3.2、完善LoginController中的regist

@PostMapping("/regist")
public String regist(@Valid UserRegistVo vo, BindingResult result, RedirectAttributes redirectAttributes) {
    if(result.hasErrors()) {
        Map<String, String> errors = result.getFieldErrors().stream().collect(
                Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
        redirectAttributes.addFlashAttribute("errors", errors);
        // 用户注册 -> /regist[post] -> 转发/reg.html (路径映射默认都是get方式访问的。)
        //校验出错,转发到注册页
        return "redirect:http://auth.gulimall.com/reg.html";
    }
    //注册成功回到首页,回到登录页
    //return "redirect:/login.html";
    return null;
}

3.3、实现LoginController中的regist中的校验验证码功能

@PostMapping("/regist")
public String regist(@Valid UserRegistVo vo, BindingResult result, RedirectAttributes redirectAttributes) {
    if(result.hasErrors()) {
        Map<String, String> errors = result.getFieldErrors().stream().collect(
                Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
        redirectAttributes.addFlashAttribute("errors", errors);
        // 用户注册 -> /regist[post] -> 转发/reg.html (路径映射默认都是get方式访问的。)
        //校验出错,转发到注册页
        return "redirect:http://auth.gulimall.com/reg.html";
    }

    // 1、校验验证码
    String code = vo.getCode();
    String s = redisTemplate.opsForValue().get(AuthServerConstant.MAIL_CODE_CACHE_PREFIX + vo.getEmail());
    if(!StringUtils.isEmpty(s) && code.equals(s.split("_")[0])) {
        //删除验证码,令牌机制
        redisTemplate.delete(AuthServerConstant.MAIL_CODE_CACHE_PREFIX + vo.getEmail());
        //验证码通过,执行真正的注册功能

    } else {
        Map<String, String> errors = new HashMap<>();
        errors.put("code", "验证码错误");
        redirectAttributes.addFlashAttribute("errors", errors);
        return "redirect:http://auth.gulimall.com/reg.html";
    }

    //注册成功回到首页,回到登录页
    return "redirect:/login.html";
}

3.4、实现用户注册

  • 在member模块中实现MemberRegistVo用来接收数据

    @Data
    public class MemberRegistVo {
    
        private String userName;
    
        private String password;
    
        private String email;
    }
    
  • 在MemberController层创建/regist请求,并调用service层的regist()方法

    @PostMapping("/regist")
    public R regist(@RequestBody MemberRegistVo vo) {
        memberService.regist(vo);
        return R.ok();
    }
    
  • 创建checkEmailUnique和checkUsernameUnique两个方法

  • 为了让controller感知到异常,使用异常机制。创建异常类

    public class EmailExistException  extends RuntimeException{
        public EmailExistException() {
            super("邮箱已存在");
        }
    }
    
    public class UsernameExistException extends RuntimeException{
        public UsernameExistException() {
            super("用户名已存在");
        }
    }
    
  • 将检查用户名和邮箱是否存在的方法,如果检查出存在就抛出异常,然后逐层抛出,这样controller中也就可以catch到异常

    /**
     * 检查email是否存在
     * @param email
     * @throws EmailExistException
     */
    @Override
    public void checkEmailUnique(String email) throws EmailExistException {
        MemberDao memberDao = this.baseMapper;
        Integer integer = memberDao.selectCount(new QueryWrapper<MemberEntity>().eq("email", email));
        if(integer > 0) {
            throw new EmailExistException();
        }
    }
    
    /**
     * 检查username是否存在
     * @param username
     * @throws UsernameExistException
     */
    @Override
    public void checkUsernameUnique(String username) throws UsernameExistException {
        MemberDao memberDao = this.baseMapper;
        Integer integer = memberDao.selectCount(new QueryWrapper<MemberEntity>().eq("username", username));
        if(integer > 0) {
            throw new UsernameExistException();
        }
    }
    
  • 注意修改service层 和controller层

  • 接着考虑设置密码,但是密码得加密后再存储

    //密码要进行加密存储
    BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
    String encode = passwordEncoder.encode(vo.getPassword());
    entity.setPassword(encode);
    
  • 具体实现:

    @Autowired
    MemberLevelDao memberLevelDao;
    
    @Override
    public void regist(MemberRegistVo vo) {
        MemberDao memberDao = this.baseMapper;
        MemberEntity entity = new MemberEntity();
    
        //设置默认等级
        MemberLevelEntity levelEntity =  memberLevelDao.getDefaultLevel();
        entity.setLevelId(levelEntity.getId());
    
        //检查用户名和邮箱是否一致
        checkEmailUnique(vo.getEmail());
        checkUsernameUnique(vo.getUserName());
    
        entity.setEmail(vo.getEmail());
        entity.setUsername(vo.getUserName());
    
        //密码要进行加密存储
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String encode = passwordEncoder.encode(vo.getPassword());
        entity.setPassword(encode);
    
        //其他配置信息
    
        memberDao.insert(entity);
    }
    
  • 设置错误机制返回常量

  • 修改controller层的捕获异常处理代码

  • 为认证模块添加远程服务MemberFeignService

    @FeignClient("gulimall-member")
    public interface MemberFeignService {
        @PostMapping("/regist")
        public R regist(@RequestBody UserRegistVo vo);
    }
    
  • 测试成功

3.5、实现用户登录

  • 创建用户登录信息vo

    @Data
    public class UserLoginVo {
        private String loginacct;
        private String password;
    }
    
  • 为前端页面提交框绑定name, 并为提交按钮绑定提交表单事件

  • 在LoginController实现处理登录请求

  • 在Member服务中实现远程login()服务

  • Member服务中实现MemberLoginVo

    @Data
    public class MemberLoginVo {
        private String loginacct;
        private String password;
    }
    
  • 实现controller层的login请求

    @PostMapping("/login")
    public R login(@RequestBody MemberLoginVo vo) {
        MemberEntity entity = memberService.login(vo);
        if(entity != null) {
            return R.ok();
        } else {
            return R.error(BizCodeEnume.LOGINACCT_PASSWORD_INVAILD_EXCEPTION.getCode(), BizCodeEnume.LOGINACCT_PASSWORD_INVAILD_EXCEPTION.getMsg());
        }
    }
    
  • 具体实现:

    /**
     * 登录
     * @param vo
     * @return
     */
    @Override
    public MemberEntity login(MemberLoginVo vo) {
        String loginacct = vo.getLoginacct();
        String password = vo.getPassword();
    
        //1、去数据库查询
        MemberDao memberDao = this.baseMapper;
        MemberEntity entity = memberDao.selectOne(new QueryWrapper<MemberEntity>().eq("username", loginacct)
                .or().eq("email", loginacct));
    
        if(entity == null) {
            //登录失败
            return null;
        } else {
            //1、获取数据库password,
            String passwordDb = entity.getPassword();
            BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
            //2、密码匹配
            boolean matches = passwordEncoder.matches(password, passwordDb);
            if(matches) {
                //登录成功
                return entity;
            }W
        }
        return null;
    }
    
  • 添加错误码

    LOGINACCT_PASSWORD_INVAILD_EXCEPTION(15003, "账号密码错误")
    
  • 测试成功

四、社交登录

4.1 介绍

OAuth1.0: OAuth(开放授权) 是一个开放标准, 允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息, 而不需要将用户名和密码提供给第三方网站或分享他们数据的所有内容。

  • OAuth2.0
    • 对于用户相关的 开放OpenAPI(例如获取用户信息, 昵称、头像、动态同步, 照片, 日志, 分享等) , 为了保护用户数据的安全和隐私, 第三方网站访问用户数据前都需要显式的向用户征求授权。

4.2 开始工作

  • 参考:Gitee OAuth文档谷粒商城gitee社交登录流程OAuth2.0社交登录流程简介与实现

  • HttpClient类所需的meven依赖:地址

  • 去gitee授权页获得授权

  • 步骤介绍:

  • 1、如上图,gitee页面获得授权后,会回调到/success这个路径,我们在OAuth2Controller这层实现对这个请求的处理

    • 具体就是参考gitee oauth文档,先通过请求获得access token, 再拼接token获得用户信息。两个请求分别是https://gitee.com/oauth/token?grant_type=authorization_code&code={code}&client_id={client_id}&redirect_uri={redirect_uri}&client_secret={client_secret}https://gitee.com/api/v5/user?access_token={access_token}
    • 获得的社交用户信息,封装成GiteeUserVo
      @Data
      public class GiteeUserVo {
          private String uid;
          private String name;
          private String accessToken;
      }
      
    • 具体实现:
      @Autowired
      MemberFeignService memberFeignService;
      
      @GetMapping("/oauth2.0/gitee/success")
      public String gitee(@RequestParam("code") String code) throws Exception {
      
          System.out.println("code: " + code);
      
          // https://gitee.com/oauth/token?grant_type=authorization_code&code={code}&client_id={client_id}&redirect_uri={redirect_uri}&client_secret={client_secret}
          Map<String, String> header = new HashMap<>();
          Map<String, String> query = new HashMap<>();
      
          Map<String, String> map = new HashMap<>();
          map.put("client_id", "5c01c112830936d4c1ddb59844bdb2b910369c3978fa3b0ab80ad9ce3aec1491");
          map.put("redirect_uri", "http://auth.gulimall.com/oauth2.0/gitee/success");
          map.put("grant_type", "authorization_code");
          map.put("client_secret", "2bdda2950445ed72728ae69f2890bda64b35debdccd1f70e1ff0cf65058f4d1b");
          map.put("code", code);
      
          //1、根据code换取accessToken;
          HttpResponse response = com.atguigu.common.utils.HttpUtils.doPost("https://gitee.com", "/oauth/token", "post", header, query, map);
          System.out.println(response.getStatusLine().getStatusCode());
      
          if(response.getStatusLine().getStatusCode() == 200) {
              // 获取AccessToken
              String json = EntityUtils.toString(response.getEntity());
              System.out.println("获取到的token为 " + json);
              JSONObject jsonObject = JSON.parseObject(json);
              String access_token = jsonObject.getString("access_token");
              System.out.println("获取到的access_token " + access_token);
      
              // gitee还需要请求use获取数据
              // https://gitee.com/api/v5/user?access_token={access_token}
              String urluser = "https://gitee.com/api/v5/user?access_token=" + access_token;
              HttpClient httpClientuser = HttpClientBuilder.create().build();
              HttpGet httpGetUser = new HttpGet(urluser);
              HttpResponse responseUser = httpClientuser.execute(httpGetUser);
      
              GiteeUserVo giteeUserVo = new GiteeUserVo();
              String user = EntityUtils.toString(responseUser.getEntity());
              System.out.println("gitee用户信息 " + user);
      
              JSONObject jsonObjectUser = JSON.parseObject(user);
              String uid = jsonObjectUser.getString("id");
              System.out.println(uid);
              giteeUserVo.setUid(uid);
              String name = jsonObjectUser.getString("name");
              System.out.println(name);
              giteeUserVo.setName(name);
              System.out.println(access_token);
              giteeUserVo.setAccessToken(access_token);
      
              //知道当前是哪个社交用户登录成功
              //1、当前用户如何是第一次进网站,就自动注册进来(为当前社交用户生成一个会员信息账号,以后这个社交账号就对应指定的会员)
              //登录或者注册这个社交用户
              R r = memberFeignService.login(giteeUserVo);
              if(r.getCode() == 0) {
                  MemberRespVo memberRespVo = r.getData(new TypeReference<MemberRespVo>(){});
                  System.out.println("登录成功, 用户信息: " + memberRespVo);
      
                  //2、登录成功就跳回首页
                  return "redirect:http://gulimall.com";
              } else {
                  return "redirect:thttp://auth.gulimall.com/login.html";
              }
          } else {
              return "redirect:http://gulimall.com/login.html";
          }
      }
      
  • 2、member服务获得login请求后,要先检查这个社交用户是否注册过,如果注册过就更新access token就行,如果没有注册过就分别在ums_member信息表和ums_gitee_user信息表中添加一下,通过ums_gitee_user表的member_id来连接两个表。

    为了不让ums_member表变得冗余,我没有像老师那样在ums_member中添加属性,而是创建了一个新表ums_gitee_user,表信息如下:

    • 由于远程服务间通过vo类传输信息,所以在member也得创建一个一样的GiteeUserVo用来接收数据。
    • controller层
      @PostMapping("/oauth2/gitee/login")
      public R login(@RequestBody GiteeUserVo vo) {
          MemberEntity entity = memberService.login(vo);
          if(entity != null) {
              return R.ok().setData(entity);
          } else {
              return R.error(BizCodeEnume.LOGINACCT_PASSWORD_INVAILD_EXCEPTION.getCode(), BizCodeEnume.LOGINACCT_PASSWORD_INVAILD_EXCEPTION.getMsg());
          }
      }
      
    • 具体实现:
      /**
       * 登录
       * @param vo
       * @return
       */
      @Override
      @Transactional
      public MemberEntity login(GiteeUserVo vo) {
      
          String uid = vo.getUid();
          //1、判断当前社交用户是否已经登录过系统
          GiteeUserEntity giteeUserEntity = giteeUserDao.selectOne(new QueryWrapper<GiteeUserEntity>().eq("gitee_uid", uid));
      
          if(giteeUserEntity != null) {
              //已经注册过了
              //更新用户的访问令牌
              GiteeUserEntity updateEntity = new GiteeUserEntity();
              updateEntity.setGiteeUid(giteeUserEntity.getGiteeUid());
              updateEntity.setAccessToken(giteeUserEntity.getAccessToken());
              giteeUserDao.updateById(updateEntity);
              // 查出对应用户信息,并返回
              MemberEntity memberEntity = this.baseMapper.selectById(giteeUserEntity.getMemberId());
              return memberEntity;
          } else {
              //未注册过
              //1、保存用户信息
              MemberEntity register = new MemberEntity();
              register.setUsername(vo.getName());
              register.setNickname(vo.getName());
              // 设置默认等级
              MemberLevelEntity levelEntity =  memberLevelDao.getDefaultLevel();
              register.setLevelId(levelEntity.getId());
              register.setCreateTime(new Date());
      
              //插入数据
              this.baseMapper.insert(register);
      
              //2、保存gitee信息
              GiteeUserEntity giteeInfo = new GiteeUserEntity();
              giteeInfo.setGiteeUid(vo.getUid());
              giteeInfo.setAccessToken(vo.getAccessToken());
              MemberEntity entity = this.baseMapper.selectOne(new QueryWrapper<MemberEntity>().eq("username", vo.getName()));
              Long memberId = entity.getId();
              giteeInfo.setMemberId(memberId);
      
              giteeUserDao.insert(giteeInfo);
      
              return register;
          }
      }
      
    • 测试:
  • 3、解决session一致性问题

    • 引入spring-session依赖:
      <dependency>
          <groupId>org.springframework.session</groupId>
          <artifactId>spring-session-data-redis</artifactId>
      </dependency>
      
    • 进行相关配置
      #spring session
      spring.session.store-type=redis
      server.servlet.session.timeout=30m
      
    • 启动类上添加注解@EnableRedisHttpSession
    • 由于我们要将MemberRespVo 对象上传到redis上,所以要在它类上实现序列化,即引用接口Serializable
    • 为了在首页显示信息,product模块同样引用spring session,和上面同样的流程
    • 实现配置类GulimallSessionCongig, 认证和商品服务各放一个
    • 测试结果:

      在product模块启动类上引入注解时引用错了,导致不能拿到redis的json


  • 4、进一步优化

    • 看视频跟着修改就行

五、单点登录

一、介绍

  • 单点登录,就是想实现一处登录,处处登录。比如你登录了百度,那么你使用百度贴吧或者百度网盘网页时就不需要重新登录。

-> 233集

posted @ 2023-12-14 14:48  A_sc  阅读(86)  评论(0编辑  收藏  举报