教你使用springSecurity+jwt实现互踢功能

jwt介绍:

        JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。JWT作为一个开放的标准( RFC 7519 ),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。

jwt认证流程介绍:

1. 用户使用账号和面发出post请求; 

2. 服务器使用私钥创建一个jwt; 

3. 服务器返回这个jwt给浏览器; 

4. 浏览器将该jwt串在请求头中像服务器发送请求; 

5. 服务器验证该jwt; 

6. 返回响应的资源给浏览器。

一.思路:

原来的实现用户登录态是:
1.后台登陆成功后生成一个令牌(uuid)----JwtAuthenticationSuccessHandler
2.后台把它包装成jwt数据,然后返回给前端—JwtAuthenticationSuccessHandler
3.后台把它加入redis缓存中,并设置失效时间----JwtAuthenticationSuccessHandler
4.前端调用接口时,带入jwt
5.后台写个拦截器或者过滤器,在前端调用接口的时候,从request的header中获取jwt,在缓存中搜索,如果存在则处于登录态,并重置失效时间(这样用户在有效时间内就处于登录态)—JwtSecurityContextRepository
6.解释下:springSecurity是个过滤器琏,是由一个一个的过滤器组成的

现在的互踢:
1.后台在登陆成功后,用用户id组成一个key,查询redis缓存中的value
2.和新的jwt比较,如果不一样则把查到的jwt当做key从redis中删掉,就是上面第三步存储的
3.把用户id组成一个key,把上面jwt当做value传入缓存中
4.在上面的第5步,也重置下我们这里存储的值

*上面 指的是原来的实现用户登录态

package com.lc.gansu.security.component.jwt;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.lc.gansu.framework.core.ConstantOfReturnCode;
import com.lc.gansu.framework.core.RedisKey;
import com.lc.gansu.framework.core.ReturnObject;
import com.lc.gansu.security.component.SecurityConstants;
import com.lc.gansu.security.domain.User;
import com.lc.gansu.security.utility.JWTHS256;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Slf4j
public class JwtAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    //private final RequestCache requestCache = new HttpSessionRequestCache();
    private final ObjectMapper jacksonObjectMapper;
    private final RedisTemplate<String, Object> redisTemplate;

    public JwtAuthenticationSuccessHandler(ObjectMapper jacksonObjectMapper, RedisTemplate<String, Object> redisTemplate) {
        this.jacksonObjectMapper = jacksonObjectMapper;
        this.redisTemplate = redisTemplate;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        log.info("JwtAuthenticationSuccessHandler=success");
        clearAuthenticationAttributes(request);
        handle(response, authentication);
    }

    protected final void clearAuthenticationAttributes(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null) return;
        session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
    }

    protected void handle(HttpServletResponse response, Authentication authentication) throws IOException {
        if (response.isCommitted()) {
            log.debug("Response has already been committed.");
            return;
        }
        User sysUser = (User) authentication.getPrincipal();
        sysUser.setClazz(authentication.getClass());
        //AuthenticationAdapter authenticationAdapter=AuthenticationAdapter.authentication2AuthenticationAdapter(authentication);
        String authOfjson = jacksonObjectMapper.writeValueAsString(sysUser);
        String subject = UUID.randomUUID().toString();
        String authOfjwt = JWTHS256.buildJWT(subject, authOfjson);
        response.addHeader("jwt", authOfjwt);
        //跨域时允许header携带jwt
        response.addHeader("Access-Control-Expose-Headers" ,"jwt");
        redisTemplate.boundValueOps(SecurityConstants.getJwtKey(subject)).set("w", 60, TimeUnit.MINUTES);
        //---------互踢start-------------
        // 在缓存中传入key="R_V_USERIDLOGINKEY_" + userId + "_LOGIN",value=SecurityConstants.getJwtKey(subject),有新登录时如果用户一样则把缓存里之前的jwt删除,这个:(redisTemplate.boundValueOps(SecurityConstants.getJwtKey(subject)).set("w", 60, TimeUnit.MINUTES);)
        log.info("设置jwt,并在缓存中传入:{}",SecurityConstants.getJwtKey(subject));
        String uuid = (String) redisTemplate.opsForValue().get(RedisKey.getUserIdKey(sysUser.getId()));
        log.info("查询原有jwt:{}",uuid);
        if(!SecurityConstants.getJwtKey(subject).equals(uuid)&& Objects.nonNull(uuid)){
            assert uuid != null;
            redisTemplate.delete(uuid);
            log.info("删除原有jwt:{}",uuid);
        }
        redisTemplate.opsForValue().set(RedisKey.getUserIdKey(sysUser.getId()), SecurityConstants.getJwtKey(subject),60, TimeUnit.MINUTES);
        log.info("在缓存里塞入新jwt:{}",SecurityConstants.getJwtKey(subject));
        //---------互踢end----------------------
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();

        User returnSysUser = new User();
        returnSysUser
                .setName(sysUser.getName())
                .setCurrentOrg(sysUser.getCurrentOrg())
                .setOrgIdMapRoleList(sysUser.getOrgIdMapRoleList())
                .setCurrentMenuList(sysUser.getCurrentMenuList())
                .setOrgList(sysUser.getOrgList());

        out.write(jacksonObjectMapper.writeValueAsString(new ReturnObject<>(this.getClass().getName(), ConstantOfReturnCode.GLOBAL_RESULT_SUCESS, "登录成功", returnSysUser)));
        out.flush();
        out.close();
    }
}
package com.lc.gansu.security.component.jwt;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lc.gansu.framework.core.RedisKey;
import com.lc.gansu.security.component.SecurityConstants;
import com.lc.gansu.security.domain.User;
import com.lc.gansu.security.utility.JWTHS256;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.SecurityContextRepository;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Slf4j
public class JwtSecurityContextRepository implements SecurityContextRepository {
    protected final Log logger = LogFactory.getLog(this.getClass());

