若依分离版 设置账号只能同时在线一个用户

遇到这个需求,暂时记录一下,思考,如果允许在线人数,又应该咋设置


1、application.yml新增一个配置soloLogin用于限制多终端同时登录。

修改token的配置

# token配置
token:
  # 令牌自定义标识
  header: Authorization
  # 令牌密钥
  secret: abcdefghijklmnopqrstuvwxyz
  # 令牌有效期(默认30分钟)
  expireTime: 30
  # 是否允许账户多终端同时登录(true允许 false不允许)
  soloLogin: false


2、Constants.java新增一个常量LOGIN_USERID_KEY公用

    /**
     * 登录用户编号 redis key
     */
    public static final String LOGIN_USERID_KEY = "login_userid:";


3、调整TokenService.java,存储&刷新缓存用户编号信息

  // 是否允许账户多终端同时登录(true允许 false不允许)
    @Value("${token.soloLogin}")
    private boolean soloLogin;
    /**
     * 删除用户身份信息
     */
    public void delLoginUser(String token, Long userId)
    {
        if (StringUtils.isNotEmpty(token))
        {
            String userKey = getTokenKey(token);
            redisCache.deleteObject(userKey);
        }
        if (!soloLogin && StringUtils.isNotNull(userId))
        {
            String userIdKey = getUserIdKey(userId);
            redisCache.deleteObject(userIdKey);
        }
    }

    /**
     * 刷新令牌有效期
     *
     * @param loginUser 登录信息
     */
    public void refreshToken(LoginUser loginUser)
    {
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
        // 根据uuid将loginUser缓存
        String userKey = getTokenKey(loginUser.getToken());
        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
        if (!soloLogin)
        {
            // 缓存用户唯一标识,防止同一帐号,同时登录
            String userIdKey = getUserIdKey(loginUser.getUser().getUserId());
            redisCache.setCacheObject(userIdKey, userKey, expireTime, TimeUnit.MINUTES);
        }
    }
 private String getUserIdKey(Long userId)
    {
        return Constants.LOGIN_USERID_KEY + userId;
    }


4、自定义退出处理类LogoutSuccessHandlerImpl.java清除缓存方法添加用户编号

  /**
     * 退出处理
     *
     * @return
     */
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException
    {
        LoginUser loginUser = tokenService.getLoginUser(request);
        if (StringUtils.isNotNull(loginUser))
        {
            String userName = loginUser.getUsername();
            // 删除用户缓存记录
            tokenService.delLoginUser(loginUser.getToken(), loginUser.getUser().getUserId());
            // 记录用户退出日志
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, "退出成功"));
        }
        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(HttpStatus.SUCCESS, "退出成功")));
    }


5、登录方法SysLoginService.java,验证如果用户不允许多终端同时登录,清除缓存信息

// 是否允许账户多终端同时登录(true允许 false不允许)
@Value("${token.soloLogin}")
private boolean soloLogin;
 /**
     * 登录验证
     *
     * @param username 用户名
     * @param password 密码
     * @param code 验证码
     * @param uuid 唯一标识
     * @return 结果
     */
    public String login(String username, String password, String code, String uuid)
    {
        //获取验证码开关
        boolean captchaEnabled = configService.selectCaptchaEnabled();
        // 验证码开关
        if (captchaEnabled)
        {
            validateCaptcha(username, code, uuid);
        }
        // 用户验证
        Authentication authentication = null;
        try
        {
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
            AuthenticationContextHolder.setContext(authenticationToken);
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager.authenticate(authenticationToken);
        }
        catch (Exception e)
        {
            if (e instanceof BadCredentialsException)
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            }
            else
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                throw new ServiceException(e.getMessage());
            }
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        if (!soloLogin)
        {
            // 如果用户不允许多终端同时登录,清除缓存信息
            String userIdKey = Constants.LOGIN_USERID_KEY + loginUser.getUser().getUserId();
            String userKey = redisCache.getCacheObject(userIdKey);
            if (StringUtils.isNotEmpty(userKey))
            {
                redisCache.deleteObject(userIdKey);
                redisCache.deleteObject(userKey);
            }
        }
        recordLoginInfo(loginUser.getUserId());


        // 生成token
        return tokenService.createToken(loginUser);
    }

