Jwt+Filter+SpringBoot+Redis实现Cookie自动登陆

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: Jwt+Filter+SpringBoot+Redis实现Cookie自动登陆

具体思路

FIlter拦截器拦截前端发送过来的请求,然后通过检查请求的cookie然后进行匹配检查,如果没有cookie则证明没有登录过,则需要过滤跳转到查询数据库验证用户名和密码看看是否有此人,如果有则以用户的名字(或者其他信息)通过Jwt生成Token并存入到Cookie里返回给客户端。下次登录的时候拦截器再拦截请求然后通过Jwt工具解码检查里面的cookie并与Redis里存的cookie进行匹配,如果符合了就直接跳转不再需要登录


图解

1.png

实现

controller:

此处可以根据自己的实际情况来搞,最主要的是登录成功后对cookie的处理,有些人的业务逻辑是放在service里处理的

@PostMapping("/login")
    //HttpServletRequest是为了把对应的employee的id存储在session里方便调用,@RequestBody中是网页返回来的数据
    public R<Employee> login(HttpServletRequest request, HttpServletResponse response, @RequestBody Employee employee) {
        //1、将页面提交的密码password进行md5加密处理
        String password = employee.getPassword();
        //将password转换成bytes这样就会进行md5转换
        password = DigestUtils.md5DigestAsHex(password.getBytes());
        //2、根据页面提交的用户名username查询数据库
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<Employee>();
        queryWrapper.eq(Employee::getUsername, employee.getUsername());
        //因为Username是唯一的所以可以用getOne方法去查处一个唯一的数据返回给ee
        Employee emp = employeeService.getOne(queryWrapper);
        //3、如果没有查询到则返回登录失败结果
        if (emp == null) {
            return R.error("登陆失败");
        }
        //4、密码比对,如果不一致则返回登录失败结果
        if (!emp.getPassword().equals(password)) {
            return R.error("登陆失败");
        }
        //5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
        if (emp.getStatus() == 0) {
            return R.error("账户已禁用");
        }
        //6、登录成功,返回登录成功结果
        //jwt生成token
        String token = JwtUtils.getJwtToken(emp.getUsername());
        //token存入redis,7天有效期
        redisTemplate.opsForValue().set(emp.getUsername(), token, Duration.ofDays(7));
        //token存入cookie返回给浏览器
        ResponseCookie cookie = ResponseCookie.from("token", token)
                .path("/")            // path
                .maxAge(60 * 60 * 24 * 7)    // 有效期
                //以下这两项是设置https共享cookie
                .secure(true)
                .sameSite("None")
                .build();
        // 设置Cookie Header
        response.setHeader("Set-Cookie", cookie.toString());
        log.info(cookie.toString());//打印看看secure和sameSite是否设置成功
        //将员工id存入Session
        request.getSession().setAttribute("employee", emp.getId());
        request.getSession().setAttribute("employeeName", emp.getUsername());
        request.getSession().setAttribute("phone",emp.getPhone());
        request.getSession().setAttribute("status",emp.getStatus());
        log.info("员工登陆为:{}", request.getSession().getAttribute("employee"));
        log.info("用户登陆为:{}", request.getSession().getAttribute("user"));
        return R.success(emp);
    }

service:

@Service
@Slf4j
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    EmployeeService employeeService;
    //自动登录获取用户信息,并更新token和cookie
    @Override
    public R<Employee> getUserMess(String name, HttpServletRequest request, HttpServletResponse response) {
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<Employee>();
        queryWrapper.eq(Employee::getUsername, name);
        //因为Username是唯一的所以可以用getOne方法去查处一个唯一的数据返回给ee
        Employee emp = employeeService.getOne(queryWrapper);
        //jwt生成token,包含用户姓名
        String token = JwtUtils.getJwtToken(name);
        //更新token,token存入redis,7天有效期,具体自己设置
        redisTemplate.opsForValue().set(name, token, Duration.ofDays(7));
        ResponseCookie cookie = ResponseCookie.from("token", token)
                .path("/")            // path
                .maxAge(60 * 60 * 24 * 7)    // 有效期
                //以下这两项是设置https共享cookie
                .secure(true)
                .sameSite("None")
                .build();
        // 设置Cookie Header
        response.setHeader("Set-Cookie", cookie.toString());
        log.info(cookie.toString());//打印看看secure和sameSite是否设置成功
        return R.success(emp);
    }
}

