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

相关推荐
benbenAItalk5 小时前
数字人口播视频的批量生产实践:素材规范、任务编排与质量验收
java·前端·音视频
sunshine22 girl5 小时前
Java学习二 基本语法2, 算术运算符
java·学习
niucloud-admin5 小时前
JAVA V6 多商户商城 开发文档——插件目录结构
java·开发语言
2601_962885726 小时前
如何用 Python 扫描 A 股跳空缺口并统计缺口回补概率?
java·前端·python
滕州市燕猫虎计算机科技工作室个体工商户6 小时前
Java锁
java·开发语言
shehuiyuelaiyuehao8 小时前
1计算机是如何工作的
java·计算机网络
letisgo58 小时前
JAVA 高级进阶07篇《@Transactional失效的8个场景:代理机制到传播行为》
java·面试·transactional·spring事务·aop动态代理
水域安全老周8 小时前
水趣钓鱼救生衣专利拆解:两级锁紧如何解决落水人衣分离
java·前端·网络
三8449 小时前
Java 模板注入(FreeMarker) · 02 · 模板引擎与 FreeMarker 语法
java·开发语言·web安全·freemarker
暮雨哀尘9 小时前
Java 基础练习(一):创建类并调用对象
java·开发语言