Day212.OAuth2、微信二维码登入注册功能、用户登录信息前后端供、讲师列表前后端 -谷粒学院

谷粒学院

OAuth2的使用场景

一、OAuth2解决什么问题

1、OAuth2提出的背景

照片拥有者想要在云冲印服务上打印照片,云冲印服务需要访问云存储服务上的资源

image-20210306135953566

2、图例

资源拥有者:照片拥有者
客户应用:云冲印
受保护的资源:照片

image-20210306140117337

3、方式一:用户名密码复制

直接给密码,让他访问

适用于同一公司内部的多个系统,不适用于不受信的第三方应用

image-20210306140131363

4、方式二:通用开发者key

开发通用钥匙

适用于合作商或者授信的不同业务部门之间

image-20210306140157118

5、方式三:办法令牌

按照特定的规则,生成一个字符串,再将字符串颁发给访问者,并可以设置字符串的有效时间,与管理字符串,如吊销等,访问者拿着生成的字符串就可以去进行访问

接近OAuth2方式,需要考虑如何管理令牌、颁发令牌、吊销令牌,需要统一的协议,因此就有了OAuth2协议

image-20210306140223240

二、现代微服务安全

除了开放系统授权,OAuth2还可以应用于现代微服务安全

1、传统单块应用的安全

image-20210306140324455

2、现代微服务安全

现代微服务中系统微服务化以及应用的形态和设备类型增多,不能用传统的登录方式
核心的技术不是用户名和密码,而是token,由AuthServer颁发token,用户使用token进行登录

image-20210306140334445

3、典型的OAuth2应用

image-20210306140345133

三、总结

image-20210306140353615

四、OAuth2最简向导

川崎高彦:OAuth2领域专家,开发了一个OAuth2 sass服务,OAuth2 as Service,并且做成了一个公司再融资的过程中为了向投资人解释OAuth2是什么,于是写了一篇文章,《OAuth2最简向导》


OAuth2的定义

一、什么是OAuth2

记住

OAuth2是一种授权框架,按照一定规则生成字符串,字符串包含用户信息,但是,他不提供具体的生成规则,他是一种解决方案,主要解决:1、开放系统间授权 ,2、分布式访问问题

1、OAuth2正式定义

image-20210306142026174

2、令牌的核心

image-20210306141539619

3、OAuth2的历史

image-20210306141548449

4、OAuth2的优势

image-20210306141555486

5、OAuth2的不足

image-20210306141603688

6、Auth2涉及的角色

image-20210306141610913

7、OAuth2术语

image-20210306141618232

image-20210306141622046

8、OAuth2令牌的类型

image-20210306141629593

9、OAuth2的误解

image-20210306141637343

二、回顾

image-20210306141649462


微信二维码生成授权URL

一、准备工作

https://open.weixin.qq.com

1、注册

2、邮箱激活

3、完善开发者资料

4、开发者资质认证

准备营业执照,1-2个工作日审批、300元

5、创建网站应用

提交审核,7个工作日审批

6、熟悉微信登录流程

参考文档:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=e547653f995d8f402704d5cb2945177dc8aa4e7e&lang=zh_CN

获取access_token时序图

image-20210306142137324


二、后端开发

1、添加配置

将固定的那个id 秘钥放在配置文件中,然后进行读取

# 微信开放平台 appid
wx.open.app_id=你的appid
# 微信开放平台 appsecret
wx.open.app_secret=你的appsecret
# 微信开放平台 重定向url
wx.open.redirect_url=http://你的服务器名称/api/ucenter/wx/callback

redirect_url指的是扫码后重定向的url

2、创建常量类

创建util包,创建ConstantPropertiesUtil.java常量类,读取上面的配置数据

@Component
//@PropertySource("classpath:application.properties")
public class ConstantProperties implements InitializingBean {
    
    

    @Value("${wx.open.app_id}")
    private String appID;

    @Value("${wx.open.app_secret}")
    private String appSecret;

    @Value("${wx.open.redirect_url}")
    private String redirectUrl;

    public static String WX_APP_ID;
    public static String WX_APP_SECRET;
    public static String WX_REDIRECT_URL;

