前后端如何实现登录token拦截校验详解

一、场景与环境

最近需要写一下前后端分离下的登录解决方案,目前大多数都采用请求头携带 Token 的形式

1、我是名小白web工作者,每天都为自己的将来担心不已。第一次记录日常开发中的过程,如有表达不当,还请一笑而过;

2、本实例开发环境前端采用 angular框架,后端采用 springboot框架;

3、实现的目的如下:

  a、前端实现登录操作(无注册功能);

  b、后端接收到登录信息,生成有效期限token(后端算法生成的一段秘钥),作为结果返回给前端;

  c、前端在此后的每次请求,都会携带token与后端校验;

  d、在token有效时间内前端的请求响应都会成功,后端实时的更新token有效时间(暂无实现),如果token失效则返回登录页。

二、后端实现逻辑

注:部分代码参考网上各个大神的资料

整个服务端项目结构如下(登录token拦截只是在此工程下的一部分,文章结尾会贴上工程地址):

1、新增AccessToken 类 model

  在model文件下新增AccessToken.java,此model 类保存校验token的信息:

/**
 * @param access_token token字段;
 * @param token_type token类型字段;
 * @param expires_in token 有效期字段;
 */
public class AccessToken {
 private String access_token;
 private String token_type;
 private long expires_in;

 public String getAccess_token() {
  return access_token;
 }

 public void setAccess_token(String access_token) {
  this.access_token = access_token;
 }

 public String getToken_type() {
  return token_type;
 }

 public void setToken_type(String token_type) {
  this.token_type = token_type;
 }

 public long getExpires_in() {
  return expires_in;
 }

 public void setExpires_in(long expires_in) {
  this.expires_in = expires_in;
 }
}

2、新增Audience 类 model

@ConfigurationProperties(prefix = "audience")
public class Audience {
 private String clientId;
 private String base64Secret;
 private String name;
 private int expiresSecond;

 public String getClientId() {
  return clientId;
 }

 public void setClientId(String clientId) {
  this.clientId = clientId;
 }

 public String getBase64Secret() {
  return base64Secret;
 }

 public void setBase64Secret(String base64Secret) {
  this.base64Secret = base64Secret;
 }

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public int getExpiresSecond() {
  return expiresSecond;
 }

 public void setExpiresSecond(int expiresSecond) {
  this.expiresSecond = expiresSecond;
 }
}

@ConfigurationProperties(prefix = "audience")获取配置文件的信息(application.properties),如下:

server.port=8888
spring.profiles.active=dev
server.servlet.context-path=/movies

audience.clientId=098f6bcd4621d373cade4e832627b4f6
audience.base64Secret=MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY=
audience.name=xxx
audience.expiresSecond=1800

配置文件定义了端口号、根路径和audience相关字段的信息,(audience也是根据网上资料命名的),audience的功能主要在第一次登录时,生成有效token,然后将token的信息存入上述AccessToken类model中,方便登录成功后校验前端携带的token信息是否正确。

3、生成以jwt包的CreateTokenUtils 工具类

  下面对这个工具类的生成、功能进行说明:

  a、首先在pom.xml文件中引用依赖(这和前端在package.json安装npm包性质相似)

 <dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt</artifactId>
  <version>0.6.0</version>
 </dependency>

  b、然后再uitls文件夹下新增工具类CreateTokenUtils,代码如下 :

public class CreateTokenUtils {
 private static Logger logger = LoggerFactory.getLogger(CreateTokenUtils.class);

 /**
  *
  * @param request
  * @return s;
  * @throws Exception
  */
 public static ReturnModel checkJWT(HttpServletRequest request,String base64Secret)throws Exception{
  Boolean b = null;
  String auth = request.getHeader("Authorization");
  if((auth != null) && (auth.length() > 4)){
   String HeadStr = auth.substring(0,3).toLowerCase();
   if(HeadStr.compareTo("mso") == 0){
    auth = auth.substring(4,auth.length());
    logger.info("claims:"+parseJWT(auth,base64Secret));
    Claims claims = parseJWT(auth,base64Secret);
    b = claims==null?false:true;
   }
  }
  if(b == false){
   logger.error("getUserInfoByRequest:"+ auth);
   return new ReturnModel(-1,b);
  }
  return new ReturnModel(0,b);
 }