    private final RedisTemplate<String, Object> redisTemplate;
    private final ObjectMapper jacksonObjectMapper;

    public JwtSecurityContextRepository(RedisTemplate<String, Object> redisTemplate, ObjectMapper jacksonObjectMapper) {
        this.redisTemplate = redisTemplate;
        this.jacksonObjectMapper = jacksonObjectMapper;
    }

    @Override
    public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
        HttpServletRequest request = requestResponseHolder.getRequest();
        return readSecurityContextFromJWT(request);
    }

    @Override
    public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
    }

    @Override
    public boolean containsContext(HttpServletRequest request) {
        return false;
    }

    private SecurityContext readSecurityContextFromJWT(HttpServletRequest request) {
        SecurityContext context = generateNewContext();
        String authenticationOfjwt = request.getHeader("jwt");
        if (StringUtils.isNotBlank(authenticationOfjwt)) {
            try {
                Map<String, Object> map = JWTHS256.vaildToken(authenticationOfjwt);
                if (Objects.nonNull(map) && map.size() == 2) {
                    String subject = (String) map.get("subject");
                    Boolean isExp = redisTemplate.hasKey(SecurityConstants.getJwtKey(subject));
                    if (Objects.nonNull(isExp) && isExp) {//redis key 未过期
                        redisTemplate.expire(SecurityConstants.getJwtKey(subject), 60, TimeUnit.MINUTES);//延期
                        String obj = (String) map.get("claim");
                        //AuthenticationAdapter authenticationAdapter=jacksonObjectMapper.readValue(obj, new TypeReference<>(){});
                        //Authentication authentication=AuthenticationAdapter.authenticationAdapter2Authentication(authenticationAdapter);
                        //Authentication authentication=jacksonObjectMapper.readValue(obj, new TypeReference<>(){});
                        //Authentication authentication=jacksonObjectMapper.readValue(obj,Authentication.class);
                        User sysUser = jacksonObjectMapper.readValue(obj, new TypeReference<User>() {
                        });
                        Authentication authentication = new UsernamePasswordAuthenticationToken(sysUser, null, sysUser.getAuthorities());
                        context.setAuthentication(authentication);
                        //-----互踢start-------
                        if(Objects.nonNull(RedisKey.getUserIdKey(sysUser.getId()))) redisTemplate.expire(RedisKey.getUserIdKey(sysUser.getId()), 60, TimeUnit.MINUTES);
                        //-----互踢end---------
                        //if(obj instanceof Authentication){
                        //context.setAuthentication((Authentication)obj);
                        //}else log.error("jwt包含authentication的数据非法");
                    } else log.error("jwt数据过期");
                } else log.error("jwt数据非法");
            } catch (Exception e) {
                e.printStackTrace();
                logger.error(e.getLocalizedMessage());
            }
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("No JWT was available from the HttpServletRequestHeader!");
            }
        }
        return context;
    }

    protected SecurityContext generateNewContext() {
        return SecurityContextHolder.createEmptyContext();
    }
}
package com.lc.gansu.framework.core;

