SpringBoot集成单点登录-“被挤下线” - Go语言中文社区

SpringBoot集成单点登录-“被挤下线”


SpringBoot配置单点登录

前言

你好,未来!上个周末过的还行,逛街逛到腿发软,生活还是要有仪式感,一生要待自己待你最亲近的人。周一休息,顺便看了看04版天龙八部,塑造了三位英雄人物,共同点:热血男儿,助人为乐,乔峰大侠气概,段誉风流倜傥,虚竹严于律己,久久思考金庸在写的时候,思想与灵魂是何尝不是伟大,让我也陷入了沉思,久久不能自拔。
在这里插入图片描述

中言

前言总会自己乱说一通,然后才能进入主题一些相关而有不相关的东西,都能说一些,可能一个人久了,发现就慢慢的变成了另一个人,好吧,就此打住,还是说说对我们有用的东西吧!
单点登录:顾名思义,就是只能一个人登录
思路:在登录时候传入username和lastLoginDate,生成token时,验证每次登录的时候过滤器判断最后登录时间,如果一直通行,不一致时候,直接返回error
代码实现:
登录

public UserTokenState login(String username, String password, Device device) {
        try{

            final Authentication authentication = authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                           username,password
                    )
            );
            // Inject into security context
            SecurityContextHolder.getContext().setAuthentication(authentication);

            JwtUser jwtuser = (JwtUser)authentication.getPrincipal();

            return generateUserTokenState(jwtuser.getId(),jwtuser.getUsername(),device);
        }catch (Exception e){
            logger.error("用户登录异常{}",e.getMessage());
            return null;
        }

    }

生成用户token

private UserTokenState generateUserTokenState(Long userId,String username,Device device){

        // token creation
        final String timestamp = TimeProvider.getTimestampNow().toString();
        com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
        jsonObject.put("username",username);
        jsonObject.put("lastLoginDate",timestamp);
        String jws = jwtUtil.generateToken( jsonObject, device);
        int expiresIn = jwtUtil.getExpiredIn(device);
        User user = userRepository.findByUsername(username);
        user.setLastLoginDate(timestamp);
        userRepository.save(user);

        //登出其他登录,并记录登录信息
        try{
           //后期在解释,此处处理单点登录踢出提示(类似,QQ你有新设备登录,您已经处于离线状态)
            sendLogOutMessage(userId);
        }catch (Exception e){
            e.printStackTrace();
        }
        return new UserTokenState(userId,jws,expiresIn);
    }

生成token

/**
     * 通过用户名与设备类型生成token
     * @param userinfo
     * @param device
     * @return
     */
    public String generateToken(JSONObject userinfo, Device device) {
        String audience = generateAudience(device);
        return Jwts.builder()
                .setIssuer( APP_NAME )
                .setSubject(userinfo.toString())
                .setAudience(audience)
                .setIssuedAt(timeProvider.now())
                .setExpiration(generateExpirationDate(device))
                .signWith( SIGNATURE_ALGORITHM, SECRET )
                .compact();
    }

根据设备类型进行token过期设定

/**
     * 根据设备类型进行token过期时间的设定(可以自己设定)
     * @param device
     * @return 转秒
     */
    private Date generateExpirationDate(Device device) {
        long expiresIn = device.isTablet() || device.isMobile() ? MOBILE_EXPIRES_IN : EXPIRES_IN;
        return new Date(timeProvider.now().getTime() + expiresIn * 1000);
    }

封装过滤器

@Override
    public void doFilterInternal(
            HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse,
            FilterChain chain
    ) throws IOException, ServletException {

        ServletRequest requestWrapper = null;
        HttpServletRequest request= (HttpServletRequest) httpServletRequest;
        HttpServletResponse response= (HttpServletResponse) httpServletResponse;
        //response此处自己封装自己用的header文件
        String username;
        logger.info("doFilterInternal={}",request.getHeader("Authorization"));
        try {
            //获取通用参数
            BaseRequestParam baseRequestParam = parseRequestParam(request);
            request.setAttribute("baseRequestParam",baseRequestParam);

            String authToken = jwtUtil.getToken(request);
            if (authToken != null) {
                // get username from token
                username = jwtUtil.getUsernameFromToken(authToken);
                logger.info("login username: "+username);
                if (username != null) {
                    // get user
                    UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                        if (jwtUtil.validateToken(authToken, userDetails)) {
                            // create authentication
                            TokenBasedAuthentication authentication = new TokenBasedAuthentication(userDetails);
                            authentication.setToken(authToken);
                            SecurityContextHolder.getContext().setAuthentication(authentication);
                        } else {
                            throw new ClassOnlineException(HttpServletResponse.SC_UNAUTHORIZED,"Unauthorized");
                        }
                }else {
                    throw new ClassOnlineException(HttpServletResponse.SC_UNAUTHORIZED,"Unauthorized");
                }
            }
        }catch (Exception e) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
            return;
        }

	//此处为了获取一些基础参数,为了日志或者后期实用方便写下的
        if(request instanceof HttpServletRequest) { //判断是否是http请求
            requestWrapper = new MAPIHttpServletRequestWrapper((HttpServletRequest) httpServletRequest); //再封装request
            String json = HttpHelper.getBodyString(requestWrapper);
            logger.info("传递参数解析 => {}"+json);
            //获取请求参数
            Map<String, String[]> map = requestWrapper.getParameterMap();
            logger.info("过滤器过滤的参数filter => " + httpServletRequest.getRemoteHost() + "  " + JSONObject.toJSONString(map));
        }
        if(requestWrapper == null) {
            chain.doFilter(request, response);
        } else {
            try {
                chain.doFilter(requestWrapper, response);
            } catch (ServletException e) {
                logger.error("doFilterInternal method: filter is exception" + e.getStackTrace().toString());
                chain.doFilter(request, response);
            } catch (IOException e){
                logger.error("doFilterInternal method: filter is exception" + e.getStackTrace().toString());
                chain.doFilter(request, response);
            }
        }
    }