Filter:

下面的代码主要关注用Jwt解析获得出Cookie然后判断是否空然后进行业务处理即可,注意返回处我用了4种方法实现,如果使用ResponseUtil工具类则继续往下看即可。使用方法2则不需要,更简便

@WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*")//urlPatterns= "/*"是指所有请求都进过滤器
@Slf4j
public class LoginCheckFilter implements Filter {
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    EmployeeService employeeService;
    //路径通配符
    public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //整型提升
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        //1、获取本次请求的URI
        String requestURI = request.getRequestURI();
        log.info("拦截到请求:{}", requestURI);
        //不需要拦截的请求路径
        String[] urls = new String[]{
                "/employee/login",
                "/employee/logout",
                //此处不拦截backend和front下所有的页面是因为下面的页面可以被查看但是要做的就是不把数据渲染上去
                "/backend/**",
                "/front/**",
                "/common/**",
                "/user/sendMsg",
                "/user/login",
                "/doc.html",
                "/webjars/**",
                "/swagger-resources",
                "/v2/api-docs"
        };
//
//
        //2、判断本次请求是否需要处理
        boolean check = check(urls, requestURI);
        //3、如果不需要处理,则直接放行
        if (check) {
            log.info("本次请求{}不需要处理", requestURI);
            filterChain.doFilter(request, response);
            return;
        }
        Cookie cookie = JwtUtils.getToken(request);
        //如果没有cookie证明用户还未登陆过,则往下走,要用账号密码登陆
        if(cookie==null){
            filterChain.doFilter(request,response);
        }else {
            boolean isValid = JwtUtils.checkTokenByCookie(cookie,redisTemplate);
            if(isValid){
//                response.sendRedirect("index.html");
                String empname = JwtUtils.getUsername(cookie.getValue());//从token中获得用户名
                //方法1
//                R r = employeeService.getUserMess(empname,request,response); //获取用户信息
                //方法2
                Employee emp = employeeService.getUserMess(empname);//通过empname获取用户信息
                log.info("员工id为{}", request.getSession().getAttribute("employee"));
                Long empId = (Long) request.getSession().getAttribute("employee");
                BaseContext.setCurrentId(empId);
                //方法1
//                ResponseUtil.write(response,r);//使用ResponseUtil工具类返回响应
                //方法2
//                response.getWriter().write(JSON.toJSONString(R.success(r)));//使用getWriter()但报错
                //方法3
//                response.getOutputStream().write(JSON.toJSONBytes(R.success(r))); //使用getOutputStream()返回,无报错但因为返回数据不对因而无法正确显示,要测试的话要先注释掉下面4-1的员工代码
                //方法4 成功后可以生成一个url发送给login
                // Create URL object
//                URL obj = new URL("http://localhost:8080/employee/login");
//
//                // Open connection
//                HttpURLConnection con = (HttpURLConnection) obj.openConnection();
//
//                // Set request method
//                con.setRequestMethod("POST");
//
//                // Set request headers
//                con.setRequestProperty("User-Agent", "Mozilla/5.0");
//                con.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
//
//
//                // Enable output and set content type
//                con.setDoOutput(true);
//                con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
//
//                // Write data to output stream
//                OutputStreamWriter wr = new OutputStreamWriter(con.getOutputStream());
//                wr.write(String.valueOf(emp));
//                wr.flush();
//                wr.close();
                filterChain.doFilter(request, response);
                return;
            }else{
                filterChain.doFilter(request,response);
            }
        }
//        //4-1、判断员工登录状态,如果已登录,则直接放行
        if (request.getSession().getAttribute("employee") != null) {
            log.info("员工已登陆,员工id为{}", request.getSession().getAttribute("employee"));
            Long empId = (Long) request.getSession().getAttribute("employee");
            BaseContext.setCurrentId(empId);
            filterChain.doFilter(request, response);
            return;
        }
        //4-2、判断用户登录状态,如果已登录,则直接放行
        if (request.getSession().getAttribute("user") != null) {
            log.info("用户已登录,用户id为:{}", request.getSession().getAttribute("user"));
            Long userId = (Long) request.getSession().getAttribute("user");
            BaseContext.setCurrentId(userId);
            filterChain.doFilter(request, response);
            return;
        }
        log.info("用户未登陆");
        //5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
        //将NOTLOGIN以json数据流的方式返回给前端,因为前端的拦截器检测到NOTLOGIN就会进行拦截
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
        System.out.println("未登录,返回前端");
        return;
    }
    /**
     * 判断本次请求是否需要拦截,与urls里匹配上的就放行
     */
    public boolean check(String[] urls, String requestURI) {
        for (String url : urls) {
            boolean match = PATH_MATCHER.match(url, requestURI);
            if (match) {
                return true;
            }
        }
        return false;
    }
}

