谷粒学院(十五)JWT | 阿里云短信服务 | 登录与注册前后端实现

一、使用JWT进行跨域身份验证

1、传统用户身份验证

谷粒学院(十五)JWT | 阿里云短信服务 | 登录与注册前后端实现_第1张图片
Internet服务无法与用户身份验证分开。一般过程如下:

  1. 用户向服务器发送用户名和密码。
  2. 验证服务器后,相关数据(如用户角色,登录时间等)将保存在当前会话中。
  3. 服务器向用户返回session_id,session信息都会写入到用户的Cookie。
  4. 用户的每个后续请求都将通过在Cookie中取出session_id传给服务器。
  5. 服务器收到session_id并对比之前保存的数据,确认用户的身份。

这种模式最大的问题是,没有分布式架构,无法支持横向扩展。

2、解决方案

  1. session广播
  2. 将透明令牌存入cookie,将用户身份信息存入redis

另外一种灵活的解决方案:使用自包含令牌,通过客户端保存数据,而服务器不保存会话数据。 JWT是这种解决方案的代表。

二、JWT令牌

1、访问令牌的类型

谷粒学院(十五)JWT | 阿里云短信服务 | 登录与注册前后端实现_第2张图片

2、JWT的组成

典型的,一个JWT看起来如下图:

谷粒学院(十五)JWT | 阿里云短信服务 | 登录与注册前后端实现_第3张图片
该对象为一个很长的字符串,字符之间通过"."分隔符分为三个子串。

每一个子串表示了一个功能块,总共有以下三个部分:JWT头、有效载荷和签名

JWT头

JWT头部分是一个描述JWT元数据的JSON对象,通常如下所示。

{
  "alg": "HS256",
  "typ": "JWT"
}

在上面的代码中,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。

有效载荷

有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择。

iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT

除以上默认字段外,我们还可以自定义私有字段,如下例:

{
  "sub": "1234567890",
  "name": "Helen",
  "admin": true
}

请注意,默认情况下JWT是未加密的,任何人都可以解读其内容,因此不要构建隐私信息字段,存放保密信息,以防止信息泄露。

JSON对象也使用Base64 URL算法转换为字符串保存。

签名哈希

签名哈希部分是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。

首先,需要指定一个密码(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用标头中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名。

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(claims), secret)

在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用"."分隔,就构成整个JWT对象。

Base64URL算法

如前所述,JWT头和有效载荷序列化的算法都用到了Base64URL。该算法和常见Base64算法类似,稍有差别。

作为令牌的JWT可以放在URL中(例如api.example/?token=xxx)。 Base64中用的三个字符是"+","/“和”=",由于在URL中有特殊含义,因此Base64URL中对他们做了替换:"=“去掉,”+“用”-“替换,”/“用”_"替换,这就是Base64URL算法。

3、JWT的原则

JWT的原则是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户,如下所示。

{
  "sub": "1234567890",
  "name": "Helen",
  "admin": true
}

之后,当用户与服务器通信时,客户在请求中发回JSON对象。服务器仅依赖于这个JSON对象来标识用户。为了防止用户篡改数据,服务器将在生成对象时添加签名。

服务器不保存任何会话数据,即服务器变为无状态,使其更容易扩展。

4、JWT的用法

客户端接收服务器返回的JWT,将其存储在Cookie或localStorage中。

此后,客户端将在与服务器交互中都会带JWT。如果将它存储在Cookie中,就可以自动发送,但是不会跨域,因此一般是将它放入HTTP请求的Header Authorization字段中。当跨域时,也可以将JWT被放置于POST请求的数据主体中。

三、整合JWT令牌

1、在common_utils模块中添加jwt工具依赖

在pom中添加

<dependencies>
   
    <dependency>
        <groupId>io.jsonwebtokengroupId>
        <artifactId>jjwtartifactId>
    dependency>
dependencies>

2、创建JWT工具类

public class JwtUtils {

    //常量
    public static final long EXPIRE = 1000 * 60 * 60 * 24; //token过期时间
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO"; //秘钥