<think>嗯,用户的问题是关于在若依前后端分离架构中实现微信授权登录的教程。我需要先理解若依框架的基本结构,以及前后端分离的常见做法。若依是一个基于Spring Boot和Vue的开源管理系统,所以后端可能用Spring Boot,前端用Vue。微信授权登录通常涉及OAuth2.0流程,需要获取code,再用code换access_token和用户信息。 首先,用户需要注册微信开放平台账号,获取AppID和AppSecret,这是必须的。然后,配置授权回调域名,这在微信的文档里有说明。接下来,后端需要处理微信的跳转,生成带参数的授权URL,引导用户跳转到微信登录页面。用户同意授权后,微信会重定向到指定的回调地址,并携带code参数。后端需要接收这个code,并向微信服务器请求access_token和openid。之后,后端可以根据openid判断用户是否已注册,若未注册则创建新用户,并生成自己的token(如JWT)返回给前端。 前端部分,Vue需要处理路由跳转,比如在登录页面放置微信登录的按钮,点击后跳转后端生成的授权URL。授权成功后,前端从URL参数中获取code,发送给后端,然后轮询或等待后端返回登录结果,最后更新用户状态并跳转到首页。 可能需要注意的地方包括:前后端分离时的跨域问题,需要正确配置CORS;回调地址必须是备案过的域名,且与微信配置一致;code只能使用一次且有效时间短,需及时处理;用户信息的加密传输和存储,确保安全性。此外,参考了用户提供的引用资料,比如引用[2]提到redirect_url是前端路由,引用[5]中的轮询机制,这些都需要在实现时考虑进去。 用户可能还关心具体的代码示例,比如后端如何生成授权URL,如何处理回调,以及前端如何发起请求和处理响应。需要确保步骤清晰,代码正确,并且符合若依框架的结构。同时,要提醒用户替换示例中的占位符,如APP_ID和回调地址,以及处理异常情况,比如网络错误或用户拒绝授权的情况。</think>### 若依前后端分离架构实现微信授权登录步骤 #### 一、前置条件准备 1. **注册微信开放平台账号** 获取`AppID`和`AppSecret`[^2] 2. **配置授权回调域名** 在微信开放平台设置`授权回调域名`(需备案域名) #### 二、后端实现(Spring Boot) 1. **生成授权URL接口** ```java @GetMapping("/wechat/authUrl") public String generateAuthUrl(@RequestParam String redirectUri) { String url = "https://open.weixin.qq.com/connect/oauth2/authorize" + "?appid=" + APP_ID + "&redirect_uri=" + URLEncoder.encode(redirectUri) + "&response_type=code" + "&scope=snsapi_userinfo" + "&state=STATE#wechat_redirect"; return url; } ``` 2. **处理回调获取用户信息** ```java @GetMapping("/wechat/callback") public ResponseEntity<?> handleCallback(@RequestParam String code) { // 1. 用code换取access_token String tokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" + "?appid=" + APP_ID + "&secret=" + APP_SECRET + "&code=" + code + "&grant_type=authorization_code"; // 2. 获取用户openid和access_token WechatTokenResponse tokenResp = restTemplate.getForObject(tokenUrl, WechatTokenResponse.class); // 3. 拉取用户详细信息 String userInfoUrl = "https://api.weixin.qq.com/sns/userinfo" + "?access_token=" + tokenResp.getAccessToken() + "&openid=" + tokenResp.getOpenid() + "&lang=zh_CN"; WechatUserInfo userInfo = restTemplate.getForObject(userInfoUrl, WechatUserInfo.class); // 4. 创建/更新本地用户数据 SysUser user = userService.createOrUpdateWechatUser(userInfo); // 5. 生成JWT返回前端 String token = jwtUtils.generateToken(user); return ResponseEntity.ok(new LoginResult(token)); } ``` #### 三、前端实现(Vue) 1. **触发授权跳转** ```vue <template> <button @click="handleWechatLogin">微信登录</button> </template> <script> export default { methods: { async handleWechatLogin() { const redirectUri = encodeURIComponent(window.location.origin + '/auth-callback') const { data: authUrl } = await getAuthUrl(redirectUri) window.location.href = authUrl } } } </script> ``` 2. **处理回调页面** ```vue // auth-callback.vue created() { const code = this.$route.query.code if (code) { this.$store.dispatch('loginByWechat', code).then(() => { this.$router.push('/dashboard') }) } } ``` #### 四、关键配置项 1. **Spring Security配置** ```java @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/wechat/**").permitAll() // 其他配置... } ``` 2. **application.yml配置** ```yaml wechat: app-id: your_appid app-secret: your_appsecret redirect-uri: 前端回调页面路径 ``` #### 五、注意事项 1. **跨域问题** 需配置CORS允许前端域名访问接口[^1] 2. **安全增强** - 校验state参数防止CSRF攻击 - 用户敏感信息需加密存储 - 建议添加登录态过期时间(如JWT有效期)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值