マルチデバイスログインの考え方と実現について


1.需要:
異なるシステム(pc,Android,ios)は同時に登録でき,同じシステム(pcとpc,AndroidとAndroid,iosとios)は登録反発を形成する.
2.マルチシステムのワンポイントログインの問題:
1)どのようにシステムを区別しますか.
デバイス情報はrequest中のUser-Agentで取得し、システムキーワード区分システムが存在するか否かを判断することができる.モバイル側は、要求ヘッダに指定されたフィールドを追加して、どのシステムであるかを確認することもできます.
2)注文はどのように保証しますか.
現在のシステムにデバイスがログインしているかどうかを知るには、ログインしたシステムの情報とログイン時間を保存する必要があります.
3)どのように反発を形成しますか.
新しいデバイスのログイン時にデバイスに関する情報を取得し、古いデバイス情報を削除または置換する必要があります.すべてのtokenは可変ではありません.
3.既存環境:
java jkd1.8,springboot2.0,mysql5.7,redis5.0
4.初歩案:
1)token生成:ユーザがウェブサイトを使用する過程で変化しない情報に基づいてユーザ一意tokenを生成し、redisのkeyとして使用し、ユーザがログインする時にログインデバイスを取得し、ログインデバイスとログイン時間をvalueとしてredisに保存する.
疑似コード:
function String createToken(String tel,String pasword){
return md5Encoding(tel+password);
}
function String createValue(HttpservlertRequest request){
return getDeviceType(request)+(new Date().toString);
}
function boolean saveToken(String token,String value){
jedis.set(token,value,"NX","EX",1800);
}
2)認証:ブロッキング要求を使用してtokenの有無を判断する.tokenがある場合、tokenに基づいてredisに対応するvalueをクエリーします.現在のオペレーティングデバイスを取得し、デバイスがログインしているか(valueにいるかどうか)、存在する場合は期限切れであるかどうかを判断し、期限切れでない場合は認証に合格します.
疑似コード:
String token = request.getParameter("token");
String value = jedis.get(token);
String deviceType = getDeviceType(request);
if(value.get(deviceType)){
if(value.get(deviceType).get("time") > new Date()){
       return true;
}
}
return false;
3)token期限切れ:tokenの保存時にデバイスの期限切れ時間を一緒に保存し、要求時にデバイスログインが期限切れであるかどうかを判断し、期限切れでない場合は要求に成功し、同時にデバイスの期限切れ時間とredis中のtoken期限切れ時間を更新する.
疑似コード:
if(value.get(deviceType).get("time") > new Date()){
value.get(deviceType).set("time") = new Date()+30m
jedis.set(token,value,"XX","EX",1800);
       return true;
}
4)ログオン終了:ログオンしたデバイスにログオン状態があるかどうかを判断し、存在する場合はデバイスを削除し、デバイスがログオンしていない場合はredisでtokenを削除する.
5.具体的な実現:
1)クラスの作成ログイン情報を保存するには、次の手順に従います.
import java.util.Date;

/**
 * @author wxm
 * @date 2019/9/13.
 *     
 */
public class LoginInfo {

    /**
     *   id
     */
    private String userId;
    /**
     *       
     */
    private String android;
    /**
     *           
     */
    private Date androidTime;
    /**
     * iphone      
     */
    private String iphone;
    /**
     * iphone        
     */
    private Date iphoneTime;
    /**
     *  windows      
     */
    private String windows;
    /**
     * windows        
     */
    private Date windowsTime;
    /**
     * ipad      
     */
    private String ipad;
    /**
     * ipad        
     */
    private Date ipadTime;
    /**
     *         
     */
    private String other;
    /**
     *           
     */
    private Date otherTime;
    /**
     *         
     */
    private String wechat;
    /**
     *             
     */
    private Date wechatTime;

    public String getWechat() {
        return wechat;
    }

    public void setWechat(String wechat) {
        this.wechat = wechat;
    }

    public Date getWechatTime() {
        return wechatTime;
    }

