微信登陆过程
在项目开发中,难免会遇到微信授权登录这一操作,本讲来讲一下微信登陆是如何实现的?
关于校验登录,有诸多方法,记录方法如下:
- 使用Spring MVC提供的拦截器
- 网关服务全局过滤器
- 使用AOP面向横切面实现
对于使用Spring MVC提供的拦截器来实现,其大致的思路如下:
注意:
- 用户登录成功以后,会生成对应的token,并且会将登录用户的信息存储到Redis中(键:token 值:userInfo),然后将token返回给前端
- 需要在每一个业务微服务中定义拦截器,影响开发效率,代码维护性较低。(解决方案:可以将拦截器定义到公共的模块中)
对于使用网关服务的全局过滤器来完成校验登录, 需要开发全局过滤器(GlobalFilter)
对于使用AOP横切面完成统一登录校验逻辑,配合自定义注解,哪一个业务方法需要验证用户登录状态,就在该方法上添加自定义注解。
这里呢我们推荐使用第三种,因此对于有的服务来讲,是不需要客户端进行登陆操作就可以直接访问的,其自由度不高。我们可以使用自定义注解来完成登录的校验,这样对于有需要登录信息的接口我们只需要加上我们自定义的注解即可,对于不需要登录信息的接口就可以不用加自定义注解。
1. AOP实现登录校验步骤
1.1 自定义登陆注解
首先自定义一个注解,该注解为登陆注解。
注解作用:哪些需要登录才能访问必须要添加,那些需要获取到用户Id 也必须加这个注解。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Login {boolean required() default true; // 是否必须要登录
}
1.2 自定义切面类
@Aspect
@Component
public class LoginAspect {@Autowiredprivate RedisTemplate<String , String> redisTemplate;@SneakyThrows@Around("execution(* com.*.*.api.*.*(..)) && @annotation(Login)") // 匹配方法上带有@Login注解的方法对其实现功能增强public Object loginAspect(ProceedingJoinPoint point, Login login){// 获取HttpServletRequest对象RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();ServletRequestAttributes sra = (ServletRequestAttributes) requestAttributes;HttpServletRequest request = sra.getRequest();String token = request.getHeader("token");// 判断是否需要登录if (login.required()){// 必须要登录,token 为空是抛出异常if (StringUtils.isEmpty(token)){throw new Exception(ResultCodeEnum.LOGIN_AUTH); // 没有token 要抛出异常}// 如果token 不为空,从缓存中获取信息.String userInfoJSON = this.redisTemplate.opsForValue().get(RedisConstant.USER_LOGIN_KEY_PREFIX + token);// 判断对象是否为空if (StringUtils.isEmpty(userInfoJSON)){throw new Exception(ResultCodeEnum.LOGIN_AUTH); // 抛出异常信息}// 将用户信息存储到ThreadLocal中// AuthContextHolder 底层就是 ThreadLocalUserInfo userInfo = JSON.parseObject(userInfoJSON, UserInfo.class);AuthContextHolder.setUserId(userInfo.getId());AuthContextHolder.setUsername(userInfo.getNickname());}else {// 不需要强制登录,但是,有可能需用到用户信息.if (!StringUtils.isEmpty(token)){// 如果token 不为空,从缓存中获取信息.String userInfoJSON = this.redisTemplate.opsForValue().get(RedisConstant.USER_LOGIN_KEY_PREFIX + token);if (!StringUtils.isEmpty(userInfoJSON)){// 将用户信息存储到ThreadLocal中UserInfo userInfo = JSON.parseObject(userInfoJSON, UserInfo.class);AuthContextHolder.setUserId(userInfo.getId());AuthContextHolder.setUsername(userInfo.getNickname());}}}try {// 执行目标方法并获取目标方法执行后的返回结果Object proceed = point.proceed();// 返回return proceed ;}catch (Throwable e) {e.printStackTrace();} finally {// 从ThreadLocal中删除数据,防止内存泄漏和移除AuthContextHolder.removeUserId();AuthContextHolder.removeUsername();}return null ;}
}
AuthContextHolder类:
/*** 获取当前用户信息帮助类*/
public class AuthContextHolder {private static ThreadLocal<Long> userId = new ThreadLocal<Long>();private static ThreadLocal<String> username = new ThreadLocal<String>();public static void setUserId(Long _userId) {userId.set(_userId);}public static Long getUserId() {return userId.get();// return 1L;}public static void removeUserId() {userId.remove();}public static void setUsername(String _username) {username.set(_username);}public static String getUsername() {return username.get();}public static void removeUsername() {username.remove();}
}
1.3 微信登陆流程介绍
参数说明:
1、前端调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
2、后端调用 auth.code2Session 接口,换取用户唯一标识 OpenID 、 用户在微信开放平台账号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台账
号)和 会话密钥 session_key。
3、之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。
注意事项:
1、会话密钥 session_key 是对用户数据进行 加密签名 的密钥。为了应用自身的数据安全,开发者服务器不应该把会话密钥下发到小程序,也不应该对外提供这个
密钥。
2、临时登录凭证 code 只能使用一次
配置WxMaService
WechatAccountConfig
定义一个实体类读取service-user-dev.yaml配置文件中关于微信开放平台的相关配置。
@Data
@ConfigurationProperties(prefix = "wechat.login")
public class WechatAccountConfig {private String appId; // 公众平台的appdIdprivate String appSecret; // 小程序微信公众平台秘钥
}// 启动类添加如下注解
@EnableConfigurationProperties(value = WechatAccountConfig.class)
配置文件中定义了微信授权登录的appId和appSecret
wechat:login:#小程序授权登录appId: # 小程序微信公众平台appIdappSecret: # 小程序微信公众平台api秘钥
WeChatMpConfig
WxMaService配置类
@Configuration
public class WeChatMpConfig {@Autowiredprivate WechatAccountConfig wechatAccountConfig;@Beanpublic WxMaService wxMaService(){WxMaDefaultConfigImpl wxMaConfig = new WxMaDefaultConfigImpl(); // 创建对象wxMaConfig.setAppid(wechatAccountConfig.getAppId());wxMaConfig.setSecret(wechatAccountConfig.getAppSecret());wxMaConfig.setMsgDataFormat("JSON");WxMaService service = new WxMaServiceImpl(); // 创建 WxMaService 对象service.setWxMaConfig(wxMaConfig); // 给 WxMaService 设置配置选项return service;}}
登录接口开发
@Operation(summary = "小程序授权登录")
@GetMapping("/wxLogin/{code}")
public Result<Map<String , String>> wxLogin(@PathVariable String code) throws WxErrorException {Map<String , String> tokenInfo = wxLoginService.wxLogin(code) ;return Result.build(tokenInfo , ResultCodeEnum.SUCCESS) ;
}
获取登录用户的openId信息
public void wxLogin(String code) throws WxErrorException {// 获取从微信服务端返回的结果WxMaJscode2SessionResult sessionInfo = wxMaService.getUserService().getSessionInfo(code);// 获取openIdString openId = sessionInfo.getOpenid();UserInfo userInfo = userInfoMapper.selectOne(new LambdaQueryWrapper<UserInfo>().eq(UserInfo::getWxOpenId, openId));// 创建tokenString token = UUID.randomUUID().toString().replaceAll("-","");// 进行后续操作...从存入Redis中等业务
}