厚积薄发打卡Day85: Shiro+JWT+Redis实现微信小程序实现单点登录(上)<前置>

4 篇文章 0 订阅
1 篇文章 0 订阅

什么是单点登录?

定义:

单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

简单来说就是多个服务器的“REMEMBER ME”功能。

扩展阅读:什么是单点登录(SSO)

使用技术

JWT

  1. JWT(Json web token ):由于HTTP是无状态的协议,这意味着服务器无法确认用户的信息,因此我们则需要JWT来代替Session,告知服务器并亮明身份。

    在这里插入图片描述

Shiro

  1. Shiro:是一个轻量级、灵活度高的鉴权框架,系统之前已做好shiro作为登录和权限控制,通过自定义实现realm实现用户名、密码验证登录
    1. 认证就是要核验用户的身份,比如说通过用户名和密码来检验用户的身份。说简单一些,认证就是登陆。登陆之后Shiro要记录用户成功登陆的凭证。
    2. 授权是比认证更加精细度的划分用户的行为。比如说一个教务管理系统中,学生登陆之后只能查看信息,不能修改信息。而班主任就可以修改学生的信息。这就是利用授权来限定不同身份用户的行为。
    3. Shiro可以利用HttpSession或者Redis存储用户的登陆凭证,以及角色或者身份信息。然后利用过滤器(Filter),对每个Http请求过滤,检查请求对应的HttpSession或者Redis中的认证与授权信息。如果用户没有登陆,或者权限不够,那么Shiro会向客户端返回错误信息。

Redis

  1. Redis:储存用户的登录凭证,也就是Token,并设计一定的有效期,可实现在一定时间内的单点登录。

实现步骤:

用户在Emos登陆页面点击登陆按钮,然后小程序把临时授权字符串提交给后端Java系统。后端Java系统拿着临时授权字符串换取到openid,我们查询用户表中是否存在这个openid。如果存在,意味着该用户是已注册用户,可以登录。如果不存在,说明该用户尚未注册,目前还不是我们的员工,所以禁止登录。

在这里插入图片描述

总体流程如上图所示:

  1. 通过小程序端获取临时授权码,
  2. 传到后端获取到与用户唯一对应的openId,
  3. 根据openId获取对应用户信息实现登录
    1. 通过Shiro鉴权获取
    2. 通过JWT获取token
    3. 存入redis

获取openId登录:

参考官方文档时序图:

在这里插入图片描述

  • 前端:

    • 具体:uni.login(Object)

      • uni-app框架中,包含了原生微信小程序的wx对象,我们可以像写原生微信小程序代码一样,通过wx对象调用各种方法。使用uni跨平台的对象,我们调用uni对象中的方法,HBuilderX在编译代码的时候,会把uni对象翻译成目标平台的对象,比如微信平台就是wx对象,支付宝平台就是my对象,所以uni对象的跨平台性更好

      • 返回参数:

        参数名说明
        authResult登录服务商提供的登录信息,服务商不同返回的结果不完全相同
        code小程序专有,用户登录凭证。开发者需要在开发者服务器后台,使用 code 换取 openid 和 session_key 等信息
        errMsg描述信息
    • 实例代码:

      login: function() {
          let that = this;
          uni.login({
              provider: 'weixin',
              success: function(resp) {
                  let code = resp.code;
                  let token = uni.getStorageSync('token');
                  that.ajax(that.url.login, 'POST', { code: code }, function(resp) {
                      let permission = resp.data.permission;
                      uni.setStorageSync('permission', permission);
                      //跳转到登陆页面
                      uni.switchTab({
                          url: '../index/index'
                      });
                  });
              },
              fail: function(e) {
                  uni.showToast({
                      icon: 'none',
                      title: '执行异常'
                  });
              }
          });
      }
      
      
  • 后端:

    • 获取微信小程序openId方法

      private String getOpenId(String code) {
          String url = "https://api.weixin.qq.com/sns/jscode2session";
          HashMap map = new HashMap<String, Object>();
          map.put("appid", appId);
          map.put("secret", appSecret);
          //刚刚获取的临时授权字符串
          map.put("js_code", code);
          map.put("grant_type", "authorization_code");
          String response = HttpUtil.post(url, map);
          JSONObject json = JSONUtil.parseObj(response);
          String openId = json.getStr("openid");
          if (StrUtil.isEmpty(openId)) {
              throw new RuntimeException("临时登陆凭证异常");
          }
          return openId;
      }
      
    • 通过具体获取的openId去找对应的用户

    • 获取消息,并跳转至首页