    public void setWechatTime(Date wechatTime) {
        this.wechatTime = wechatTime;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getAndroid() {
        return android;
    }

    public void setAndroid(String android) {
        this.android = android;
    }

    public Date getAndroidTime() {
        return androidTime;
    }

    public void setAndroidTime(Date androidTime) {
        this.androidTime = androidTime;
    }

    public String getIphone() {
        return iphone;
    }

    public void setIphone(String iphone) {
        this.iphone = iphone;
    }

    public Date getIphoneTime() {
        return iphoneTime;
    }

    public void setIphoneTime(Date iphoneTime) {
        this.iphoneTime = iphoneTime;
    }

    public String getWindows() {
        return windows;
    }

    public void setWindows(String windows) {
        this.windows = windows;
    }

    public Date getWindowsTime() {
        return windowsTime;
    }

    public void setWindowsTime(Date windowsTime) {
        this.windowsTime = windowsTime;
    }

    public String getIpad() {
        return ipad;
    }

    public void setIpad(String ipad) {
        this.ipad = ipad;
    }

    public Date getIpadTime() {
        return ipadTime;
    }

    public void setIpadTime(Date ipadTime) {
        this.ipadTime = ipadTime;
    }

    public String getOther() {
        return other;
    }

    public void setOther(String other) {
        this.other = other;
    }

    public Date getOtherTime() {
        return otherTime;
    }

    public void setOtherTime(Date otherTime) {
        this.otherTime = otherTime;
    }

    @Override
    public String toString() {
        return "LoginInfo{" +
                "userId='" + userId + '\'' +
                ", android='" + android + '\'' +
                ", androidTime=" + androidTime +
                ", iphone='" + iphone + '\'' +
                ", iphoneTime=" + iphoneTime +
                ", windows='" + windows + '\'' +
                ", windowsTime=" + windowsTime +
                ", ipad='" + ipad + '\'' +
                ", ipadTime=" + ipadTime +
                ", other='" + other + '\'' +
                ", otherTime=" + otherTime +
                ", wechat='" + wechat + '\'' +
                ", wechatTime=" + wechatTime +
                '}';
    }
}

2)ユーザーログイン:
    // token    (s)
    public static final int LOGIN_TIMEOUT_SECOND = 1800;
    // Android
    public static final String DEVICE_TYPE_ANDROID = "Android";
    //   
    public static final String DEVICE_TYPE_MICRO_MESSENGER = "MicroMessenger";
    // iPhone
    public static final String DEVICE_TYPE_IPHONE = "iPhone";
    // iPad
    public static final String DEVICE_TYPE_IPAD = "iPad";
    // Android
    public static final String DEVICE_TYPE_WINDOWS = "Windows";
    // unknown
    public static final String DEVICE_TYPE_UNKNOWN = "unknown";
    // Linux
    public static final String DEVICE_TYPE_LINUX = "Linux";
    /**
     *   
     */
    public String login(String account, String password, HttpServletRequest request) {
        //       
        String devType = getDevType(request.getHeader("User-Agent"));
        User preLogin = userMapper.getUserByAccount(account);
        //    :protostuff    
        RuntimeSchema schaema = RuntimeSchema.createFrom(LoginInfo.class);
        LoginInfo loginInfo = schaema.newMessage();
        Jedis jedis = jedisPool.getResource();
        //   token
        String tokenCode =  MD5Utils.MD5(preLogin.getTel() + preLogin.getPassword());
        //   redis   
        byte[] loginInfor = jedis.get(tokenCode.getBytes());
        if(loginInfor == null || loginInfor.length == 0){
            //          userId  
            loginInfo.setUserId(preLogin.getId());
        }else{
            //          loginInfo
            ProtostuffIOUtil.mergeFrom(loginInfor, loginInfo, schaema);
        }
        //        
        switch (devType){
            case DEVICE_TYPE_ANDROID:
                loginInfo.setAndroid(request.getHeader("User-Agent"));
                loginInfo.setAndroidTime(new Date());
                break;
            case DEVICE_TYPE_IPAD:
                loginInfo.setIpad(request.getHeader("User-Agent"));
                loginInfo.setIpadTime(new Date());
                break;
            case DEVICE_TYPE_IPHONE:
                loginInfo.setIphone(request.getHeader("User-Agent"));
                loginInfo.setIphoneTime(new Date());
                break;
            case DEVICE_TYPE_LINUX:
                loginInfo.setAndroid(request.getHeader("User-Agent"));
                loginInfo.setAndroidTime(new Date());
                break;
            case DEVICE_TYPE_MICRO_MESSENGER:
                loginInfo.setWechat(request.getHeader("User-Agent"));
                loginInfo.setWechatTime(new Date());
                break;
            case DEVICE_TYPE_WINDOWS:
                loginInfo.setWindows(request.getHeader("User-Agent"));
                loginInfo.setWindowsTime(new Date());
                break;
            default:
                loginInfo.setOther(request.getHeader("User-Agent"));
                loginInfo.setOtherTime(new Date());
        }
        try {
            //      
            byte[]  bytes = ProtostuffIOUtil.toByteArray(loginInfo, schaema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
            //   redis
            jedis.del(tokenCode.getBytes());
            jedis.set(tokenCode.getBytes(), bytes, "NX".getBytes(), "EX".getBytes(), UserBll.LOGIN_TIMEOUT_SECOND);
        }finally {
            jedis.close();
        }
        return tokenCode;
    }

    /**
     *    userAgent      
     */
    public static String getDevType(String userAgent){
        String dev_type = "";
        if (userAgent.contains(DEVICE_TYPE_MICRO_MESSENGER)) {
            dev_type = DEVICE_TYPE_MICRO_MESSENGER;
        }else{
            if (userAgent.contains(DEVICE_TYPE_ANDROID) || userAgent.contains(DEVICE_TYPE_LINUX)) {
                dev_type = DEVICE_TYPE_ANDROID;
            }else if (userAgent.contains(DEVICE_TYPE_IPHONE)) {
                dev_type = DEVICE_TYPE_IPHONE;
            }else if (userAgent.contains(DEVICE_TYPE_IPAD)) {
                dev_type = DEVICE_TYPE_IPAD;
            }else if(userAgent.contains(DEVICE_TYPE_WINDOWS)){
                dev_type = DEVICE_TYPE_WINDOWS;
            }else{
                dev_type = DEVICE_TYPE_UNKNOWN;
            }
        }
        return dev_type;
    }

3)遮断鑑権:
import com.alibaba.dubbo.common.utils.StringUtils;
import com.alibaba.fastjson.JSON;
import com.chinacarbon.api.model.ReturnModel;
import com.chinacarbon.bll.UserService;
import com.chinacarbon.utils.DateUtil;
import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.runtime.RuntimeSchema;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.Date;