上述基本上一个单点流程走完了,但是接下来或许你可以参考

单点登录“被挤下线”

所谓的“被挤下线”功能,即一个账号在A客户端保持登录状态,然后又在B客户端进行登录操作,那么A客户端就会被挤下线

App如何知道该账户已经在其他设备上登陆了呢?有三种实现方式

  1. api请求中后台返回特定的code。缺点是需要下次请求才知道被踢下线
  2. 使用推送。后台可以推送给APP,从而使APP得知已在其他地方登陆,可以及时响应。
  3. 使用第三方的监听器。比如集成了环信,环信自身有提供连接状态的接听,通过监听环信的用户状态,从而达到监听app自身用户系统的效果

环信的即时聊天:
Android端:
// 注册连接监听 全局的监听
EMChatManager.getInstance().addConnectionListener(connectionListener);

实现这个链接监听
连接监听,的那个检测到连接断开的时候判断是用户被移除还是连接冲突即账号在其他地方登陆,做出相应的操作。

connectionListener = new EMConnectionListener() {
@Override
public void onDisconnected(int error) {
if (error == EMError.USER_REMOVED) {
onCurrentAccountRemoved();
} else if (error == EMError.CONNECTION_CONFLICT) {
onConnectionConflict();
}
}

@Override
public void onConnected() {
// in case group and contact were already synced, we supposed to
// notify sdk we are ready to receive the events
}
};

我们只关心账号在别处登陆,这个时候,我们一般要跳转到MainActivity,然后强制弹出对话框提示用户重新登陆。

/**

  • 账号在别的设备登录
    */
    protected void onConnectionConflict() {
    Intent intent = new Intent(appContext, MainActivity.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.putExtra(Constant.ACCOUNT_CONFLICT, true);
    appContext.startActivity(intent);
    }

这个地方检测到登陆冲突之后需要回到MainActivity,并为MainActivity携带了一个标识和一个标记位Intent.FLAG_ACTIVITY_NEW_TASK,表示在一个新的task中开启一个Activity,如果包含这个Activity的task已经在运行,那么这个Activity就回到前台显示。然后回调onNewIntent()方法处理这个Intent。

@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent.getBooleanExtra(Constant.ACCOUNT_CONFLICT, false) && !isConflictDialogShow) {
showConflictDialog();
} else if (intent.getBooleanExtra(Constant.ACCOUNT_REMOVED, false)
&& !isAccountRemovedDialogShow) {
showAccountRemovedDialog();
}
}

首先会判断标识,如果是账户冲突就会弹出对话框提示用户跳转登陆页面重新登陆。另外这个对话框是不能取消也不可关闭的。
这样被挤下线功能就基本实现了。

踢出用户提示

消息踢出:实现方式,其实单点登录到用户下线都已经成型了,接下来就是优化和完善人为提示,可有可无,看你做到什么程度
思路:用阿里到消息队列发送一条push,app和ios接受到之后,做出相应到提示
实现:logout方式踢出

 /**
     * 用户被登出时发送消息
     */
    private void sendLogOutMessage(Long userId){

        //获取当前用户登录的信息
        Object loginInfoObj = cacheService.get("LOGIN_INFO" + userId);
        if(loginInfoObj != null){
            JSONObject userJson = JSONObject.fromObject(loginInfoObj);

            String pushType = StringUtils.isBlank(userJson.getString(CommonConsts.PUSH_TYPE)) ? "":userJson.getString(CommonConsts.PUSH_TYPE);
            String pushId = StringUtils.isBlank(userJson.getString(CommonConsts.PUSH_ID)) ? "":userJson.getString(CommonConsts.PUSH_ID);

            if(!StringUtils.equals("-1",pushType)){
                //将消息存储到消息队列中
                JSONObject requestBodyJson = new JSONObject();
                requestBodyJson.put(CommonConsts.PUSH_MSG_TYPE, CommonConsts.PUSH_MSG_TYPE_LOGOUT);
                requestBodyJson.put(CommonConsts.PUSH_TYPE,pushType);
                requestBodyJson.put(CommonConsts.PUSH_ID,pushId);
                aliMQServiceImpl.sendMessage(CommonConsts.MQPRODUCER_TYPE_PUSH,aliMQServiceImpl.getTopicPush(),
                        aliMQServiceImpl.getTagPush(),requestBodyJson.toString().getBytes());
            }
        }
    }

后序

用一句话结束今天的记录吧,生命不止,奋斗不息。

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/chajinglong/article/details/85066243
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