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()就可以了。

相关推荐
我命由我123456 小时前
Android Studio - Android Studio 自定义预览尺寸
android·java·ide·java-ee·android studio·android-studio·android runtime
咖啡八杯7 小时前
GoF设计模式——迭代器模式
java·后端·设计模式·迭代器模式
AIGS0017 小时前
企业AI落地的关键认知:向量空间JBoltAI的本体语义平台
java·人工智能·人工智能ai大模型应用
KaMeidebaby8 小时前
卡梅德生物技术快报|抗体亲和力成熟工业化调控新机制:差异性浆细胞增殖工艺优化思路
java·开发语言·人工智能·算法·机器学习·架构·spark
醉城夜风~10 小时前
Java详解经典算法题:接雨水(三种实现方案+原理剖析)
java·开发语言·算法
数智化转型推荐官11 小时前
2026统一身份管理系统五大发展趋势解读
java·运维·微服务
看菜鸡互11 小时前
探索用 SlideML 让大模型生成 PPT 的实验方法
java·运维
丑八怪大丑13 小时前
MyBatis
mybatis
程序员张313 小时前
SpringBoot集成BCrypt密码加密库
java·spring boot·后端
闲猫13 小时前
Spring AI Agentic 模式(第1部分):Agent Skills——模块化、可复用的能力
java·人工智能·spring