    //生成token字符串的方法
    public static String getJwtToken(String id, String nickname){

        String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")

                .setSubject("guli-user")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))

                .claim("id", id)  //设置token主体部分 ,存储用户信息
                .claim("nickname", nickname)

                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();

        return JwtToken;
    }

    /**
     * 判断token是否存在与有效
     * @param jwtToken
     * @return
     */
    public static boolean checkToken(String jwtToken) {
        if(StringUtils.isEmpty(jwtToken)) return false;
        try {
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 判断token是否存在与有效
     * @param request
     * @return
     */
    public static boolean checkToken(HttpServletRequest request) {
        try {
            String jwtToken = request.getHeader("token");
            if(StringUtils.isEmpty(jwtToken)) return false;
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 根据token字符串获取会员id
     * @param request
     * @return
     */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("token");
        if(StringUtils.isEmpty(jwtToken)) return "";
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String)claims.get("id");
    }
}

四、阿里云短信服务

帮助文档:https://help.aliyun.com/product/44282.html?spm=5176.10629532.0.0.38311cbeYzBm73

1、开通阿里云短信服务

谷粒学院(十五)JWT | 阿里云短信服务 | 登录与注册前后端实现_第4张图片

2、添加签名管理与模板管理

(1)添加模板管理

选择 国内消息 - 模板管理 - 添加模板

谷粒学院(十五)JWT | 阿里云短信服务 | 登录与注册前后端实现_第5张图片
点击 添加模板,进入到添加页面,输入模板信息

谷粒学院(十五)JWT | 阿里云短信服务 | 登录与注册前后端实现_第6张图片

点击提交,等待审核,审核通过后可以使用

(2)添加签名管理

选择 国内消息 - 签名管理 - 添加签名

谷粒学院(十五)JWT | 阿里云短信服务 | 登录与注册前后端实现_第7张图片
点击添加签名,进入添加页面,填入相关信息
注意:签名要写的有实际意义

谷粒学院(十五)JWT | 阿里云短信服务 | 登录与注册前后端实现_第8张图片

点击提交,等待审核,审核通过后可以使

五、新建短信微服务

1、在service模块下创建子模块service_msm

2、创建controller和service代码

谷粒学院(十五)JWT | 阿里云短信服务 | 登录与注册前后端实现_第9张图片

3、配置application.properties

# 服务端口
server.port=8005
# 服务名
spring.application.name=service-msm

# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456

spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000

spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
#最小空闲

#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

4、创建启动类

创建MsmApplication.java

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源自动配置
@ComponentScan(basePackages = {"com.kuang"})
public class MsmApplication {

    public static void main(String[] args) {
        SpringApplication.run(MsmApplication.class, args);
    }
}

5、在service_msm的pom中引入依赖

<dependencies>
   <dependency>
       <groupId>com.alibabagroupId>
       <artifactId>fastjsonartifactId>
   dependency>
   <dependency>
       <groupId>com.aliyungroupId>
       <artifactId>aliyun-java-sdk-coreartifactId>
   dependency>
dependencies>

6、随机数工具类

/**
 * 获取随机数
 * 
 * @author qianyi
 *
 */
public class RandomUtil {

	private static final Random random = new Random();

	private static final DecimalFormat fourdf = new DecimalFormat("0000");

	private static final DecimalFormat sixdf = new DecimalFormat("000000");

	public static String getFourBitRandom() {
		return fourdf.format(random.nextInt(10000));
	}

	public static String getSixBitRandom() {
		return sixdf.format(random.nextInt(1000000));
	}

	/**
	 * 给定数组,抽取n个数据
	 * @param list
	 * @param n
	 * @return
	 */
	public static ArrayList getRandom(List list, int n) {

		Random random = new Random();

		HashMap<Object, Object> hashMap = new HashMap<Object, Object>();

		// 生成随机数字并存入HashMap
		for (int i = 0; i < list.size(); i++) {

			int number = random.nextInt(100) + 1;

			hashMap.put(number, i);
		}

		// 从HashMap导入数组
		Object[] robjs = hashMap.values().toArray();

		ArrayList r = new ArrayList();

		// 遍历数组并打印数据
		for (int i = 0; i < n; i++) {
			r.add(list.get((int) robjs[i]));
			System.out.print(list.get((int) robjs[i]) + "\t");
		}
		System.out.print("\n");
		return r;
	}
}

7、编写controller,根据手机号发送短信

@Api(description = "短信服务")
@RestController
@RequestMapping("/edumsm/msm")
@CrossOrigin
public class MsmController {

    @Autowired
    private MsmService msmService;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @ApiOperation(value = "发送短信的方法")
    @GetMapping("send/{phone}")
    public R sendMsm(@PathVariable String phone) {
        //从redis中获取验证码,如果获取到就直接返回
        String code = redisTemplate.opsForValue().get(phone);
        if (!StringUtils.isEmpty(code)) {
            return R.ok();
        }
        //如果redis获取不到,就进行阿里云发送
        //生成随机值,传递阿里云进行发送
        code = RandomUtil.getFourBitRandom();
        Map<String, Object> param = new HashMap<>();
        param.put("code",code);
        //调用service发送短信的方法
        boolean isSend = msmService.send(param,phone);
        if (isSend){
            //发送成功,把发送成功的验证码放到redis里面
            //设置有效时间
            redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES);
            return R.ok();
        }else {
            return R.error().message("短信发送失败");
        }
    }

}