    @Override
    public void afterPropertiesSet() throws Exception {
    
    
        WX_APP_ID=this.appID;
        WX_APP_SECRET=this.appSecret;
        WX_REDIRECT_URL=this.redirectUrl;
    }

}

3、创建controller

guli-microservice-ucenter微服务中创建api包
api包中创建WxApiController

@Controller //注意这里没有配置 @RestController
@CrossOrigin
@RequestMapping("/api/ucenter/wx")
public class WxApiController {
    
    

    //生成微信扫描二维码,   %s 相当于?占位符
    @GetMapping("/login")
    public String getWxCode(){
    
    
        String baseUrl ="https://open.weixin.qq.com/connect/qrconnect"
                +"?appid=%s"
                +"&redirect_uri=%s"
                +"&response_type=code"
                +"&scope=snsapi_login"
                +"&state=%s"
                +"#wechat_redirect";

        //对redirect_uri进行URLEncoder编码
        String redirect_uri = ConstantProperties.WX_REDIRECT_URL;
        try {
    
    
            redirect_uri = URLEncoder.encode(redirect_uri, "utf-8");//参数1:待编码字符串 参数2:编码方式
        } catch (UnsupportedEncodingException e) {
    
    
            e.printStackTrace();
        }

        //设置 %s 占位符的参数,上面有3处
        String url = String.format(baseUrl,
                ConstantProperties.WX_APP_ID,
                redirect_uri,
                "achang");


        //请求微信地址
        return "redirect:" + url;
    }

}