 public static Claims parseJWT(String jsonWebToken, String base64Security){
  try
  {
   Claims claims = Jwts.parser()
     .setSigningKey(DatatypeConverter.parseBase64Binary(base64Security))
     .parseClaimsJws(jsonWebToken).getBody();
   return claims;
  }
  catch(Exception ex)
  {
   return null;
  }
 }
 public static String createJWT(String name,String audience, String issuer, long TTLMillis, String base64Security)
 {
  SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

  long nowMillis = System.currentTimeMillis();
  Date now = new Date(nowMillis);

  byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(base64Security);
  Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

  JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")
    .claim("unique_name", name)
    .setIssuer(issuer)
    .setAudience(audience)
    .signWith(signatureAlgorithm, signingKey);
  if (TTLMillis >= 0) {
   long expMillis = nowMillis + TTLMillis;
   Date exp = new Date(expMillis);
   builder.setExpiration(exp).setNotBefore(now);
  }
  return builder.compact();
 }
}

此工具类有三个 静态方法:

 checkJWT—— 此方法在后端拦截器中使用,检测前端发来的请求是否带有token值

 createJWT——此方法在登陆接口中调用,首次登陆生成token值

 parseJWT——此方法在checkJWT中调用,解析token值,将jwt类型的token值分解成audience模块

 可以在parseJWT方法中打断点,查看Claims 对象,发现其字段存储的值与audience对象值一一对应。

注:Claims对象直接会将token的有效期进行判断是否过期,所以不需要再另写相关时间比对逻辑,前端的带来的时间与后台的配置文件audience的audience.expiresSecond=1800 Claims对象会直接解析

4、拦截器的实现HTTPBasicAuthorizeHandler类的实现

在typesHandlers文件夹中新建HTTPBasicAuthorizeHandler类,代码如下:

@WebFilter(filterName = "basicFilter",urlPatterns = "/*")
public class HTTPBasicAuthorizeHandler implements Filter {
 private static Logger logger = LoggerFactory.getLogger(HTTPBasicAuthorizeHandler.class);
 private static final Set<String> ALLOWED_PATHS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("/person/exsit")));
 @Autowired
 private Audience audience;
 @Override
 public void init(FilterConfig filterConfig) throws ServletException {
  logger.info("filter is init");
 }
 @Override
 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  logger.info("filter is start");
  try {
   logger.info("audience:"+audience.getBase64Secret());
   HttpServletRequest request = (HttpServletRequest) servletRequest;
   HttpServletResponse response = (HttpServletResponse) servletResponse;
   String path = request.getRequestURI().substring(request.getContextPath().length()).replaceAll("[/]+$", "");
   logger.info("url:"+path);
   Boolean allowedPath = ALLOWED_PATHS.contains(path);
   if(allowedPath){
    filterChain.doFilter(servletRequest,servletResponse);
   }else {
    ReturnModel returnModel = CreateTokenUtils.checkJWT((HttpServletRequest)servletRequest,audience.getBase64Secret());
    if(returnModel.getCode() == 0){
     filterChain.doFilter(servletRequest,servletResponse);
    }else {
     // response.setCharacterEncoding("UTF-8");
//     response.setContentType("application/json; charset=utf-8");
//     response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
//     ReturnModel rm = new ReturnModel();
//     response.getWriter().print(rm);
    }
   }
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
 @Override
 public void destroy() {
  logger.info("filter is destroy");
 }
}

此类继承Filter类,所以重写的三个方法init、doFitler、destory,重点拦截的功能在doFitler方法中:

 a、前端发来请求都会到这个方法,那么显而易见,第一登陆请求肯定不能拦截,因为它不带有token值,所以剔除登录拦截这种情况:

private static final Set<String> ALLOWED_PATHS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("/person/exsit")));

这里面的我的登录接口路径是“/person/exsit”,所以在将前端请求路径分解:

String path = request.getRequestURI().substring(request.getContextPath().length()).replaceAll("[/]+$", "");