8、编写service

@Service
public class MsmServiceIml implements MsmService {

    @Override
    public boolean send(Map<String, Object> param, String phone) {

        if(StringUtils.isEmpty(phone)) return false;

        DefaultProfile profile =
                DefaultProfile.getProfile("default", "LTAI4GAS6zW5Gh6amLaHJBLT", "A0ZKeYdnQw0eWE1hR7BLmq0SOR5OMA");
        IAcsClient client = new DefaultAcsClient(profile);

        //设置相关固定的参数
        CommonRequest request = new CommonRequest();
        //request.setProtocol(ProtocolType.HTTPS);
        request.setMethod(MethodType.POST);
        request.setDomain("dysmsapi.aliyuncs.com");
        request.setVersion("2017-05-25");
        request.setAction("SendSms");

        //设置发送相关的参数
        request.putQueryParameter("PhoneNumbers",phone); //手机号
        request.putQueryParameter("SignName","我的xmall购物商城"); //申请阿里云 签名名称
        request.putQueryParameter("TemplateCode",""); //申请阿里云 模板code
        request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param)); //验证码数据,转换json数据传递

        try {
            //最终发送
            CommonResponse response = client.getCommonResponse(request);
            boolean success = response.getHttpResponse().isSuccess();
            return success;
        }catch(Exception e) {
            e.printStackTrace();
            return false;
        }
    }

}

9、使用swagger进行测试

http://localhost:8005/swagger-ui.html

六、用户登录注册接口【后端】

1、在service模块下创建子模块service_ucenter

2、使用代码生成器生成代码

(1)创建ucenter_member表

谷粒学院(十五)JWT | 阿里云短信服务 | 登录与注册前后端实现_第10张图片

(2)生成代码

谷粒学院(十五)JWT | 阿里云短信服务 | 登录与注册前后端实现_第11张图片

3、配置application.properties

# 服务端口
server.port=8006
# 服务名
spring.application.name=service-ucenter

# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456

spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
#最小空闲

#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

#配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:com/kuang/educenter/mapper/xml/*.xml

#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

4、创建启动类

创建UcenterApplication.java

@SpringBootApplication
@ComponentScan(basePackages = {"com.kuang"})
@MapperScan("com.kuang.educenter.mapper")
public class UcenterApplication {
    public static void main(String[] args) {
        SpringApplication.run(UcenterApplication.class, args);
    }
}

5、创建Controller编写登录和注册方法

@Api(description = "前端登录注册接口")
@RestController
@RequestMapping("/educenter/member")
@CrossOrigin
public class UcenterMemberController {

    @Autowired
    private UcenterMemberService memberService;

    @ApiOperation(value = "登录")
    @PostMapping("login")
    public R loginUser(@RequestBody UcenterMember member) {
        //member对象封装手机号和密码
        //调用service方法实现登录
        //返回token值,使用jwt生成
        String token = memberService.login(member);
        return R.ok().data("token",token);
    }

    @ApiOperation(value = "注册")
    @PostMapping("register")
    public R registerUser(@RequestBody RegisterVo registerVo) {
        memberService.register(registerVo);
        return R.ok();
    }

    @ApiOperation(value = "根据token获取用户信息")
    @GetMapping("getMemberInfo")
    public R getMemberInfo(HttpServletRequest request) {
        //调用jwt工具类的方法。根据request对象获取头信息,返回用户id
        String memberId = JwtUtils.getMemberIdByJwtToken(request);
        //查询数据库根据用户id获取用户信息
        UcenterMember member = memberService.getById(memberId);
        return R.ok().data("userInfo",member);
    }

}

