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数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
26天前
|
JSON 安全 算法
|
9天前
|
NoSQL Java API
springboot项目Redis统计在线用户
通过本文的介绍,您可以在Spring Boot项目中使用Redis实现在线用户统计。通过合理配置Redis和实现用户登录、注销及统计逻辑,您可以高效地管理在线用户。希望本文的详细解释和代码示例能帮助您在实际项目中成功应用这一技术。
19 3
|
11天前
|
消息中间件 NoSQL Java
Spring Boot整合Redis
通过Spring Boot整合Redis,可以显著提升应用的性能和响应速度。在本文中,我们详细介绍了如何配置和使用Redis,包括基本的CRUD操作和具有过期时间的值设置方法。希望本文能帮助你在实际项目中高效地整合和使用Redis。
27 1
|
26天前
|
存储 安全 Java
|
30天前
|
缓存 NoSQL Java
Spring Boot与Redis:整合与实战
【10月更文挑战第15天】本文介绍了如何在Spring Boot项目中整合Redis,通过一个电商商品推荐系统的案例,详细展示了从添加依赖、配置连接信息到创建配置类的具体步骤。实战部分演示了如何利用Redis缓存提高系统响应速度,减少数据库访问压力,从而提升用户体验。
70 2
|
1月前
|
JSON NoSQL Java
springBoot:jwt&redis&文件操作&常见请求错误代码&参数注解 (九)
该文档涵盖JWT(JSON Web Token)的组成、依赖、工具类创建及拦截器配置,并介绍了Redis的依赖配置与文件操作相关功能,包括文件上传、下载、删除及批量删除的方法。同时,文档还列举了常见的HTTP请求错误代码及其含义,并详细解释了@RequestParam与@PathVariable等参数注解的区别与用法。
|
1月前
|
NoSQL Java Redis
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
这篇文章介绍了如何使用Spring Boot整合Apache Shiro框架进行后端开发,包括认证和授权流程,并使用Redis存储Token以及MD5加密用户密码。
30 0
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
|
16天前
|
JavaScript NoSQL Java
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
30 0
|
1月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
74 6
|
8天前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题