两者进行如下比对:

Boolean allowedPath = ALLOWED_PATHS.contains(path);

根据allowedPath 的值进行判断是否拦截;

 b、拦截的时候调用上述工具类的checkJWT方法,判断token是否有效:

ReturnModel returnModel = CreateTokenUtils.checkJWT((HttpServletRequest)servletRequest,audience.getBase64Secret());

ReturnModel 是我定义的返回类型结构,在model文件下;

 c、如果token无效,处理代码注释了:

原因前端angular实现的拦截器和后端会冲突,导致前端代码异常,后面会详细说明。

 d、配置拦截器有两种方法(这里只介绍一种):

直接在拦截类上添加注释的方法,urlPatterns是你过滤的路径,还需在服务启动的地方配置

注:这里面过滤的路径不包括配置文件的根路径,比如说前端访问接口路径“/movies/people/exist”,这里面的movies是根路径,在配置文件中配置,如果你想拦截这个路径,则urlPatterns=”/people/exist“即可。

5、登录类的实现

在controller文件夹中新建PersonController类,代码如下

/**
 * Created by jdj on 2018/4/23.
 */
@RestController
@RequestMapping("/person")
public class PersonController {
 private final static Logger logger = LoggerFactory.getLogger(PersonController.class);
 @Autowired
 private PersonBll personBll;
 @Autowired
 private Audience audience;
 /**
  * @content:根据id对应的person
  * @param id=1;
  * @return returnModel
  */
 @RequestMapping(value = "/exsit",method = RequestMethod.POST)
 public ReturnModel exsit(
   @RequestParam(value = "userName") String userName,
   @RequestParam(value = "passWord") String passWord
 ){
  String md5PassWord = Md5Utils.getMD5(passWord);
  String id = personBll.getPersonExist(userName,md5PassWord);
  if(id == null||id.length()<0){
   return new ReturnModel(-1,null);
  }else {
   Map<String,Object> map = new HashMap<>();
   Person person = personBll.getPerson(id);
   map.put("person",person);
   String accessToken = CreateTokenUtils
     .createJWT(userName,audience.getClientId(), audience.getName(),audience.getExpiresSecond() * 1000, audience.getBase64Secret());
   AccessToken accessTokenEntity = new AccessToken();
   accessTokenEntity.setAccess_token(accessToken);
   accessTokenEntity.setExpires_in(audience.getExpiresSecond());
   accessTokenEntity.setToken_type("bearer");
   map.put("accessToken",accessTokenEntity);
   return new ReturnModel(0,map);
  }
 }
 /**
  * @content:list
  * @param null;
  * @return returnModel
  */
 @RequestMapping(value = "/list",method = RequestMethod.GET)
 public ReturnModel list(){
  List<Person> list = personBll.selectAll();
  if(list.size()==0){
   return new ReturnModel(-1,null);
  }else {
   return new ReturnModel(0,list);
  }
 }

 @RequestMapping(value = "/item",method = RequestMethod.GET)
 public ReturnModel getItem(
   @RequestParam(value = "id") String id
 ){
  Person person = personBll.getPerson(id);
  if(person != null){
   return new ReturnModel(0,person);
  }else {
   return new ReturnModel(-1,"无此用户");
  }
 }
}

前端调用这个类的接口路径:“/movies/people/exist”

首先它会查询数据库

 String id = personBll.getPersonExist(userName,md5PassWord);

如果查询存在,创建accessToken

 String accessToken = CreateTokenUtils
 .createJWT(userName,audience.getClientId(), audience.getName(),audience.getExpiresSecond() * 1000, audience.getBase64Secret());

最后整合返回到前端model

AccessToken accessTokenEntity = new AccessToken();
   accessTokenEntity.setAccess_token(accessToken);
   accessTokenEntity.setExpires_in(audience.getExpiresSecond());
   accessTokenEntity.setToken_type("bearer");
   map.put("accessToken",accessTokenEntity);
   return new ReturnModel(0,map);

这个controller类中还有两个接口供前端登陆成功后调用。

以上都是服务端的实现逻辑,接下来说明前端的实现逻辑,我本身是前端小码农,后端只是大多是不会的,如有错误,请一笑而过哈~_~哈