6、创建service接口和实现类

@Service
public class UcenterMemberServiceImpl extends ServiceImpl<UcenterMemberMapper, UcenterMember> implements UcenterMemberService {

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Override
    public String login(UcenterMember member) {
        //获取登录手机号和密码
        String mobile = member.getMobile();
        String password = member.getPassword();

        //手机号和密码非空判断
        if(StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)) {
            throw new GuliException(20001,"登录失败");
        }

        //判断手机号是否正确
        QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
        wrapper.eq("mobile",mobile);
        UcenterMember mobileMember = baseMapper.selectOne(wrapper);
        //判断查询对象是否为空
        if(mobileMember == null) {//没有这个手机号
            throw new GuliException(20001,"登录失败");
        }

        //判断密码
        //因为存储到数据库密码肯定加密的
        //把输入的密码进行加密,再和数据库密码进行比较
        //加密方式 MD5
        if(!MD5.encrypt(password).equals(mobileMember.getPassword())) {
            throw new GuliException(20001,"登录失败");
        }

        //判断用户是否禁用
        if(mobileMember.getIsDisabled()) {
            throw new GuliException(20001,"登录失败");
        }

        //登录成功
        //生成token字符串,使用jwt工具类
        String jwtToken = JwtUtils.getJwtToken(mobileMember.getId(), mobileMember.getNickname());
        return jwtToken;
    }

    @Override
    public void register(RegisterVo registerVo) {
        //获取注册的数据
        String code = registerVo.getCode(); //验证码
        String mobile = registerVo.getMobile(); //手机号
        String nickname = registerVo.getNickname(); //昵称
        String password = registerVo.getPassword(); //密码

        //非空判断
        if(StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)
                || StringUtils.isEmpty(code) || StringUtils.isEmpty(nickname)) {
            throw new GuliException(20001,"注册失败");
        }
        //判断验证码
        //获取redis验证码
        String redisCode = redisTemplate.opsForValue().get(mobile);
        if(!code.equals(redisCode)) {
            throw new GuliException(20001,"注册失败");
        }

        //判断手机号是否重复,表里面存在相同手机号不进行添加
        QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
        wrapper.eq("mobile",mobile);
        Integer count = baseMapper.selectCount(wrapper);
        if(count > 0) {
            throw new GuliException(20001,"注册失败");
        }

        //数据添加数据库中
        UcenterMember member = new UcenterMember();
        member.setMobile(mobile);
        member.setNickname(nickname);
        member.setPassword(MD5.encrypt(password));//密码需要加密的
        member.setIsDisabled(false);//用户不禁用
        member.setAvatar("https://guli-edu-20201.oss-cn-beijing.aliyuncs.com/2020/10/08/3a6bf3d4a85f415693e062db5fb17df8file.png");
        baseMapper.insert(member);
    }
}

7、MD5工具类

public final class MD5 {

    public static String encrypt(String strSrc) {
        try {
            char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
                    '9', 'a', 'b', 'c', 'd', 'e', 'f' };
            byte[] bytes = strSrc.getBytes();
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(bytes);
            bytes = md.digest();
            int j = bytes.length;
            char[] chars = new char[j * 2];
            int k = 0;
            for (int i = 0; i < bytes.length; i++) {
                byte b = bytes[i];
                chars[k++] = hexChars[b >>> 4 & 0xf];
                chars[k++] = hexChars[b & 0xf];
            }
            return new String(chars);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new RuntimeException("MD5加密出错!!+" + e);
        }
    }

    public static void main(String[] args) {
        System.out.println(MD5.encrypt("111111"));
    }

}

8、使用swagger测试

http://localhost:8006/swagger-ui.html

在这里插入图片描述

八、用户登录注册【前端】

1、在Nuxt环境中安装插件

(1)安装element-ui 和 vue-qriously插件

npm install element-ui
npm install vue-qriously

(2)修改配置文件 nuxt-swiper-plugin.js,使用插件

import Vue from 'vue'
import VueAwesomeSwiper from 'vue-awesome-swiper/dist/ssr'
import VueQriously from 'vue-qriously'
import ElementUI from 'element-ui' //element-ui的全部组件
import 'element-ui/lib/theme-chalk/index.css'//element-ui的css
Vue.use(ElementUI) //使用elementUI
Vue.use(VueQriously)
Vue.use(VueAwesomeSwiper)