授权url参数说明

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C46Z4eue-1615040671221)(http://qpgf4uqra.hn-bkt.clouddn.com/20210306152122.png)]

4、测试

访问:http://localhost:8006/api/ucenter/wx/login
访问授权url后会得到一个微信登录二维码

image-20210306152210419

用户扫描二维码会看到确认登录的页面

image-20210306152236435

用户点击“确认登录”后,微信服务器会向谷粒学院的业务服务器发起回调,因此接下来我们需要开发回调controller
注意:如果没有正确的配置业务服务器的回调url,则会看到以下错误提示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zYBRK2Gp-1615040671224)(http://qpgf4uqra.hn-bkt.clouddn.com/20210306152330.png)]


微信登录开发回调URL

一、准备工作

用于测试,实际开发中,只需要修改下面的redirect_url的值指定跳转即可

这里是尚硅谷为了让我们测试到效果,所以指定了跳转到本地的8150端口的/api/ucenter/wx/callback路径

所以根据这个,下面就在controller中写一个这样子的方法用于扫码后跳转测试

1、全局配置的跳转路径

# 微信开放平台 重定向url
wx.open.redirect_url=http://回调地址/api/ucenter/wx/callback

redirect_url指的是扫码后重定向的url

2、修改当前项目启动端口号为8150

# 服务端口
#server.port=8006
server.port=8150

3、测试回调是否可用

在WxApiController中添加方法

@GetMapping("/callback")
public String callback(String code, String state, HttpSession session){
    
    
    //得到授权临时票据code
    System.out.println("code = " + code);
    System.out.println("state = " + state);
    return "redirect:http://localhost:3000";
}
  • 测试效果

image-20210306153858193


二、后台开发

image-20210306165919680

1、添加依赖

<dependencies>
    <!--httpclient-->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
    </dependency>
    <!--commons-io-->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
    </dependency>
    <!--gson-->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
    </dependency>
</dependencies>

2、创建httpclient工具类

放入util包

HttpClientUtils.java

3、创建回调controller方法

在WxApiController.java中添加如下方法

package com.achang.serviceUcenter.controller;

import com.achang.commonutils.JwtUtils;
import com.achang.serviceUcenter.entity.UcenterMember;
import com.achang.serviceUcenter.service.UcenterMemberService;
import com.achang.serviceUcenter.utils.ConstantProperties;
import com.achang.serviceUcenter.utils.HttpClientUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.gson.Gson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;

/******
 @author 阿昌
 @create 2021-03-06 14:48
 *******
 */
@Controller //注意这里没有配置 @RestController
@CrossOrigin
@RequestMapping("/api/ucenter/wx")
public class WxApiController {
    
    

    @Autowired
    private UcenterMemberService ucenterMemberService;

    @GetMapping("/callback")
    public String callback(String code, String state, HttpSession session){
    
    
        //获取code值,临时票据,类似于验证码


        //拿着code,去请求微信固定的地址,得到两个值 access_token 和 openid
        String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token"+
                "?appid=%s" +
                "&secret=%s" +
                "&code=%s" +
                "&grant_type=authorization_code";

        //拼接三个参数:id 秘钥 和 code值
        String accessTokenUrl = String.format(baseAccessTokenUrl,
                ConstantProperties.WX_APP_ID
                , ConstantProperties.WX_APP_SECRET
                , code);


        //请求上面拼接好的地址,得到两个值 access_token 和 openid
        //使用httpclient【不用浏览器,也能模拟器出浏览器的请求和响应过程】发送请求,得到返回的结果
        String accessTokenInfo = null;
        try {
    
    
            accessTokenInfo = HttpClientUtils.get(accessTokenUrl);
            System.out.println("accessTokenInfo:。。。" +accessTokenInfo);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }

        //从accessTokenInfo中获取出  access_token 和 openid 的值
        //将 accessTokenInfo 转换成 map集合,根据map的key 就可以获取对应的value
        //使用json转换工具
        Gson gson = new Gson();
        HashMap mapAccessToken = gson.fromJson(accessTokenInfo, HashMap.class);
        String access_token = (String) mapAccessToken.get("access_token");
        String openid = (String) mapAccessToken.get("openid");

        //判断数据库是否存在相同的微信内容
        UcenterMember member = ucenterMemberService.getMemberByOpenId(openid);

        //如果没有,则为新用户,添加数据库
        if (member == null){
    
    

            //拿着 access_token 和 openid 的值再去请求微信提供的固定地址
            //访问微信的资源服务器,获取用户信息
            String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
                    "?access_token=%s" +
                    "&openid=%s";
            String userInfoUrl = String.format(baseUserInfoUrl, access_token, openid);

            String resultUserInfo = null;
            try {
    
    
                resultUserInfo = HttpClientUtils.get(userInfoUrl);
                System.out.println(resultUserInfo);
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
            HashMap<String, Object> resultMap = gson.fromJson(resultUserInfo, HashMap.class);

            String nickname = (String) resultMap.get("nickname");
            String headimgurl = (String) resultMap.get("headimgurl");

            //向数据库插入一条记录
            member = new UcenterMember();
            member.setNickname(nickname);
            member.setAvatar(headimgurl);
            member.setOpenid(openid);
            ucenterMemberService.save(member);
        }

        //使用jwt根据member对象生成token字符串
        String jwtToken = JwtUtils.getJwtToken(member.getId(), member.getNickname());

        //最后,返回首页,通过路径传递token字符串
        return "redirect:http://localhost:3000?token="+jwtToken;
    }

}

上面最后整合了JWT生成token字符串

  • 测试

image-20210306174149342

image-20210306174306099


前端整合和显示用户登录信息

  • 前端获取路径的代码

image-20210306175841446

1、修改 default.vue页面脚本

<script>
import "~/assets/css/reset.css";
import "~/assets/css/theme.css";
import "~/assets/css/global.css";
import "~/assets/css/web.css";
import cookie from "js-cookie";
import loginApi from "@/api/login";

export default {
     
     
  data() {
     
     
    return {
     
     
      token: "",
      loginInfo: {
     
     
        id: "",
        age: "",
        avatar: "",
        mobile: "",
        nickname: "",
        sex: "",
      },
    };
  },
  created() {
     
     
    //获取路径中token的值【用于微信二维码登录】
    this.token = this.$route.query.token;
    if (this.token) {
     
     
      //判断路径中是否有token值
      this.wxLogin();
    }

    this.showInfo();
  },
  methods: {
     
     
    wxLogin() {
     
     
      //设置token值
      cookie.set("guli_token", this.token, {
     
      domain: "localhost" });
      cookie.set("guli_ucenter", "", {
     
      domain: "localhost" });
      //调用接口,根据token获取用户信息
      loginApi.getLoginInfo().then((resp) => {
     
     
        this.loginInfo = resp.data.data.userInfo;
        cookie.set("guli_ucenter", this.loginInfo, {
     
      domain: "localhost" });
      });
    },
    logout() {
     
     
      //清空cookie值
      cookie.set("guli_token", "", {
     
     
        domain: "localhost",
      });
      cookie.set("guli_ucenter", "", {
     
     
        domain: "localhost",
      });
      //跳转页面到登录
      this.$router.push({
     
      path: "/login" });
    },
    //创建方法从cookie中获取信息
    showInfo() {
     
     
      //从cookie中获取信息
      var userStr = cookie.get("guli_ucenter");
      //转字符串转换成json对象(js对象)
      if (userStr) {
     
     
        this.loginInfo = JSON.parse(userStr);
      }
    },
  },
};
</script>
  • 测试

访问之前上面的二维码生成的链接

http://localhost:8150/api/ucenter/wx/login

扫码确认

image-20210306191510000


讲师列表页

一、后端部分

1、TeacherFrontController

com.achang.eduservice.controller.front.TeacherFrontController

@RestController
@CrossOrigin
@RequestMapping("/eduservice/teacherFront")
public class TeacherFrontController {
    
    

    @Autowired
    private EduTeacherService eduTeacherService;

    //前台系统分页查询讲师的方法
    //page:当前页 ,limit:显示记录数
    @PostMapping("/getTeacherFrontPageList/{page}/{limit}")
    public R getTeacherFrontPageList(@PathVariable Long page,@PathVariable Long limit){
    
    
        Page<EduTeacher> teacherPage = new Page<>(page, limit);

        Map<String,Object> map = eduTeacherService.getTeacherFrontPageList(teacherPage);

        //返回分页中的所有数据
        return R.ok().data(map);
        
    }

}

2、service

  • 接口
public interface EduTeacherService extends IService<EduTeacher> {
    
    
    ......
    //前台系统分页查询讲师的方法
    Map<String, Object> getTeacherFrontPageList(Page<EduTeacher> teacherPage);
}
  • Impl
@Service
public class EduTeacherServiceImpl extends ServiceImpl<EduTeacherMapper, EduTeacher> implements EduTeacherService {
    
    

    //前台系统分页查询讲师的方法
    @Override
    public Map<String, Object> getTeacherFrontPageList(Page<EduTeacher> teacherPage) {
    
    
        QueryWrapper<EduTeacher> wrapper = new QueryWrapper<>();
        wrapper.orderByDesc("id");
        //把分页数据封装到pageTeacher对象中
        baseMapper.selectPage(teacherPage,wrapper);

        //把分页的数据获取出来返回一个map集合
        HashMap<String, Object> map = new HashMap<>();

        //总记录数
        long total = teacherPage.getTotal();
        //当前页
        long current = teacherPage.getCurrent();
        //每页记录数
        long size = teacherPage.getSize();
        //查询到的对象
        List<EduTeacher> teacherList = teacherPage.getRecords();
        //总页数
        long pages = teacherPage.getPages();
        //是否有上一页
        boolean hasPrevious = teacherPage.hasPrevious();
        //是否有下一页
        boolean hasNext = teacherPage.hasNext();

        //将数据封装到map中返回
        map.put("total",total);
        map.put("current",current);
        map.put("size",size);
        map.put("list",teacherList);
        map.put("hasPrevious",hasPrevious);
        map.put("hasNext",hasNext);
        map.put("pages",pages);

        return map;
    }
    
}
  • swagger测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c8oiWCuy-1615040671235)(http://qpgf4uqra.hn-bkt.clouddn.com/20210306195613.png)]


二、前端部分

1、创建api

创建文件夹api,api下创建teacher.js,用于封装讲师模块的请求

  • guli-front\api\teacher.js
import request from '@/utils/request'

export default{
    
    
    getPageList(page, limit){
    
    
        return request({
    
    
            url:`/eduservice/teacherFront/getTeacherFrontPageList/${
      
      page}/${
      
      limit}`,
            method: 'get'
        })
    }
}

2、讲师列表组件中调用api

  • pages/teacher/index.vue

这里采用的是另一种写法,效果跟之前的是一样的,看个人喜好

<script>
import teacherAPI from "@/api/teacher";

export default {
    
    
  //异步调用,进页面调用,且只会调一次
  //params:相当于之前的 this.$route.params.id 等价 params.id
  //error:错误信息
  asyncData({
    
     params, error }) {
    
    
    return teacherAPI.getPageList(1, 8).then((response) => {
    
    
      //this.data = response.data.data
      return {
    
     data: response.data.data };
    });
  },
  data() {
    
    
    return {
    
    };
  },
  methods: {
    
    },
  created() {
    
    },
};
</script>

三、页面渲染

1、页面模板

<template>
    <div id="aCoursesList" class="bg-fa of">
        <!-- 讲师列表 开始 -->
        <section class="container">
            <section class="c-sort-box unBr">
                <div>
                <!-- 无数据提示 开始-->
                <!-- /无数据提示 结束-->
                <!-- 数据列表 开始-->
                <!-- /数据列表 结束-->
                </div>
            <!-- 公共分页 开始 -->
            <!-- /公共分页 结束 -->
            </section>
        </section>
        <!-- /讲师列表 结束 -->
    </div>
</template>

2、无数据提示

添加:v-if=“data.total==0”

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GtRZjkqo-1615040671237)(http://qpgf4uqra.hn-bkt.clouddn.com/20210306215939.png)]

3、有数据的情况

image-20210306220020427

<article class="i-teacher-list" v-if="data.total > 0">
    <ul class="of">
        <li v-for="item in data.list" :key="item.id">
            <section class="i-teach-wrap">
                <div class="i-teach-pic">
                    <a href="/teacher/1" :title="item.name" target="_blank">
                        <img :src="item.avatar" :alt="item.name" />
                    </a>
                </div>
                <div class="mt10 hLh30 txtOf tac">
                    <a
                       href="/teacher/1"
                       :title="item.name"
                       target="_blank"
                       class="fsize18 c-666"
                       >{
   
   { item.name }}</a
                        >
                </div>
                <div class="hLh30 txtOf tac">
                    <span class="fsize14 c-999"
                          >{
   
   {item.intro}}</span
                        >
                </div>
                <div class="mt15 i-q-txt">
                    <p class="c-999 f-fA">{
   
   {item.career}}</p>
                </div>
            </section>
        </li>
    </ul>
    <div class="clear"></div>
</article>

4、测试

image-20210306220115301


四、分页

1、分页方法

methods: {
    
    
    //分页切换方法
    //参数是页码数
    gotoPage(page) {
    
    
        teacherAPI.getPageList(page, 8).then((resp) => {
    
    
            this.data = resp.data.data;
        });
    },
},

2、分页页面渲染

<!-- 公共分页 开始 -->
<div>
    <div class="paging">
        <!-- undisable这个class是否存在,取决于数据属性hasPrevious -->
        <a
           :class="{ undisable: !data.hasPrevious }"
           href="#"
           title="首页"
           @click.prevent="gotoPage(1)"
           >首页</a>
        <a
           :class="{ undisable: !data.hasPrevious }"
           href="#"
           title="前一页"
           @click.prevent="gotoPage(data.current - 1)"
           >&lt;</a
            >
        <a
           v-for="page in data.pages"
           :key="page"
           :class="{
                   current: data.current == page,
                   undisable: data.current == page,
                   }"
           :title="'' + page + ''"
           href="#"
           @click.prevent="gotoPage(page)"
           >{
   
   { page }}</a
            >
        <a
           :class="{ undisable: !data.hasNext }"
           href="#"
           title="后一页"
           @click.prevent="gotoPage(data.current + 1)"
           >&gt;</a
            >
        <a
           :class="{ undisable: !data.hasNext }"
           href="#"
           title="末页"
           @click.prevent="gotoPage(data.pages)"
           >末页</a
            >
        <div class="clear" />
    </div>
</div>
<!-- 公共分页 结束 -->

3、测试

image-20210306222146858


猜你喜欢

转载自blog.csdn.net/qq_43284469/article/details/114460504