三、前端实现逻辑

前端使用angular框架,目录如下

上述app文件下common 存一些共同组建(分页、弹框)、component存一些整体布局框架、
page是各个页面组件,service是请求接口聚集地,shared是表单自定义校验;所以这里面都有相关的angular2+表单校验、http请求、分页、angular动画等各种实现逻辑。

1、前端http请求(确切的说httpClient请求)

所有的请求都在service文件夹service.service.ts文件中,代码如下:

import { Injectable } from '@angular/core';
import { HttpClient,HttpHeaders } from "@angular/common/http";
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/forkJoin';

@Injectable()
export class ServiceService {
 movies:string;
 httpOptions:Object;
 constructor(public http:HttpClient) {
 this.movies = "/movies";
 this.httpOptions = {
  headers:new HttpHeaders({
  'Content-Type':'application/x-www-form-urlencoded;charset=UTF-8',
  }),
 }
 }
 /**登录模块开始*/
 loginMovies(body){
 const url = this.movies+"/person/exsit";
 const param = 'userName='+body.userName+"&passWord="+body.password;
 return this.http.post(url,param,this.httpOptions);
 }
 /**登录模块结束*/
 //首页;
 getPersonItem(param){
 const url = this.movies+"/person/item";
 return this.http.get(url,{params:param});
 }
 //个人中心
 getPersonList(){
 const url = this.movies+"/person/list";
 return this.http.get(url);
 /**首页模块结束 */
}

上述有三个请求与后端personController类中三个接口方法一一对应,这里面的请求方式官网有,这里不做赘述,this.httpOptions是设置请求头。然后再app.modules.ts中添加到provides,所谓的依赖注入,这样就可以在各个页面调用servcie方法了

 providers: [ServiceService,httpInterceptorProviders]

httpInterceptorProviders 是前端拦截器,前端每次请求结果都会出现成功或者错误,所以在拦截器中统一处理返回结果使代码更简洁。

2、前端拦截器的实现

在app文件在新建InterceptorService.ts文件,代码如下:

import { Injectable } from '@angular/core';
import { HttpEvent,HttpInterceptor,HttpHandler,HttpRequest,HttpResponse} from "@angular/common/http";
import {Observable} from "rxjs/Observable";
import { ErrorObservable } from 'rxjs/observable/ErrorObservable';
import { mergeMap } from 'rxjs/operators';
import {Router} from '@angular/router';

@Injectable()
export class InterceptorService implements HttpInterceptor{
 constructor(
  private router:Router,
 ){ }
 authorization:string = "";
 authReq:any;
 intercept(req:HttpRequest<any>,next:HttpHandler):Observable<HttpEvent<any>>{
  this.authorization = "mso " + localStorage.getItem("accessToken");

  if (req.url.indexOf('/person/exsit') === -1) {
   this.authReq = req.clone({
    url:req.url,
    headers:req.headers.set("Authorization",this.authorization)
   });
  }else{
   this.authReq = req.clone({
    url:req.url,
   });
  }
  return next.handle(this.authReq).pipe(mergeMap((event:any) => {
   if(event instanceof HttpResponse && event.body === null){
    return this.handleData(event);
   }
   return Observable.create(observer => observer.next(event));
  }));
 }
 private handleData(event: HttpResponse<any>): Observable<any> {
  // 业务处理:一些通用操作
  switch (event.status) {
   case 200:
   if (event instanceof HttpResponse) {
    const body: any = event.body;
    if (body === null) {
     this.backForLoginOut();
    }
   }
   break;
   case 401: // 未登录状态码
   this.backForLoginOut();
   break;
   case 404:
   case 500:
   break;
   default:
   return ErrorObservable.create(event);
  }
 }
 private backForLoginOut(){
  if(localStorage.getItem("accessToken") !== null || localStorage.getItem("person")!== null){
   localStorage.removeItem("accessToken");
   localStorage.removeItem("person");
  }
   if(localStorage.getItem("accessToken") === null && localStorage.getItem("person") === null){
   this.router.navigateByUrl('/login');
  }
 }
}

拦截器的实现官网也详细说明了,但是拦截器有几大坑:

