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

本文涉及的产品
云数据库 Redis 版,标准版 2GB
推荐场景:
搭建游戏排行榜
云原生内存数据库 Tair,内存型 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
目录
相关文章
|
1月前
|
缓存 NoSQL Java
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
这篇文章是关于如何在SpringBoot应用中整合Redis并处理分布式场景下的缓存问题,包括缓存穿透、缓存雪崩和缓存击穿。文章详细讨论了在分布式情况下如何添加分布式锁来解决缓存击穿问题,提供了加锁和解锁的实现过程,并展示了使用JMeter进行压力测试来验证锁机制有效性的方法。
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
|
22天前
|
编解码 NoSQL Java
使用Spring Boot + Redis 队列实现视频文件上传及FFmpeg转码的技术分享
【8月更文挑战第30天】在当前的互联网应用中,视频内容的处理与分发已成为不可或缺的一部分。对于视频平台而言,高效、稳定地处理用户上传的视频文件,并对其进行转码以适应不同设备的播放需求,是提升用户体验的关键。本文将围绕使用Spring Boot结合Redis队列技术来实现视频文件上传及FFmpeg转码的过程,分享一系列技术干货。
61 3
|
1月前
|
缓存 NoSQL Java
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解、如何添加锁解决缓存击穿问题?分布式情况下如何添加分布式锁
这篇文章介绍了如何在SpringBoot项目中整合Redis,并探讨了缓存穿透、缓存雪崩和缓存击穿的问题以及解决方法。文章还提供了解决缓存击穿问题的加锁示例代码,包括存在问题和问题解决后的版本,并指出了本地锁在分布式情况下的局限性,引出了分布式锁的概念。
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解、如何添加锁解决缓存击穿问题?分布式情况下如何添加分布式锁
|
1月前
|
NoSQL Java Redis
Redis6入门到实战------ 八、Redis与Spring Boot整合
这篇文章详细介绍了如何在Spring Boot项目中整合Redis,包括在`pom.xml`中添加依赖、配置`application.properties`文件、创建配置类以及编写测试类来验证Redis的连接和基本操作。
Redis6入门到实战------ 八、Redis与Spring Boot整合
|
1月前
|
Web App开发 前端开发 关系型数据库
基于SpringBoot+Vue+Redis+Mybatis的商城购物系统 【系统实现+系统源码+答辩PPT】
这篇文章介绍了一个基于SpringBoot+Vue+Redis+Mybatis技术栈开发的商城购物系统,包括系统功能、页面展示、前后端项目结构和核心代码,以及如何获取系统源码和答辩PPT的方法。
|
23天前
|
缓存 NoSQL Java
惊!Spring Boot遇上Redis,竟开启了一场缓存实战的革命!
【8月更文挑战第29天】在互联网时代,数据的高速读写至关重要。Spring Boot凭借简洁高效的特点广受开发者喜爱,而Redis作为高性能内存数据库,在缓存和消息队列领域表现出色。本文通过电商平台商品推荐系统的实战案例,详细介绍如何在Spring Boot项目中整合Redis,提升系统响应速度和用户体验。
44 0
|
28天前
|
缓存 NoSQL Java
【Azure Redis 缓存】定位Java Spring Boot 使用 Jedis 或 Lettuce 无法连接到 Redis的网络连通性步骤
【Azure Redis 缓存】定位Java Spring Boot 使用 Jedis 或 Lettuce 无法连接到 Redis的网络连通性步骤
|
1月前
|
NoSQL JavaScript Java
SpringBoot+Vue+Redis实现验证码功能、一个小时只允许发三次验证码。一次验证码有效期二分钟。SpringBoot整合Redis
这篇文章介绍了如何使用SpringBoot结合Vue和Redis实现验证码功能,包括验证码的有效期控制和一小时内发送次数的限制。
|
1月前
|
存储 NoSQL Java
基于SpringBoot+Redis实现查找附近用户的功能
使用Redis的GEO命令结合SpringBoot实现查找附近用户的功能,通过`GEOADD`命令添加地理位置信息和`GEORADIUS`命令查询附近用户。
50 0
|
1月前
|
存储 NoSQL Redis
基于SpringBoot+Redis实现点赞/排行榜功能,可同理实现收藏/关注功能,可拓展实现共同好友/共同关注/关注推送功能
在SpringBoot项目中使用Redis的Set和ZSet集合实现点赞和排行榜功能,并通过示例代码展示了如何使用`stringRedisTemplate`操作Redis来完成这些功能。
125 0