怎么實現同一賬號只能在一台設備登錄


同一賬號只能在一台設備登錄實現思路。

注意:登錄是在白名單(直接放行的接口)。生成的token攜帶賬號信息。

1.用戶每次登錄生成token時,將賬號當成key,token當成value,以token的過期時間存入redis中。

2.用戶訪問的時候,在攔截器解析token,獲取賬號,拿賬號去redis中獲取value,如果是value的token與當前用戶攜帶過來的token一致就放行。如果不一致,則告訴前端重復登錄,讓前端清除token,跳轉到登錄頁面。

3.用戶在另一台設備登錄時,也是存入redis,這樣就刷新了token的值和redis的過期時間。

這是我的一個實現思路,有沒有大佬可以指點,互相學習一下。

寫了個小demo,主要代碼如下:

1.攔截器代碼:

package com.yblue.config;


import com.yblue.dto.Payload;
import com.yblue.dto.UserInfo;
import net.minidev.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;

/**
 * @author: JiaXinMa
 * @description: 解決跨域和權限
 * @date: 2021/3/26
 */
public class AuthInterceptor implements HandlerInterceptor {
    @Autowired
    FilterProperties filterProperties;

    @Autowired
    JwtProperties jwtProps;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //表示接受任意域名的請求,也可以指定域名
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("origin"));

        //該字段可選,是個布爾值,表示是否可以攜帶cookie
        response.setHeader("Access-Control-Allow-Credentials", "true");

        response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS");

        response.setHeader("Access-Control-Allow-Headers", "*");

        //預請求,這里可以不加,但是其他語言開發的話記得處理options請求
        // 如:異步請求的時候前端發兩次請求,其中一次是測試通不通
        if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
            return true;
        }
        //獲取當前訪問的URL接口后的路徑
        String uri = request.getRequestURI(); //如:/api/user/xxx

        //判斷uri地址,如果為 /error 則可能是請求方式、參數、訪問路徑不對
        if (uri.equals("/error")) {
            this.responseError(response, 405, "請求方式、參數類型、訪問地址錯誤、數據庫方面異常!");
            return false;
        }

        //1.判斷當前訪問路徑是否在白名單列表中
        List<String> allowPaths = filterProperties.getAllowPaths();
        for (String allowPath : allowPaths) {
            if (uri.contains(allowPath)) {
                //如果在,直接放行了
                return true;
            }
        }


        //2.校驗token合法性
        Payload<UserInfo> payload = null;
        try {
            String token = request.getHeader(jwtProps.getCookie().getCookieName());
//            String token = CookieUtils.getCookieValue(request, jwtProps.getCookie().getCookieName());
            payload = JwtUtils.getInfoFromToken(token, jwtProps.getPublicKey(), UserInfo.class);

            String redisToken = redisTemplate.opsForValue().get(payload.getInfo().getUsername());
            if (token.equals(redisToken)) {
                return true;
            } else {
                this.responseError(response, 401, "您的賬號在另一台設備上登錄,請及時修改密碼!"); }
        } catch (Exception e) {
            this.responseError(response, 401, "登錄失效或未登錄!");
            return false;
        }

        //3.如果你的想校驗權限、角色,可以在你的userInfo里封裝,然后在下面驗證,不過我現在的userInfo沒放
//        UserInfo userInfo = payload.getInfo();
//        List<Module> modules = userInfo.getModules();
//        for (Module module : modules) {
//            if (uri.equals(module.getUrl())) {//判斷用戶是否有訪問路徑的權限
//                return true;
//            } else {
//                this.responseError(response, 403, "權限不足!");
//            }
//        }

        return false;
    }

    /**
     * @author: JiaXinMa
     * @description: 響應錯誤代碼 處理響應錯誤信息的方法,可以拿去用
     * @date: 2021/8/23
     */
    public void responseError(HttpServletResponse response, Integer code, String returnMessage) {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject.put("code", code);
            jsonObject.put("msg", returnMessage);
            response.getWriter().append(jsonObject.toString());
        } catch (Exception e) { e.printStackTrace(); } }


    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }

}

2.業務層代碼:

package com.yblue.service;


import com.yblue.config.JwtProperties;
import com.yblue.config.JwtUtils;
import com.yblue.domain.User;
import com.yblue.dto.Payload;
import com.yblue.dto.UserInfo;
import com.yblue.exception.pojo.ExceptionEnum;
import com.yblue.exception.pojo.MdException;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author: JiaXinMa
 * @description: 驗證授權 Service層
 * @date: 2021/3/25
 */
@Service
@Transactional
public class AuthService {

    @Autowired
    UserService userService;

    @Autowired
    private JwtProperties jwtProps;

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * @author: JiaXinMa
     * @description: 登錄
     * @date: 2021/5/28
     */
    public Map login(String username, String password) {
        try {
            Map<String, Object> map = new HashMap();//存放token和用戶信息

            //1. 判斷用戶名和密碼是否正確
            User loginUser = userService.findUserByNameAndPwd(username, password);

            UserInfo userInfo = new UserInfo(loginUser.getUserId(), loginUser.getName(), loginUser.getUsername());

            //2.生成token
            String token = this.generateToken(userInfo);
            map.put(jwtProps.getCookie().getCookieName(), token);
            map.put("userInfo", userInfo);


            //3.將token放進redis中
            redisTemplate.opsForValue().set(loginUser.getUsername(), token, 60, TimeUnit.MINUTES);

            return map;
        } catch (Exception e) {
            throw new MdException(ExceptionEnum.INVALID_USERNAME_PASSWORD);
        }
    }

    /**
     * @author: JiaXinMa
     * @description: 生成token
     * @date: 2021/4/6
     */
    public String generateToken(UserInfo info) {
        //利用JwtUtils+私鑰生成加密token
        //以前是的工具類是通過cookies帶過去了,然后現在他們說要放在請求頭上,所以那些類名字沒改,不妨礙業務的使用
        return JwtUtils.generateTokenExpireInMinutes(info, jwtProps.getPrivateKey(), jwtProps.getCookie().getExpire());
    }
}

3.控制層代碼

package com.yblue.controller;

import com.yblue.domain.User;
import com.yblue.service.AuthService;
import com.yblue.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
 * @author: JiaXinMa
 * @description: 驗證 控制器
 * @date: 2021/5/28
 */
@Slf4j
@RestController
@RequestMapping("/api")
public class AuthController {

    @Autowired
    AuthService authService;

    @Autowired
    UserService userService;

    @PostMapping("/auth/login")
    public Map login(@RequestBody User user) {
        log.info("/api/auth/login:{}", user);
        return authService.login(user.getUsername(), user.getPassword());
    }

    @GetMapping("/user/findByUserId")
    public User findByUserId(@RequestParam("userId") Integer userId) {
        log.info("/api/user/findByUserId:{}", userId);
        return userService.findByUserId(userId);
    }
}

效果如下圖:

第一次:

 第二次:

 

 

 如果有些小伙伴有更好的解決方案或者覺得該方案有什么不足的,可以提出來一起討論交流。

 

想看更多精彩內容,可以關注我的CSDN

我的CSDN


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM