Spring Security_05

该文章衔接Spring Security的前4个章节。

项目应用

首先

添加redis的依赖

复制代码
<!--Spring Boot-redis的依赖包-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>

yml配置:

复制代码
  #redis配置
 spring: 
    data:
      redis:
        host: 127.0.0.1
        database: 0
        password: 123456
        port: 6379

在认证成功处理器中使用

处理redis中乱码

但是我们发现认证成功后存储到redis中的值有乱码,不方便我们查看:

这是因为存储的时候默认以jdk的Key序列化器:JdkSerializationRedisSerializer后存储的,看到是二进制数据,但是不影响我们读取。怎么处理这样的数据呢?只需要默认更改序列化的方式就可以了:

添加一个redis的配置类

复制代码
package com.sy.config;

import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;

@Configuration
public class RedisConfig{
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {

        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();

        //默认的Key序列化器为:JdkSerializationRedisSerializer
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setHashKeySerializer(RedisSerializer.string());
        //一般value就不需要序列化了

        redisTemplate.setConnectionFactory(connectionFactory);

        return redisTemplate;
    }
}

结果就变成这样了:

方便查看。

请求携带token

怎么能够保证每次请求携带这个服务器生成的token呢?这个时候我们立马就想到了axios的拦截器,该拦截器包括请求拦截器和响应拦截器,我们在请求拦截器中获取之前在浏览器上存储的token,然后加入到对应的请求头中就好了,这样就避免了,我们每次发送请求还需要手动的添加这个请求头。

过滤器获取请求头

但是又面临一个问题?我们应该怎么获取这个请求头呢?什么时候获取呢?以为以后每次请求都会发送这个请求头,如果我们写在对应的Controller中,那么每个controller的每个方法中都需要获取并验证,对吧!

这个时候,我们想到了过滤器,我们可以定义个过滤器,在这个过滤器中来获取请求头,验证这个token是否一致,同时因为spring security底层也是很多的过滤器链,定义完成后,加入到安全框架的过滤器链中就好了。

需要注意的是:去登录是不需要验证的,所以需要直接放行,我们这里继承OncePerRequestFilter,而不是我们之前学习的实现Filter因为需要类型转换(request,response)

复制代码
package com.sy.filter;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTPayload;
import cn.hutool.jwt.JWTUtil;
import com.sy.entity.TUser;
import com.sy.util.Result;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;

@Component
@Data
public class JwtTokenFilter extends OncePerRequestFilter {
    @Value("${jwt.secrt}")
    private String secrt;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //解决乱码
        request.setCharacterEncoding("utf-8");
        response.setContentType("application/json;charset=utf-8");
        //获取请求路径
        String uri = request.getRequestURI();//因为此时我们项目的上下问名称为 /,所以获取的路径为 /xx/xx
        //如果路径是登录页面的路径直接放行
        if ("/user/login".equals(uri)) {
            filterChain.doFilter(request, response);
        } else {
            //否则需要验证了
            //1.获取token
            String authorization = request.getHeader("authorization");
            String token = authorization.substring(6);
            if (!StringUtils.hasText(token)) {
                //返回给前端对应的错误信息
                response.getWriter().print(JSONUtil.toJsonStr(Result.fail("token不存在", 505)));
            } else {
                //验证token
                boolean verify = false;
                try {
                    verify = JWTUtil.verify(token, secrt.getBytes(StandardCharsets.UTF_8));
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (!verify) {
                    //验证错误,返回错误信息
                    response.getWriter().print(JSONUtil.toJsonStr(Result.fail("token不合法", 506)));

                } else {
                    //否则和redis中的token对比一下
                    //获取redis中token
                    //hash中的key我们当时存储的是用户id怎么获取呢?
                    //通过jwt解析呀
                    JSONObject payloads = JWTUtil.parseToken(token).getPayloads();
                    String tuser_json = payloads.get("tuser", String.class);
                    //将json字符串转换为对象
                    TUser tuser = JSONUtil.toBean(tuser_json, TUser.class);
                    String redis_token = (String) redisTemplate.opsForHash().get("login:user:token", String.valueOf(tuser.getId()));
                    if (!token.equals(redis_token)) {
                        response.getWriter().print(JSONUtil.toJsonStr(Result.fail("token校验错误", 507)));

                    } else {

                        //同时将认证信息添加到security上下文
                        UsernamePasswordAuthenticationToken upat =
                                new UsernamePasswordAuthenticationToken(
                                        tuser,//这里什么都包含了
                                        null, null
                                );
                        SecurityContextHolder.getContext().setAuthentication(upat);
                        //放行
                        filterChain.doFilter(request, response);

                    }
                }
            }

        }

    }
}

添加到过滤器链中

在一次认证通过后发送访问Index页面

成功了。

重启项目如何让token失效

因为JWT无状态,重启项目后,jwt并没有失效,依然可以访问后端的接口;

原因是,你重启后端springboot项目后,前端sessionStorage中token没有失效,后端redis中的token也没有失效

解决办法:

1、把jwt存入redis中并设置一个过期时间,到期后jwt自动失效;(30分钟失效)

2、实现一个退出功能,用户点击退出登录,让jwt失效;(用户如果不点击退出)

3、服务关闭/重启,删除redis的所有jwt,而不是某一个用户的;

这个时候使用监听器解决,监听项目的关闭事件就可以了

复制代码
package com.sy.listener;

import jakarta.annotation.Resource;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

@Component
public class ApplicationShutdownListener implements ApplicationListener<ContextClosedEvent> {
    @Resource
    private RedisTemplate<String,Object> redisTemplate;
    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        redisTemplate.delete("login:user:token");
    }
}

权限授予

和我们之前讲的一样,现在这里就说一个,万一没有权限怎么办呢?

我们去配置一个无权限的配置

和我们之前的成功失败的处理器是一样的

注意:

标注的地方就不能为空了,

tuser.getAuthorities()就可以了。

相关推荐
无级程序员2 小时前
Mybatis中保证时间戳的一致性
mybatis
我登哥MVP2 小时前
【Spring6笔记】 - 11 - JDBCTemplate
java·数据库·spring boot·mysql·spring
也许明天y2 小时前
Spring AI 核心原理解析:基于 1.1.4 版本拆解底层架构
java·后端·spring
小红的布丁2 小时前
BIO、NIO、AIO 与 IO 多路复用:select、poll、epoll 详解
java·数据库·nio
lifallen2 小时前
Flink Checkpoint 流程、Barrier 流动与 RocksDB 排障
java·大数据·flink
疯狂打码的少年2 小时前
【Day12 Java转Python】Python工程的“骨架”——模块、包与__name__
java·开发语言·python
希望永不加班2 小时前
SpringBoot 自定义 Starter:从零开发一个私有 Starter
java·spring boot·后端·spring·mybatis
悟空码字3 小时前
别再System.out了!这份SpringBoot日志优雅指南,让你告别日志混乱
java·spring boot·后端
一 乐3 小时前
工会管理|基于springboot + vue工会管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·工会管理系统