/**
 * TODO
 *
 * @author songtianxiong
 * @version 1.0
 * @date 2021/11/16 19:15
 */
public class RedisKey {
    //线上投放计划反馈结果催办
    public static String getOlpmIdAndUserIdRedisKey(Long OlpmId, Long userId) {
        return "R_V_OLPMURGE_" + OlpmId + "_" + userId;
    }

    //催办
    public static String getOdpIdAndUserIdRedisKey(Long OdpId, Long userId) {
        return "R_V_ODPMURGE_" + OdpId + "_" + userId;
    }

    //催办
    public static String getJwtAndUrl(String jwt, String url) {
        return "R_V_REPEAT_" + jwt + "_" + url;
    }

    //用户登录互踢
    public static String getUserIdKey(Long userId) {
        return "R_V_USERIDLOGINKEY_" + userId + "_LOGIN";
    }
}

---------------------------关键词:互踢

关键代码:

        //---------互踢start-------------
        // 在缓存中传入key="R_V_USERIDLOGINKEY_" + userId + "_LOGIN",value=SecurityConstants.getJwtKey(subject),有新登录时如果用户一样则把缓存里之前的jwt删除,这个:(redisTemplate.boundValueOps(SecurityConstants.getJwtKey(subject)).set("w", 60, TimeUnit.MINUTES);)
        log.info("设置jwt,并在缓存中传入:{}",SecurityConstants.getJwtKey(subject));
        String uuid = (String) redisTemplate.opsForValue().get(RedisKey.getUserIdKey(sysUser.getId()));
        log.info("查询原有jwt:{}",uuid);
        if(!SecurityConstants.getJwtKey(subject).equals(uuid)&& Objects.nonNull(uuid)){
            assert uuid != null;
            redisTemplate.delete(uuid);
            log.info("删除原有jwt:{}",uuid);
        }
        redisTemplate.opsForValue().set(RedisKey.getUserIdKey(sysUser.getId()), SecurityConstants.getJwtKey(subject),60, TimeUnit.MINUTES);
        log.info("在缓存里塞入新jwt:{}",SecurityConstants.getJwtKey(subject));
        //---------互踢end----------------------
                        //-----互踢start-------
                        if(Objects.nonNull(RedisKey.getUserIdKey(sysUser.getId()))) redisTemplate.expire(RedisKey.getUserIdKey(sysUser.getId()), 60, TimeUnit.MINUTES);
                        //-----互踢end---------
    //用户登录互踢
    public static String getUserIdKey(Long userId) {
        return "R_V_USERIDLOGINKEY_" + userId + "_LOGIN";
    }

到此这篇关于springSecurity+jwt中实现互踢功能的文章就介绍到这了,更多相关springSecurity+jwt互踢内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2021-11-24

springboot+jwt+springSecurity微信小程序授权登录问题

场景重现:1.微信小程序向后台发送请求 --而后台web采用的springSecuriry没有token生成,就会拦截请求,,所以小编记录下这个问题 微信小程序授权登录问题 思路 参考网上一大堆资料 核心关键字: 自定义授权+鉴权 (说的通俗就是解决办法就是改造springSecurity的过滤器) 参考文章 https://www.jb51.net/article/204704.htm 总的来说的 通过自定义的WxAppletAuthenticationFilter替换默认的UsernameP

解析SpringSecurity+JWT认证流程实现

纸上得来终觉浅,觉知此事要躬行. 楔子 本文适合:对Spring Security有一点了解或者跑过简单demo但是对整体运行流程不明白的同学,对SpringSecurity有兴趣的也可以当作你们的入门教程,示例代码中也有很多注释. 本文代码:码云地址  GitHub地址 大家在做系统的时候,一般做的第一个模块就是认证与授权模块,因为这是一个系统的入口,也是一个系统最重要最基础的一环,在认证与授权服务设计搭建好了之后,剩下的模块才得以安全访问. 市面上一般做认证授权的框架就是shiro和Spri

SpringSecurity构建基于JWT的登录认证实现

最近项目的登录验证部分,采用了 JWT 验证的方式.并且既然采用了 Spring Boot 框架,验证和权限管理这部分,就自然用了 Spring Security.这里记录一下具体实现. 在项目采用 JWT 方案前,有必要先了解它的特性和适用场景,毕竟软件工程里,没有银弹.只有合适的场景,没有万精油的方案. 一言以蔽之,JWT 可以携带非敏感信息,并具有不可篡改性.可以通过验证是否被篡改,以及读取信息内容,完成网络认证的三个问题:"你是谁"."你有哪些权限".&qu

SpringSecurity整合springBoot、redis实现登录互踢功能

背景 基于我的文章--<SpringSecurity整合springBoot.redis token动态url权限校验>.要实现的功能是要实现一个用户不可以同时在两台设备上登录,有两种思路: (1)后来的登录自动踢掉前面的登录. (2)如果用户已经登录,则不允许后来者登录. 需要特别说明的是,项目的基础是已经是redis维护的session. 配置redisHttpSession 设置spring session由redis 管理. 2.1去掉yml中的http session 配置,yml和

SpringSecurity Jwt Token 自动刷新的实现

功能需求 最近项目中有这么一个功能,用户登录系统后,需要给 用户 颁发一个 token ,后续访问系统的请求都需要带上这个 token ,如果请求没有带上这个 token 或者 token 过期了,那么禁止访问系统.如果用户一直访问系统,那么还需要自动延长 token 的过期时间. 功能分析 1.token 的生成 使用现在比较流行的 jwt 来生成. 2.token 的自动延长 要实现 token 的自动延长,系统给用户 颁发 一个 token 无法实现,那么通过变通一个,给用户生成 2个 t

SpringSecurity整合jwt权限认证的全流程讲解

JWT 本文代码截取自实际项目. jwt(Json Web Token),一个token,令牌. 简单流程: 用户登录成功后,后端返回一个token,也就是颁发给用户一个凭证.之后每一次访问,前端都需要携带这个token,后端通过token来解析出当前访问对象. 优点 1.一定程度上解放了后端,后端不需要再记录当前用户是谁,不需要再维护一个session,节省了开销. 2.session依赖于cookie,某些场合cookie是用不了的,比如用户浏览器cookie被禁用.移动端无法存储cooki

SpringSecurity+JWT实现前后端分离的使用详解

创建一个配置类 SecurityConfig 继承 WebSecurityConfigurerAdapter package top.ryzeyang.demo.common.config; import org.springframework.context.annotation.Bean; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework

CI框架实现框架前后端分离的方法详解

