0
点赞
收藏
分享

微信扫一扫

图解微信小程序用户登录解决方案 + 代码实现

晚熟的猫 2022-04-04 阅读 43

微信官方文档:

图解

下图是参考官方再加入本人的思路,红色字体为本人登录的思路
在这里插入图片描述

代码实现(供参考)

1. controller 层

package com.mszlu.courseware.controller;

import com.mszlu.courseware.common.Result;
import com.mszlu.courseware.model.WXAuth;
import com.mszlu.courseware.service.UserService;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("user")
@Slf4j
public class UserController {

    @Autowired
    private UserService userService;
    @GetMapping("getSessionId")
    public Result getSessionId(String code) {
        return userService.getSessionId(code);
    }

    @PostMapping("/authLogin")
    public Result authLogin(@RequestBody WXAuth wxAuth) {
        Result result = userService.authLogin(wxAuth);
        log.info("{}", result);
        return result;
    }
    
    //这是登录后获取用户信息的接口,可以选择不看这部分
    @GetMapping("userinfo")
    public Result userinfo(@RequestHeader("Authorization") String token,Boolean refresh){
        return userService.userinfo(token, refresh);
    }

}

2. service 层

package com.mszlu.courseware.service;

import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.mszlu.courseware.common.RedisKey;
import com.mszlu.courseware.common.Result;
import com.mszlu.courseware.mapper.UserMapper;
import com.mszlu.courseware.model.WXAuth;
import com.mszlu.courseware.model.WxUserInfo;
import com.mszlu.courseware.pojo.User;
import com.mszlu.courseware.pojo.dto.UserDto;
import com.mszlu.courseware.utills.JWTUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class UserService {
    @Value("${wxmini.appid}")
    private String appid;
    @Value("${wxmini.secret}")
    private String secret;

    @Autowired
    private StringRedisTemplate redisTemplate;
    public Result getSessionId(String code) {
        /**
         * 1. 拼接一个微信登录凭证登录校验接口
         * 2. 发起一个 http 的调用, 获取微信的返回结果
         * 3. 存到 Redis
         * 4. 生成一个 sessionId, 返回给前端,作为当前需要登录用户的标识
         * 5. 生成一个 sessionId 用户在点击微信登录的时候, 我们可以标识是谁点击微信登录
         */
        String url = "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code";
        String replaceUrl = url.replace("{0}", appid).replace("{1}", secret).replace("{2}", code);
        final String res = HttpUtil.get(replaceUrl);//返回的是Json字符串
        String uuid = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(RedisKey.WX_SESSION_ID + uuid, res, 30, TimeUnit.MINUTES);
        Map<String, String> map = new HashMap<>();
        map.put("sessionId", uuid);
        return Result.SUCCESS(map);

    }

    @Autowired
    private WxService wxService;
    @Resource
    private UserMapper userMapper;
    public Result authLogin(WXAuth wxAuth) {
        /**
         * 1. 通过 wxAuth中的值, 要对它进行解密
         * 2. 解密完成后会获取微信用户信息 其中包含 openID,性别,昵称,头像等信息
         * 3. openID 是唯一都得,需要去 user 表中查询 openID 是否存在,以此用户的身份登录成功
         * 4。 不存在, 新用户,注册流程, 登录成功
         * 5. 使用 jwt 技术,生成一个token,提供给前端 token 令牌, 用户在下次访问的时候,携带 token 来访问
         * 6. 后端通过对 token 验证,知道此用于是否处于登录状态,以及是哪个用户登录的
         */
        try {
            final String json = wxService.wxDecrypt(wxAuth.getEncryptedData(), wxAuth.getSessionId(), wxAuth.getIv());
            final WxUserInfo wxUserInfo = JSON.parseObject(json, WxUserInfo.class);
            final User user = userMapper.selectOne(Wrappers.<User>lambdaQuery().eq(User::getOpenId, wxUserInfo.getOpenId()));
            UserDto userDto = new UserDto();
            userDto.from(wxUserInfo);
            if (user == null) {
                //注册
                return this.register(userDto);
            } else {
                //登录
                userDto.setId(user.getId());
                return this.login(userDto);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Result.FAIL();
    }

    private Result login(UserDto userDto) {
        String token = JWTUtils.sign(userDto.getId());
        userDto.setToken((token));
        userDto.setOpenId(null);
        userDto.setWxUnionId(null);
        // 需要吧 token 存入 redis, value 值为 userDto, 下次用户登录访问需要登录的时候,可以根据 token 拿到用户的详细信息
        redisTemplate.opsForValue().set(RedisKey.TOKEN + token, JSON.toJSONString(userDto), 7, TimeUnit.DAYS);
        return Result.SUCCESS(userDto);
    }

    private Result register(UserDto userDto) {
        // 注册之前 判断用户是否存在
        User user = new User();
        BeanUtils.copyProperties(userDto, user);
        this.userMapper.insert(user);
        userDto.setId(user.getId());
        return this.login(userDto);
    }

    public Result userinfo(String token, Boolean refresh) {
        /**
         * 1. 根据 token 来验证此 token 是否有效
         * 2. refresh 如果为 true 代表刷新 重新生成 token 和 Redis 里面重新存储 续期
         * 3. false 直接返回用户信息 -》 redis 中查询出来 直接返回
         */
        token = token.replace("Bearer ","");
        boolean verify = JWTUtils.verify(token);
        if (!verify) {
            return Result.FAIL("未登录");
        }
        final String userJson = redisTemplate.opsForValue().get(RedisKey.TOKEN + token);
        if (StringUtils.isBlank(userJson)) {
            return Result.FAIL("未登录");
        }
        final UserDto userDto = JSON.parseObject(userJson, UserDto.class);
        if (refresh) {
            token = JWTUtils.sign(userDto.getId());
            userDto.setToken(token);
            redisTemplate.opsForValue().set(RedisKey.TOKEN + token, JSON.toJSONString(userDto), 7, TimeUnit.DAYS);
        }
        return Result.SUCCESS(userDto);
    }
}

3.工具类

package com.mszlu.courseware.utills;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.io.UnsupportedEncodingException;
import java.util.Date;

public class JWTUtils {
    public static final String AUTH_HEADER_KEY = "Authorization";

    public static final String TOKEN_PREFIX = "Bearer ";
    /**
     * 过期时间一周
     */
    private static final long EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000;

    private static final String secret = "43dcbc5b-8776-429e-b122-3cae6bd97020";

    /**
     * 校验token是否正确
     *
     * @param token 密钥
     * @return 是否正确
     */
    public static boolean verify(String token) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .build();
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception exception) {
            return false;
        }
    }

    /**
     * 获得token中的信息无需secret解密也能获得
     *
     * @return token中包含的id
     */
    public static Long getUserId(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("id").asLong();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 签发token
     *
     * @return 加密的token
     */
    public static String sign(Long id) {
        try {
            Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            Algorithm algorithm = Algorithm.HMAC256(secret);
            // 附带username信息
            return JWT.create()
                    .withClaim("id", id)
                    .withExpiresAt(date)
                    .sign(algorithm);
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }

    /**
     * 判断过期
     *
     * @param token
     * @return
     */
    public static boolean isExpire(String token) {
        DecodedJWT jwt = JWT.decode(token);
        return System.currentTimeMillis() > jwt.getExpiresAt().getTime();
    }
}

4. 微信返回数据解密的service类

package com.mszlu.courseware.service;

import cn.hutool.core.codec.Base64;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.mszlu.courseware.common.RedisKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Random;

@Component
public class WxService {
    @Autowired
    private StringRedisTemplate redisTemplate;

    public String wxDecrypt(String encryptedData, String sessionId, String vi) throws Exception {
        // 开始解密
        String json =  redisTemplate.opsForValue().get(RedisKey.WX_SESSION_ID + sessionId);
        JSONObject jsonObject = JSON.parseObject(json);
        String sessionKey = (String) jsonObject.get("session_key");
        byte[] encData = cn.hutool.core.codec.Base64.decode(encryptedData);
        byte[] iv = cn.hutool.core.codec.Base64.decode(vi);
        byte[] key = Base64.decode(sessionKey);
        AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        return new String(cipher.doFinal(encData), "UTF-8");
    }





    //生成随机用户名,数字和字母组成,
    public String getStringRandom(int length) {

        StringBuilder val = new StringBuilder();
        Random random = new Random();

        //参数length,表示生成几位随机数
        for (int i = 0; i < length; i++) {

            String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";
            //输出字母还是数字
            if ("char".equalsIgnoreCase(charOrNum)) {
                //输出是大写字母还是小写字母
                int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;
                val.append((char) (random.nextInt(26) + temp));
            } else {
                val.append(random.nextInt(10));
            }
        }
        return val.toString();
    }
}

5. 模型类(model 层)

package com.mszlu.courseware.model;

import lombok.Data;

@Data
public class WXAuth {
    private String encryptedData; //目标密文
    private String iv; //解密算法初始向量
    private String sessionId;
}
package com.mszlu.courseware.model;

import lombok.Data;

@Data
public class WxUserInfo {

    private String openId;

    private String nickName;

    private String gender;

    private String city;

    private String province;

    private String country;

    private String avatarUrl;

    private String unionId;
}

6. 实体类(pojo层)

package com.mszlu.courseware.pojo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

/**
 */
@Data
@TableName("user")
public class User implements Serializable {

    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    private String nickname;

    private String username;

    private String password;

    private String gender;

    /**
     * 头像
     */
    private String portrait;

    /**
     * 背景图片
     */
    private String background;


    private String phoneNumber;

    private String openId;

    private String wxUnionId;
    
}

7. dto 层

package com.mszlu.courseware.pojo.dto;


import com.mszlu.courseware.model.WxUserInfo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDto implements Serializable {

    private Long id;
    private String nickname;
    //更新的时候可以为null(代表不更新)
    private String username;
    @NotNull
    private String password;
    private String gender;
    @NotNull
    private String phoneNumber;
    /**
     * 背景图片
     */
    private String background;
    private String portrait;

    private String openId;

    private String wxUnionId;

    //dto拓展属性
    private String token;
    List<String> permissions;
    List<String> roles;
    //验证码
    private String code;

    public void from(WxUserInfo wxUserInfo) {
        this.nickname = wxUserInfo.getNickName();
        this.portrait = wxUserInfo.getAvatarUrl();
        this.username = "";
        this.password = "";
        this.phoneNumber = "";
        this.gender = wxUserInfo.getGender();
        this.openId = wxUserInfo.getOpenId();
        this.wxUnionId = wxUserInfo.getUnionId();
    }
}

了解微信登录最重要还是把整个流程搞清楚,看懂上图即可,代码实现相信还是比较容易的

举报

相关推荐

0 条评论