工具类

JwtUtils:

package com.k1.reggie.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Date;
/**
 * jwt 工具类
 */
public class JwtUtils {
    public static final long EXPIRE = 1000 * 60 * 60 * 24;
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";
    //包含用户id和用户昵称
    public static String getJwtToken(String id, String nickname){
        String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                .setSubject("fish-user")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .claim("id", id)
                .claim("nickname", nickname)
                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();
        return JwtToken;
    }
    //只包含 用户名
    public static String getJwtToken(String username){
        String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                .setSubject("fish-user")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .claim("username", username)
                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();
        return JwtToken;
    }
    /**
     * 从header中取token
     * 根据token,获取其中的用户名,根据这个用户名查询redis这个token 是否有效
     */
    public static boolean checkTokenByRequest(HttpServletRequest request, RedisTemplate redisTemplate){
        String token = request.getHeader("token");
        System.out.println("请求头中的token为:"+token);
        //如果token为空
        if(token==null || token.isEmpty()) return false;
        //根据token获取用户名
        String username = JwtUtils.getUsername(token);
        System.out.println("token中获取的用户名为:"+username);
        //判断token是否存储在redis中
        Object redisValue = redisTemplate.opsForValue().get(username);
        if(redisValue == null){
            return false;
        }
        return true;
    }
    /**
     * 从cookies中取token
     * 根据token,获取其中的用户名,根据这个用户名查询redis这个token 是否有效
     */
    public static boolean checkTokenByCookie(HttpServletRequest request, RedisTemplate redisTemplate){
        Cookie[] cookies = request.getCookies();
        String token = Arrays.stream(cookies).filter(cookie -> cookie.getName().equals( "token")).findFirst().get().getValue();
        String username = JwtUtils.getUsername(token);
        System.out.println("token中获取的用户名为:"+username);
        //判断token是否存储在redis中
        Object redisValue = redisTemplate.opsForValue().get(username);
        if(redisValue == null){
            return false;
        }
        return true;
    }
    public static boolean checkTokenByCookie(Cookie cookie, RedisTemplate redisTemplate){
        try {
            String username = JwtUtils.getUsername(cookie.getValue());
            System.out.println("token中获取的用户名为:"+username);
            //判断token是否存储在redis中
            Object redisValue = redisTemplate.opsForValue().get(username);
            if(redisValue == null){
                return false;
            }
        }catch (Exception e){
            return false;
        }
        return true;
    }
    /**
     * 判断token是否存在与有效
     * @param jwtToken
     * @return
     */
    public static boolean checkToken(String jwtToken) {
        if(StringUtils.isEmpty(jwtToken)) return false;
        try {
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
    /**
     * 判断token是否存在与有效
     * @param request
     * @return
     */
    public static boolean checkToken(HttpServletRequest request) {
        try {
            String jwtToken = request.getHeader("token");
            if(StringUtils.isEmpty(jwtToken)) return false;
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
    /**
     * 根据token获取会员id
     * @param request
     * @return
     */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("token");
        if(StringUtils.isEmpty(jwtToken))
            return "";
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String)claims.get("id");
    }
    /**
     * 根据token获取用户名
     */
    public static String getUsername(String token){
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        return (String)claims.get("username");
    }
    /**
     * 根据cookie获取用户名
     * @param request
     */
    public static String getUsernameByCookie(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie:cookies) {
            if(cookie.getName().equals("token")){
                return JwtUtils.getUsername(cookie.getValue());
            }
        }
        return null;
    }
    public static boolean checkTokenByUsername(String username,String token,RedisTemplate redisTemplate) {
        //判断token是否存储在redis中
        Object redisValue = redisTemplate.opsForValue().get(username);
        return redisValue.equals(token);
    }
    //从token中获取用户名,并根据用户名和token去redis中判断该token是否有效
    public static boolean checkTokenByToken(String token,RedisTemplate redisTemplate) {
        String username = getUsername(token);
        if(StringUtils.isEmpty(username)){
            return false;
        }else {
            return checkTokenByUsername(username,token,redisTemplate);
        }
    }
    public static Cookie getToken(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        if(cookies == null){
            return null;
        }else{
            for (Cookie cookie:cookies) {
                if(cookie.getName().equals("token")){
                    return cookie;
                }
            }
        }
        return null;
    }
}

ResponseUtil:

package com.k1.reggie.utils;
/**
 * @Author:kkoneone11
 * @name:ResponseUtil
 * @Date:2023/4/10 20:15
 */
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.k1.reggie.common.R;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
        * 用于处理响应(HttpServletResponse)的工具类
        */
//public class ResponseUtil {
//
//    public static void out(HttpServletRequest request, HttpServletResponse response, R r) {
//        ObjectMapper mapper = new ObjectMapper();
//        response.setStatus(HttpStatus.OK.value());
//        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
//        /**
//         * 响应跨域配置
//         */
//        // 响应标头指定 指定可以访问资源的URI路径
//        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
//        //响应标头指定响应访问所述资源到时允许的一种或多种方法
//        response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
//        //设置 缓存可以生存的最大秒数
//        response.setHeader("Access-Control-Max-Age", "3600");
//        //设置  受支持请求标头
//        response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
//        // 指示的请求的响应是否可以暴露于该页面。当true值返回时它可以被暴露
//        response.setHeader("Access-Control-Allow-Credentials", "true");
//        try {
//            mapper.writeValue(response.getWriter(), r);
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//    }
//}
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletResponse;
public class ResponseUtil {
    public static void write(HttpServletResponse response,Object o) throws IOException{
//        ;
            PrintWriter out = response.getWriter();
            out.write(JSON.toJSONString(R.success(o)));
            out.flush();
            out.close();
    }
}
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
相关文章
|
9月前
|
JSON 安全 Java
什么是JWT?如何使用Spring Boot Security实现它?
什么是JWT?如何使用Spring Boot Security实现它?
1681 5
|
6月前
|
NoSQL Java 关系型数据库
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Redis 介绍
本文介绍在 Spring Boot 中集成 Redis 的方法。Redis 是一种支持多种数据结构的非关系型数据库(NoSQL),具备高并发、高性能和灵活扩展的特点,适用于缓存、实时数据分析等场景。其数据以键值对形式存储,支持字符串、哈希、列表、集合等类型。通过将 Redis 与 Mysql 集群结合使用,可实现数据同步,提升系统稳定性。例如,在网站架构中优先从 Redis 获取数据,故障时回退至 Mysql,确保服务不中断。
253 0
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Redis 介绍
|
6月前
|
缓存 NoSQL Java
基于SpringBoot的Redis开发实战教程
Redis在Spring Boot中的应用非常广泛,其高性能和灵活性使其成为构建高效分布式系统的理想选择。通过深入理解本文的内容,您可以更好地利用Redis的特性,为应用程序提供高效的缓存和消息处理能力。
474 79
|
7月前
|
NoSQL Java Redis
Springboot使用Redis实现分布式锁
通过这些步骤和示例,您可以系统地了解如何在Spring Boot中使用Redis实现分布式锁,并在实际项目中应用。希望这些内容对您的学习和工作有所帮助。
639 83
|
3月前
|
机器学习/深度学习 数据采集 人机交互
springboot+redis互联网医院智能导诊系统源码,基于医疗大模型、知识图谱、人机交互方式实现
智能导诊系统基于医疗大模型、知识图谱与人机交互技术,解决患者“知症不知病”“挂错号”等问题。通过多模态交互(语音、文字、图片等)收集病情信息,结合医学知识图谱和深度推理,实现精准的科室推荐和分级诊疗引导。系统支持基于规则模板和数据模型两种开发原理:前者依赖人工设定症状-科室规则,后者通过机器学习或深度学习分析问诊数据。其特点包括快速病情收集、智能病症关联推理、最佳就医推荐、分级导流以及与院内平台联动,提升患者就诊效率和服务体验。技术架构采用 SpringBoot+Redis+MyBatis Plus+MySQL+RocketMQ,确保高效稳定运行。
224 0
|
6月前
|
存储 人工智能 NoSQL
SpringBoot整合Redis、ApacheSolr和SpringSession
本文介绍了如何使用SpringBoot整合Redis、ApacheSolr和SpringSession。SpringBoot以其便捷的配置方式受到开发者青睐,通过引入对应的starter依赖,可轻松实现功能整合。对于Redis,可通过配置RedisSentinel实现高可用;SpringSession则提供集群Session管理,支持多种存储方式如Redis;整合ApacheSolr时,借助Zookeeper搭建SolrCloud提高可用性。文中详细说明了各组件的配置步骤与代码示例,方便开发者快速上手。
105 11
|
8月前
基于springboot+thymeleaf+Redis仿知乎网站问答项目源码
基于springboot+thymeleaf+Redis仿知乎网站问答项目源码
249 36
|
6月前
|
NoSQL Java API
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Spring Boot 集成 Redis
本文介绍了在Spring Boot中集成Redis的方法,包括依赖导入、Redis配置及常用API的使用。通过导入`spring-boot-starter-data-redis`依赖和配置`application.yml`文件,可轻松实现Redis集成。文中详细讲解了StringRedisTemplate的使用,适用于字符串操作,并结合FastJSON将实体类转换为JSON存储。还展示了Redis的string、hash和list类型的操作示例。最后总结了Redis在缓存和高并发场景中的应用价值,并提供课程源代码下载链接。
1563 0
|
6月前
|
NoSQL Java Redis
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Redis 安装
本教程介绍在 VMware 虚拟机(CentOS 7)或阿里云服务器中安装 Redis 的过程,包括安装 gcc 编译环境、下载 Redis(官网或 wget)、解压安装、修改配置文件(如 bind、daemonize、requirepass 等设置)、启动 Redis 服务及测试客户端连接。通过 set 和 get 命令验证安装是否成功。适用于初学者快速上手 Redis 部署。
124 0
|
8月前
|
XML JavaScript Java
SpringBoot集成Shiro权限+Jwt认证
本文主要描述如何快速基于SpringBoot 2.5.X版本集成Shiro+JWT框架,让大家快速实现无状态登陆和接口权限认证主体框架,具体业务细节未实现,大家按照实际项目补充。
510 11