2、用户注册功能前端整合

1、在api文件夹中定义接口

register.js

import request from '@/utils/request'

export default {
  //根据手机号发验证码
  sendCode(phone) {
    return request({
      url: `/edumsm/msm/send/${phone}`,
      method: 'get'
    })
  },

  //注册的方法
  registerMember(formItem) {
    return request({
      url: `/educenter/member/register`,
      method: 'post',
      data: formItem
    })
  }

}

2、在pages文件夹中创建注册页面,调用方法

(1)在layouts创建布局页面

sign.vue

<template>
  <div class="sign">
    
    <div class="logo">
      <img src="~/assets/img/logo.png" alt="logo">
    div>
    
    <nuxt/>
  div>
template>

(2)创建注册页面

修改layouts文件夹里面default.vue页面,修改登录和注册超链接地址

谷粒学院(十五)JWT | 阿里云短信服务 | 登录与注册前后端实现_第12张图片
在pages文件夹下,创建注册和登录页面

谷粒学院(十五)JWT | 阿里云短信服务 | 登录与注册前后端实现_第13张图片
register.vue

<template>
  <div class="main">
    <div class="title">
      <a href="/login">登录a>
      <span>·span>
      <a class="active" href="/register">注册a>
    div>

    <div class="sign-up-container">
      <el-form ref="userForm" :model="params">

        <el-form-item class="input-prepend restyle" prop="nickname" :rules="[{ required: true, message: '请输入你的昵称', trigger: 'blur' }]">
          <div>
            <el-input type="text" placeholder="你的昵称" v-model="params.nickname"/>
            <i class="iconfont icon-user"/>
          div>
        el-form-item>

        <el-form-item class="input-prepend restyle no-radius" prop="mobile" :rules="[{ required: true, message: '请输入手机号码', trigger: 'blur' },{validator: checkPhone, trigger: 'blur'}]">
          <div>
            <el-input type="text" placeholder="手机号" v-model="params.mobile"/>
            <i class="iconfont icon-phone"/>
          div>
        el-form-item>

        <el-form-item class="input-prepend restyle no-radius" prop="code" :rules="[{ required: true, message: '请输入验证码', trigger: 'blur' }]">
          <div style="width: 100%;display: block;float: left;position: relative">
            <el-input type="text" placeholder="验证码" v-model="params.code"/>
            <i class="iconfont icon-phone"/>
          div>
          <div class="btn" style="position:absolute;right: 0;top: 6px;width: 40%;">
            <a href="javascript:" type="button" @click="getCodeFun()" :value="codeTest" style="border: none;background-color: none">{{codeTest}}a>
          div>
        el-form-item>

        <el-form-item class="input-prepend" prop="password" :rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]">
          <div>
            <el-input type="password" placeholder="设置密码" v-model="params.password"/>
            <i class="iconfont icon-password"/>
          div>
        el-form-item>

        <div class="btn">
          <input type="button" class="sign-up-button" value="注册" @click="submitRegister()">
        div>
        <p class="sign-up-msg">
          点击 “注册” 即表示您同意并愿意遵守简书
          <br>
          <a target="_blank" href="http://www.jianshu.com/p/c44d171298ce">用户协议a><a target="_blank" href="http://www.jianshu.com/p/2ov8x3">隐私政策a>p>
      el-form>
      
      <div class="more-sign">
        <h6>社交帐号直接注册h6>
        <ul>
          <li><a id="weixin" class="weixin" target="_blank" href="http://huaan.free.idcfengye.com/api/ucenter/wx/login"><i
            class="iconfont icon-weixin"/>a>li>
          <li><a id="qq" class="qq" target="_blank" href="#"><i class="iconfont icon-qq"/>a>li>
        ul>
      div>
    div>
  div>
template>

<script>
import '~/assets/css/sign.css'
import '~/assets/css/iconfont.css'

import registerApi from '@/api/register'