本文实例分析了CI框架实现框架前后端分离的方法.分享给大家供大家参考,具体如下: 我们在使用CI框架开发中(或者使用所有的其他框架开发中),总是习惯于将所开发项目的前端和后台分离开来,将各自的控制器.模型或视图放到不同的文件夹中.在这一点上ThinkPHP框架要比CI框架更要方便一些.这篇博文我们就讲述如何实现根据CI框架开发将前后端实现分离.(本文以本人开发的Pkadmin后台管理系统为例,Pkadmin后台管理系统是基于Codeigniter_v3.1.0框架和Olive Admin后台模版

Spring boot整合shiro+jwt实现前后端分离

本文实例为大家分享了Spring boot整合shiro+jwt实现前后端分离的具体代码,供大家参考,具体内容如下 这里内容很少很多都为贴的代码,具体内容我经过了看源码和帖子加了注释.帖子就没用太多的内容 先下载shiro和jwt的jar包 <!-- shiro包 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId

SpringBoot+Vue前后端分离,使用SpringSecurity完美处理权限问题的解决方法

当前后端分离时,权限问题的处理也和我们传统的处理方式有一点差异.笔者前几天刚好在负责一个项目的权限管理模块,现在权限管理模块已经做完了,我想通过5-6篇文章,来介绍一下项目中遇到的问题以及我的解决方案,希望这个系列能够给小伙伴一些帮助.本系列文章并不是手把手的教程,主要介绍了核心思路并讲解了核心代码,完整的代码小伙伴们可以在GitHub上star并clone下来研究.另外,原本计划把项目跑起来放到网上供小伙伴们查看,但是之前买服务器为了省钱,内存只有512M,两个应用跑不起来(已经有一个V部落开

nginx+vue.js实现前后端分离的示例代码

1.nginx 是一个高性能的HTTP和反向代理服务器,常用于分布式服务器管理. 它常用于做负载均衡(通过调用多台服务器达到此目的) 静态资源输出更快,可以对资源实现gzip压缩后输出(这也是本文为什么用它做静态资源访问的一个重要原因) 适合解决跨域问题和反向代理(因为谁也不想看到在本域名下看到访问其他域名的情况发生,跨域可导致csrf攻击,这是本文用它的第二个原因) 占用内存少,秒启,能快速切换结点,防止宕机 2.es6 是ECMAScript的第六个版本,如果想要学好vue.js等js框架,

Spring Boot + Vue 前后端分离项目如何踢掉已登录用户

上篇文章中,我们讲了在 Spring Security 中如何踢掉前一个登录用户,或者禁止用户二次登录,通过一个简单的案例,实现了我们想要的效果. 但是有一个不太完美的地方,就是我们的用户是配置在内存中的用户,我们没有将用户放到数据库中去.正常情况下,松哥在 Spring Security 系列中讲的其他配置,大家只需要参考Spring Security+Spring Data Jpa 强强联手,安全管理只有更简单!一文,将数据切换为数据库中的数据即可. 本文是本系列的第十三篇,阅读前面文章有助

nuxt+axios解决前后端分离SSR的示例代码

​背景:由于后端程序猿通常对CSS .JS掌握不是特别好,通常的开发模式,UI把静态html做好交给程序猿,程序猿开发,把静态html变成动态的时候经常会有各种样式错乱的问题,并且要迎合上级一天三遍样式需求,因此决定用前后端分离.考虑到网站的推广,又必须做SEO.前端框架选择VUE,解决SSR顺便选择了nuxt.js,此为背景. 一.准备工作 1.安装nodejs 2.安装vuejs 3.安装vue-cli 4.安装nuxt 二.创建nuxt项目并配置 找一个自己喜欢的目录,作为你的worksp

Django+Vue.js搭建前后端分离项目的示例

在写这篇文章的时候,顺带学习了一下关于Markdown的使用方法. 笔者是个渣渣,一切都是自己在摸索的学着,所以也谈不上什么体系.系统学习.在这里主要是为了实现把项目前后端分离开. 这里假设你的电脑上所需的django.vue.js已经有了,如果没有,往下拉就是vue.js的安装流程.django前面写过了,就不赘述了. 一,正常搭建前后端分离项目流程 1.创建django项目 命令: django-admin startproject ulb_manager 结构: ├── manage.py

vue+mockjs模拟数据实现前后端分离开发的实例代码

本文介绍了vue+mockjs模拟数据实现前后端分离开发的实例代码,分享给大家,也给自己留个笔记. 在项目中尝试了mockjs,mock数据,实现前后端分离开发. 关于mockjs,官网描述的是 1.前后端分离 2.不需要修改既有代码,就可以拦截 Ajax 请求,返回模拟的响应数据. 3.数据类型丰富 4.通过随机数据,模拟各种场景. 等等优点. 总结起来就是在后端接口没有开发完成之前,前端可以用已有的接口文档,在真实的请求上拦截ajax,并根据mockjs的mock数据的规则,模拟真实接口返回

详解前后端分离之VueJS前端

前言 前端用什么框架都可以,这里选择小巧的vuejs. 要实现的功能很简单: 1.登录功能,成功将服务器返回的token存在本地 2.使用带token的header访问服务器的一个资源 本次实验环境: "dependencies": { "vue": "^2.2.1" }, "devDependencies": { "babel-core": "^6.0.0", "babel-

详解前后端分离之Java后端

前后端分离的思想由来已久,不妨尝试一下,从上手开始,先把代码写出来再究细节. 代码下载:https://github.com/jimolonely/AuthServer 前言 以前服务端为什么能识别用户呢?对,是session,每个session都存在服务端,浏览器每次请求都带着sessionId(就是一个字符串),于是服务器根据这个sessionId就知道是哪个用户了. 那么问题来了,用户很多时,服务器压力很大,如果采用分布式存储session,又可能会出现不同步问题,那么前后端分离就很好的解