/**
 * 2019-07-04
 * redis    
 * @author wxm
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {

    private Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);

    @Autowired
    private JedisPool jedisPool;

    @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 {

    }

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws Exception {
        //   token
        String token = request.getParameter("token");
        //       
        if(StringUtils.isEmpty(token)){
            response.sendRedirect("/user/login");
            return false;
        }else {
            //     
            Jedis jedis = jedisPool.getResource();
            try {
                String devType = UserService.getDevType(request.getHeader("User-Agent"));
                byte[] loginInfor = jedis.get(token.getBytes());
                if(loginInfor == null || loginInfor.length == 0){
                    //     
                    response.setCharacterEncoding("utf-8");
                    response.setContentType("application/json; charset=utf-8");
                    PrintWriter writer = response.getWriter();
                    writer.write(JSON.toJSONString(new ReturnModel(-1011, null,"     !")));
                    return false;
                }else{
                    //    :protostuff    
                    RuntimeSchema schaema = RuntimeSchema.createFrom(LoginInfo.class);
                    LoginInfo loginInfo = schaema.newMessage();
                    //          loginMap
                    ProtostuffIOUtil.mergeFrom(loginInfor, loginInfo, schaema);
                    //              
                    switch (devType){
                        case UserService.DEVICE_TYPE_ANDROID:
                            if(loginInfo.getAndroid() != null){
                                Long secound = DateUtil.SecondsBetween(loginInfo.getAndroidTime(),new Date());
                                if(secound < UserService.LOGIN_TIMEOUT_SECOND) {
                                    loginInfo.setAndroidTime(new Date());
                                    byte[]  bytes = ProtostuffIOUtil.toByteArray(loginInfo, schaema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
                                    jedis.set(token.getBytes(), bytes, "XX".getBytes(), "EX".getBytes(), UserService.LOGIN_TIMEOUT_SECOND);
                                    return true;
                                }
                            }
                            break;
                        case UserService.DEVICE_TYPE_IPAD:
                            if(loginInfo.getIpad() != null){
                                Long secound = DateUtil.SecondsBetween(loginInfo.getIpadTime(),new Date());
                                if(secound < UserService.LOGIN_TIMEOUT_SECOND) {
                                    loginInfo.setIpadTime(new Date());
                                    byte[]  bytes = ProtostuffIOUtil.toByteArray(loginInfo, schaema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
                                    jedis.set(token.getBytes(), bytes, "XX".getBytes(), "EX".getBytes(), UserService.LOGIN_TIMEOUT_SECOND);
                                    return true;
                                }
                            }
                            break;
                        case UserService.DEVICE_TYPE_IPHONE:
                            if(loginInfo.getIphone() != null){
                                Long secound = DateUtil.SecondsBetween(loginInfo.getIphoneTime(),new Date());
                                if(secound < UserService.LOGIN_TIMEOUT_SECOND) {
                                    loginInfo.setIphoneTime(new Date());
                                    byte[]  bytes = ProtostuffIOUtil.toByteArray(loginInfo, schaema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
                                    jedis.set(token.getBytes(), bytes, "XX".getBytes(), "EX".getBytes(), UserService.LOGIN_TIMEOUT_SECOND);
                                    return true;
                                }
                            }
                            break;
                        case UserService.DEVICE_TYPE_LINUX:
                            if(loginInfo.getAndroid() != null){
                                Long secound = DateUtil.SecondsBetween(loginInfo.getAndroidTime(),new Date());
                                if(secound < UserService.LOGIN_TIMEOUT_SECOND) {
                                    loginInfo.setAndroidTime(new Date());
                                    byte[]  bytes = ProtostuffIOUtil.toByteArray(loginInfo, schaema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
                                    jedis.set(token.getBytes(), bytes, "XX".getBytes(), "EX".getBytes(), UserService.LOGIN_TIMEOUT_SECOND);
                                    return true;
                                }
                            }
                            break;
                        case UserService.DEVICE_TYPE_MICRO_MESSENGER:
                            if(loginInfo.getWechat() != null){
                                Long secound = DateUtil.SecondsBetween(loginInfo.getWechatTime(),new Date());
                                if(secound < UserService.LOGIN_TIMEOUT_SECOND) {
                                    loginInfo.setWechatTime(new Date());
                                    byte[]  bytes = ProtostuffIOUtil.toByteArray(loginInfo, schaema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
                                    jedis.set(token.getBytes(), bytes, "XX".getBytes(), "EX".getBytes(), UserService.LOGIN_TIMEOUT_SECOND);
                                    return true;
                                }
                            }
                            break;
                        case UserService.DEVICE_TYPE_WINDOWS:
                            if(loginInfo.getWindows() != null){
                                Long secound = DateUtil.SecondsBetween(loginInfo.getWindowsTime(),new Date());
                                if(secound < UserService.LOGIN_TIMEOUT_SECOND) {
                                    loginInfo.setWindowsTime(new Date());
                                    byte[]  bytes = ProtostuffIOUtil.toByteArray(loginInfo, schaema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
                                    jedis.set(token.getBytes(), bytes, "XX".getBytes(), "EX".getBytes(), UserService.LOGIN_TIMEOUT_SECOND);
                                    return true;
                                }
                            }
                            break;
                        case UserService.DEVICE_TYPE_UNKNOWN:
                            if(loginInfo.getOther() != null){
                                Long secound = DateUtil.SecondsBetween(loginInfo.getOtherTime(),new Date());
                                if(secound < UserService.LOGIN_TIMEOUT_SECOND) {
                                    loginInfo.setOtherTime(new Date());
                                    byte[]  bytes = ProtostuffIOUtil.toByteArray(loginInfo, schaema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
                                    jedis.set(token.getBytes(), bytes, "XX".getBytes(), "EX".getBytes(), UserService.LOGIN_TIMEOUT_SECOND);
                                    return true;
                                }
                            }
                            break;
                        default:
                            response.setCharacterEncoding("utf-8");
                            response.setContentType("application/json; charset=utf-8");
                            PrintWriter writer = response.getWriter();
                            writer.write(JSON.toJSONString(new ReturnModel(-1011, null,"    !")));
                            return false;
                    }
                }
            }finally {
                jedis.close();
            }
        }
        //     
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json; charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write(JSON.toJSONString(new ReturnModel(-1011, null,"     !")));
        return false;
    }
}

4)ログオンを終了する
/**
     *     
     */
    public void loginOut(String token,HttpServletRequest request) {
        // 1.        
        Jedis jedis = jedisPool.getResource();
        byte[] loginInfor = jedis.get(token.getBytes());
        try {
            if (loginInfor == null || loginInfor.length == 0) {
                logger.warn("    token       !    :" + request.getHeader("User-Agent"));
            } else {
                //    :protostuff    
                RuntimeSchema schaema = RuntimeSchema.createFrom(LoginInfo.class);
                LoginInfo loginInfo = schaema.newMessage();
                //          loginInfo
                ProtostuffIOUtil.mergeFrom(loginInfor, loginInfo, schaema);
                //       
                String devType = getDevType(request.getHeader("User-Agent"));
                //     
                switch (devType) {
                    case DEVICE_TYPE_ANDROID:
                        loginInfo.setAndroid(null);
                        loginInfo.setAndroidTime(null);
                        break;
                    case DEVICE_TYPE_IPAD:
                        loginInfo.setIpad(null);
                        loginInfo.setIpadTime(null);
                        break;
                    case DEVICE_TYPE_IPHONE:
                        loginInfo.setIphone(null);
                        loginInfo.setIphoneTime(null);
                        break;
                    case DEVICE_TYPE_LINUX:
                        loginInfo.setAndroid(null);
                        loginInfo.setAndroidTime(null);
                        break;
                    case DEVICE_TYPE_MICRO_MESSENGER:
                        loginInfo.setWechat(null);
                        loginInfo.setWechatTime(null);
                        break;
                    case DEVICE_TYPE_WINDOWS:
                        loginInfo.setWindows(null);
                        loginInfo.setWindowsTime(null);
                        break;
                    case DEVICE_TYPE_UNKNOWN:
                        loginInfo.setOther(null);
                        loginInfo.setOtherTime(null);
                        break;
                    default:
                        logger.warn("           token          !    :"
                                + request.getHeader("User-Agent"));
                }
                if (loginInfoIsNull(loginInfo)) {
                    //             token
                    jedis.del(token.getBytes());
                } else {
                    //             token
                    //      
                    byte[] bytes = ProtostuffIOUtil.toByteArray(loginInfo, schaema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
                    //   redis
                    jedis.del(token.getBytes());
                    jedis.set(token.getBytes(), bytes, "NX".getBytes(), "EX".getBytes(), LOGIN_TIMEOUT_SECOND);
                }
            }
        }finally {
            jedis.close();
        }
    }

    private boolean loginInfoIsNull(LoginInfo loginInfo) {
        if(loginInfo.getAndroid() != null) {
            return false;
        }
        if(loginInfo.getIpad() != null) {
            return false;
        }
        if(loginInfo.getIphone() != null) {
            return false;
        }
        if(loginInfo.getOther() != null) {
            return false;
        }
        if(loginInfo.getWindows() != null) {
            return false;
        }
        if(loginInfo.getWechat() != null) {
            return false;
        }
        return true;
    }

6.使用するjarパッケージ:
1)protostuffシーケンス化および逆シーケンス化ツールは、jdkが持っているシーケンス化ツールよりもパフォーマンスとメモリの消費量が優れています.   com.dyuproject.protostuff    protostuff-core    1.0.8    com.dyuproject.protostuff    protostuff-runtime    1.0.8