export default {
layout: 'sign',
data() {
    return {
    params: { //封装注册输入数据
        mobile: '',
        code: '',  //验证码
        nickname: '',
        password: ''
    },
    sending: true,      //是否发送验证码
    second: 60,        //倒计时间
    codeTest: '获取验证码'
    }
},
methods: {
    //注册提交的方法
    submitRegister() {
        registerApi.registerMember(this.params)
        .then(response => {
        //提示注册成功
            this.$message({
            type: 'success',
            message: "注册成功"
            })
        //跳转登录页面
        this.$router.push({path:'/login'})
            
        })
    },
    //60秒倒计时
    timeDown() {
        let result = setInterval(() => {
            --this.second;
            this.codeTest = this.second
            if (this.second < 1) {
            clearInterval(result);
            this.sending = true;
            //this.disabled = false;
            this.second = 60;
            this.codeTest = "获取验证码"
            }
        }, 1000);
    },
    //通过输入手机号发送验证码
    getCodeFun() {
        registerApi.sendCode(this.params.mobile)
        .then(response => {
            this.sending = false
            //调用倒计时的方法
            this.timeDown()
        })
    },
    checkPhone (rule, value, callback) {
        //debugger
        if (!(/^1[34578]\d{9}$/.test(value))) {
            return callback(new Error('手机号码格式不正确'))
        }
        return callback()
        }
    }
}
script>

3、修改nginx配置文件的端口

谷粒学院(十五)JWT | 阿里云短信服务 | 登录与注册前后端实现_第14张图片

4、启动服务进行测试

3、用户登录功能前端整合

1、在api文件夹中创建登录的js文件,定义接口

login.js

import request from '@/utils/request'

export default {
  //用户登录
  submitLoginUser(userInfo) {
    return request({
        url: `/educenter/member/login`,
        method: 'post',
        data: userInfo
      })
  },
  //根据token获取用户信息
  getLoginUserInfo() {
    return request({
      url: `/educenter/member/getMemberInfo`,
      method: 'get'
    })
  }
}

2、在pages文件夹中创建登录页面,调用方法

谷粒学院(十五)JWT | 阿里云短信服务 | 登录与注册前后端实现_第15张图片

(1)安装js-cookie插件

npm install js-cookie

(2)login.vue

<template>
  <div class="main">
    <div class="title">
      <a class="active" href="/login">登录a>
      <span>·span>
      <a href="/register">注册a>
    div>

    <div class="sign-up-container">
      <el-form ref="userForm" :model="user">

        <el-form-item class="input-prepend restyle" prop="mobile" :rules="[{ required: true, message: '请输入手机号码', trigger: 'blur' },{validator: checkPhone, trigger: 'blur'}]">
          <div >
            <el-input type="text" placeholder="手机号" v-model="user.mobile"/>
            <i class="iconfont icon-phone" />
          div>
        el-form-item>

        <el-form-item class="input-prepend" prop="password" :rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]">
          <div>
            <el-input type="password" placeholder="密码" v-model="user.password"/>
            <i class="iconfont icon-password"/>
          div>
        el-form-item>

        <div class="btn">
          <input type="button" class="sign-in-button" value="登录" @click="submitLogin()">
        div>
      el-form>
      
      <div class="more-sign">
        <h6>社交帐号登录h6>
        <ul>
          <li><a id="weixin" class="weixin" target="_blank" href="http://qy.free.idcfengye.com/api/ucenter/weixinLogin/login"><i class="iconfont icon-weixin"/>a>li>
          <li><a id="qq" class="qq" target="_blank" href="#"><i class="iconfont icon-qq"/>a>li>
        ul>
      div>
    div>

  div>
template>

<script>
import '~/assets/css/sign.css'
import '~/assets/css/iconfont.css'
import cookie from 'js-cookie'
import loginApi from '@/api/login'
export default {
  layout: 'sign',
  data () {
    return {
      //用于封装手机号和密码
      user:{
        mobile:'',
        password:''
      },
      //用户信息
      loginInfo:{}
    }
  },
  methods: {
    //登录的方法
    submitLogin() {
      //第一步 调用接口进行登录,返回token字符串
      loginApi.submitLoginUser(this.user)
        .then(response => {
          //第二步 获取token字符串放到cookie里面
          //第一个参数cookie名称,第二个参数值,第三个参数作用范围
          cookie.set('guli_token',response.data.data.token,{domain: 'localhost'})
          //第四步 调用接口根据token获取用户信息,为了首页显示
          loginApi.getLoginUserInfo()
            .then(response => {
              this.loginInfo = response.data.data.userInfo
              //获取返回用户信息,放到cookie里面
              cookie.set('guli_ucenter',this.loginInfo,{domain: 'localhost'})
              //跳转页面
              window.location.href = "/";
            })
        })
    },
    checkPhone (rule, value, callback) {
      //debugger
      if (!(/^1[34578]\d{9}$/.test(value))) {
        return callback(new Error('手机号码格式不正确'))
      }
      return callback()
    }
  }
}
script>