  a、如果用的是angular2,你请求是采用的是import { Http } from "@angular/http"包http,那么拦截器无效,你可能需要另一种写法了,angular4、5、6都是采用import { HttpClient,HttpHeaders } from "@angular/common/http"包下HttpClient和请求头HttpHeaders ;

  b、拦截器返回结果的方法中:

return next.handle(this.authReq).pipe(mergeMap((event:any) => {
   if(event instanceof HttpResponse && event.body === null){
    return this.handleData(event);
   }
   return Observable.create(observer => observer.next(event));
  }));

打断点查看这个方法一次请求会循环两次,第一次event:{type:0} ,第二次才会返回对象,截图如下:

第一次

第二次

但是如果以我上述后端拦截器token无效的情况处理代码(就是我注释的那段代码,我注释的代码重点的作用是返回401,可以回看),这个逻辑只循环一次,所以我将后端代码返回token无效的代码注释,前端拦截器在后端代码注释的情况下第二次返回的event结果体存在event.body=== null ,以这个条件进行token是否有效判断;

  c、拦截器使用rxjs,如果你在页面请求中使用rxjs中Observable.forkJoin()方法进行并发请求,那么不好意思,好像无效,如果你有办法解决这两个不冲突,请告诉我哈。

  d、这里面也要剔除登陆的拦截,具体看代码。

3、登录效果

以上的逻辑都是实现过程,下面来看下整体的效果:

登陆逻辑中我用的是localStorage存储token值的:

点击登录会先到前端拦截器,然后直接跳到else

接着到后端服务拦截器

过滤登陆接口,直接跳到登陆接口,创建token值并返回

观察返回的map值

最后返回前端界面

上面的返回结果与后端对应,登录成功后,再请求其他页面会携带token值

以上就是关于前后端分离登录校验,还有一步没有完成,就是token更新时间有效期,等抽时间再补充,上述代码后端用idea编辑器,后端服务搭建会涉及到很多配置。

上面实现的代码github地址如下:github.com/yuelinghuny… (本地下载)

麻烦各位给我点个赞,第一次写记录文档,我会坚持写下去,会坚信越来越好,谢谢。

总结:

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • 详解在ASP.NET Core中使用Angular2以及与Angular2的Token base身份认证

    Angular2是对Angular1的一次彻底的,破坏性的更新. 相对于Angular1.x,借用某果的广告语,唯一的不同,就是处处都不同. •首先,推荐的语言已经不再是Javascript,取而代之的TypeScript,(TypeScript = ES6 + 类型系统 + 类型注解), TypeScriipt的类型系统对于开发复杂的单页Web app大有帮助,同时编译成javascript后的执行效率也比大多数手写javascript要快.有兴趣的同学可以查阅官方文档:英文传送门 |中文传送

  • 前后端如何实现登录token拦截校验详解

    一.场景与环境 最近需要写一下前后端分离下的登录解决方案,目前大多数都采用请求头携带 Token 的形式 1.我是名小白web工作者,每天都为自己的将来担心不已.第一次记录日常开发中的过程,如有表达不当,还请一笑而过: 2.本实例开发环境前端采用 angular框架,后端采用 springboot框架: 3.实现的目的如下:   a.前端实现登录操作(无注册功能):   b.后端接收到登录信息,生成有效期限token(后端算法生成的一段秘钥),作为结果返回给前端:   c.前端在此后的每次请求,

  • vue.js前后端数据交互之提交数据操作详解

    本文实例讲述了vue.js前后端数据交互之提交数据操作.分享给大家供大家参考,具体如下: 前端小白刚开始做页面的时候,我们的前端页面中经常会用到表单,所以学会提交表单也是一个基本技能,其实用ajax就能实现,但他的原始语法有点...额 ...复杂,所以这里给大家提供一种用vue-resource向后端提交数据. (1)第一步,先在template中写一个表单: <el-form :model="ruleForm" :rules="rules" ref=&quo

  • 关于前后端json数据的发送与接收详解