创建Token

  • 导入依赖:

    <dependency>
    	<groupId>com.auth0</groupId>
    	<artifactId>java-jwt</artifactId>
    	<version>3.10.3</version>
    </dependency>
    
  • 根据userId 创建token

    @Component
    @Slf4j
    public class JwtUtil {
        //密钥
        @Value("${emos.jwt.secret}")
        private String secret;
    
        //过期时间
        @Value("${emos.jwt.expire}")
        private int expire;
    
        public String createToken(int userId) {
            Date date = DateUtil.offset(new Date(), DateField.DAY_OF_YEAR, expire).toJdkDate();
            Algorithm algorithm = Algorithm.HMAC256(secret); //创建加密算法对象
            JWTCreator.Builder builder = JWT.create();
            String token = builder.withClaim("userId", userId).withExpiresAt(date).sign(algorithm);
            return token;
        }
    
    
        public int getUserId(String token) {
            try {
                DecodedJWT jwt = JWT.decode(token);
                return jwt.getClaim("userId").asInt();
            } catch (Exception e) {
                throw new EmosException("令牌无效");
            }
        }
    
        public void verifierToken(String token) {
            Algorithm algorithm = Algorithm.HMAC256(secret); //创建加密算法对象
            JWTVerifier verifier = JWT.require(algorithm).build();
            verifier.verify(token);
        }
    }
    

实现登录逻辑

  • Service层:

    @Slf4j
    @Service
    @Scope("prototype")  //多例防止多个用户同时登录
    public class UserServiceImpl implements UserService {
        //...
    
        @Override
        public Integer login(String code) {
            // 根据获取的授权码获取微信账号的userid
            String openId = getOpenId(code);
            //根据openId找对应的用户信息
            Integer id = userDao.searchIdByOpenId(openId);
            if (id == null) {
                throw new EmosException("账户不存在");
            }
            //从消息队列中接收消息,转移到消息表
            messageTask.receiveAysnc(id+"");
            return id;
        }
        //...
    }
    
  • Web层:

    @Data
    @ApiModel
    public class LoginForm {
        //使用validation框架做校验
        @NotBlank(message = "临时授权不能为空")
        private String code;
    }
    
    @RestController
    @RequestMapping("/user")
    @Api("用户模块web接口")
    public class UserController {
        @PostMapping("/login")
        @ApiOperation("登陆系统")
        public R login(@Valid @RequestBody LoginForm form, @RequestHeader("token") String token) {
            Integer id;
            if (StrUtil.isNotEmpty(token)) {
                try {
                    //验证令牌的有效性
                    jwtUtil.verifierToken(token);   
                } catch (TokenExpiredException e) {
                    //如果令牌过期就生成新的令牌
                    id = userService.login(form.getCode());
                    token = jwtUtil.createToken(id);
                    saveCacheToken(token, id);
                }
                id = jwtUtil.getUserId(token);
            } else {
                id = userService.login(form.getCode());
                token = jwtUtil.createToken(id);
                saveCacheToken(token, id);
            }
            Set<String> permsSet = userService.searchUserPermissions(id);
            return R.ok("登陆成功").put("token", token).put("permission", permsSet);
        }
    
        //将token保存至redis
        //下一步详解
        private void saveCacheToken(String token, int userId) {
            redisTemplate.opsForValue().set(token, userId + "", cacheExpire, TimeUnit.DAYS);
        }
    }
    

RedisTemplate

参考:RedisTemplate操作Redis,这一篇文章就够了

SpringBoot集成redis操作类:

  • maven依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  • 使用方法:

    //直接注入即可
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
  • 常用api:

    不同操作类型对象:

    在这里插入图片描述

    分别对应geo、hash、list、set、String、zset等Redis数据类型

    • 常用String,即boundValueOps

      ValueOperations<String, String> ops = redisTemplate.opsForValue();
      //void set(K key, V value, long timeout, TimeUnit unit);
      ops.set(token, userId + "", cacheExpire, TimeUnit.DAYS);
      //void set(K key, V value);
      //直接保存键值对
      
      //根据key直接删除value
      Boolean result = redisTemplate.delete(token);
      
      //顺序递增
      redisTemplate.boundValueOps(token).increment(3L);
      //顺序递减
      redisTemplate.boundValueOps(token).increment(-3L);
      
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值