<style>
   .el-form-item__error{
    z-index: 9999999;
  }
style>

登录和登录成功后首页显示护具实现过程分析:

(1)调用接口登录返回token字符串
(2)把第一步返回token字符串放到cookie里面
(3)创建拦截器:判断cookie里面是否有token字符串,如果有,把token字符串放到header(请求头中)
(4)根据token值,调用接口,根据token获取用户信息,为了首页面显示。调用接口返回用户信息放到cookie里面
(5)从首页面显示用户信息,从第四步cookie获取用户信息

3、在request.js添加拦截器,用于传递token信息

import { MessageBox, Message } from 'element-ui'
import cookie from 'js-cookie'

//第三步 创建拦截器 http request 拦截器
service.interceptors.request.use(
  config => {
  //debugger
  //判断cookie里面是否有名称是guli_token数据
  if (cookie.get('guli_token')) {
    //把获取cookie值放到header里面
    config.headers['token'] = cookie.get('guli_token');
  }
    return config
  },
  err => {
  return Promise.reject(err);
})

// http response 拦截器
service.interceptors.response.use(
  response => {
    //debugger
    if (response.data.code == 28004) {
        console.log("response.data.resultCode是28004")
        // 返回 错误代码-1 清除ticket信息并跳转到登录页面
        //debugger
        window.location.href="/login"
        return
    }else{
      if (response.data.code !== 20000) {
        //25000:订单支付中,不做任何提示
        if(response.data.code != 25000) {
          Message({
            message: response.data.message || 'error',
            type: 'error',
            duration: 5 * 1000
          })
        }
      } else {
        return response;
      }
    }
  },
  error => {
    return Promise.reject(error.response)   // 返回接口返回的错误信息
});

4、修改layouts中的default.vue页面

(1)显示登录之后的用户信息

import cookie from 'js-cookie'

export default {
  data() {
    return {
      token: '',
      loginInfo: {
        id: '',
        age: '',
        avatar: '',
        mobile: '',
        nickname: '',
        sex: ''
      }
    }
  },
  created() {
    this.showInfo()
  },
  methods: {
    //创建方法,从cookie获取用户信息
    showInfo() {
      //从cookie获取用户信息
      var userStr = cookie.get('guli_ucenter')
      // 把字符串转换json对象(js对象)
      if(userStr) {
        this.loginInfo = JSON.parse(userStr)
      }
    }
  },
  //退出
  logout() {
    //清空cookie值
    cookie.set('guli_token','',{domain: 'localhost'})
    cookie.set('guli_ucenter','',{domain: 'localhost'})
    //回到首页面
    window.location.href = "/";
  }
};

(2)default.vue页面显示登录之后的用户信息


<ul class="h-r-login">
  <li v-if="!loginInfo.id" id="no-login">
      <a href="/login" title="登录">
          <em class="icon18 login-icon"> em>
          <span class="vam ml5">登录span>
      a>
      |
      <a href="/register" title="注册">
          <span class="vam ml5">注册span>
      a>
  li>
  <li v-if="loginInfo.id" id="is-login-one" class="mr10">
      <a id="headerMsgCountId" href="#" title="消息">
          <em class="icon18 news-icon"> em>
      a>
      <q class="red-point" style="display: none"> q>
  li>
  <li v-if="loginInfo.id" id="is-login-two" class="h-r-user">
      <a href="/ucenter" title>
          <img
              :src="loginInfo.avatar"
              width="30"
              height="30"
              class="vam picImg"
              alt
              >
          <span id="userName" class="vam disIb">{{ loginInfo.nickname }}span>
      a>
      <a href="javascript:void(0);" title="退出" @click="logout()" class="ml5">退出a>
  li>
  
ul>

5、启动服务测试


如果有收获!!! 希望老铁们来个三连,点赞、收藏、转发。
创作不易,别忘点个赞,可以让更多的人看到这篇文章,顺便鼓励我写出更好的博客

你可能感兴趣的:(谷粒学院,/,权限管理系统,谷粒学院,jwt,阿里云短信服务,登录与注册后端,登录与注册前端)