    前言 最近因为笔者后台使用的是flask框架接收和前端使用的是原生的JavaScript和jQuery的ajax发送,能力有限,在此仅写下我开发项目过程中所得,分享出来供大家参考学习,下面话不多说,跟着小编来一起看看详细的介绍: 一.flask中的json数据接收 1.利用flask的request.form.get()方法 Python后台部分代码 from flask import Flask from flask import jsonify from flask import reque

  • node+vue前后端分离实现登录时使用图片验证码功能

    目录 后端代码 前端代码 获取验证码方法 登录验证方法 记录一下前端使用验证码登录的过程后端用的是node.js,关键模块是svg-captcha前端使用的是vue2最后的登录界面如下: 后端代码 先上代码,然后解释 const svgCaptcha = require('svg-captcha') exports.checkCode = (req, res) => { const img = svgCaptcha.create({ size: 4, ignoreChars: '0o1l', c

  • SpringBoot登录拦截配置详解(实测可用)

    背景:写一个用户登录拦截,在网上找了一圈没找到好用的,于是自己试验了一下,总结出来,分享给大家. 1.自定义登录拦截器LoginInterceptor public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) thr

  • Vue微信项目按需授权登录策略实践思路详解

    项目采用Vue作为开发框架,用户浏览页面时有两种情况: 一种是需要用户先登录之后才能继续浏览: 另一种是用户无需登录即可随意浏览. 在无需用户登录的页面中,可能包含需要用户信息的操作,此时就需要用户登录之后方能进行后续操作.因此,需要对授权登录策略进行区分. 思路 1.一般而言,我们为微信开发的H5页面,进入页面的时候就进行鉴权,要求用户登录之后才能继续浏览.但由于产品需求,这个项目我们需要对不同页面的鉴权策略进行划分,按照一般与特殊进行设计: 2.一般情况,用户进入页面第一时间要求用户授权登录

  • Spring Security 实现用户名密码登录流程源码详解

    目录 引言 探究 登录流程 校验 用户信息保存 引言 你在服务端的安全管理使用了 Spring Security,用户登录成功之后,Spring Security 帮你把用户信息保存在 Session 里,但是具体保存在哪里,要是不深究你可能就不知道, 这带来了一个问题,如果用户在前端操作修改了当前用户信息,在不重新登录的情况下,如何获取到最新的用户信息? 探究 无处不在的 Authentication 玩过 Spring Security 的小伙伴都知道,在 Spring Security 中

  • GoLang jwt无感刷新与SSO单点登录限制解除方法详解

    目录 前言 为什么使用JWT Cookie和Session token (header.payload.signature) token 安全性 基于token安全性的处理 客户端与服务端基于无感刷新流程图 golang实现atoken和rtoken 颁发token 校验token 无感刷新token 完整实现代码 SSO(Single Sign On)单用户登录以及无感刷新token 实现思路 实战代码 小结 前言 为什么使用JWT Jwt提供了生成token以及token验证的方法,而tok

  • SpringMVC中的拦截器详解及代码示例

    本文研究的主要是SpringMVC中的拦截器的介绍及实例代码,配置等内容,具体如下. Springmvc的处理器拦截器类似于Servlet 开发中的过滤器Filter,用于对处理器进行预处理和后处理.本文主要总结一下springmvc中拦截器是如何定义的,以及测试拦截器的执行情况和使用方法. 1. springmvc拦截器的定义和配置 1.1 springmvc拦截器的定义 在springmvc中,定义拦截器要实现HandlerInterceptor接口,并实现该接口中提供的三个方法,如下: /

  • uni-app微信小程序登录并使用vuex存储登录状态的思路详解

    微信小程序注册登录思路 (这是根据自身的项目的思路,不一定每个项目都适用) 1.制作授权登录框,引导用户点击按钮 2.uni.login获取code 3.把code传给后端接口,后端返回如下数据 openid: "ogtVM5RWdfadfasdfadfadV5s" status: 1 // 状态码:status==0(该用户未注册,需调用注册接口) status==1(该用户已注册) 4.判断用户是否注册,并调用用户信息接口 (1)若已注册则提示登录成功,并调用后台给的获取用户信息的

随机推荐