Spring Boot 整合SpringSecurity和JWT和Redis实现统一鉴权认证
作者:mmseoamin日期:2023-12-11

📑前言

本文主要讲了Spring Security文章,如果有什么需要改进的地方还请大佬指出⛺️

🎬作者简介:大家好,我是青衿🥇

☁️博客首页:CSDN主页放风讲故事

🌄每日一句:努力一点,优秀一点

在这里插入图片描述

目录

文章目录

  • 📑前言
  • **目录**
    • 一、介绍
    • 二、主要功能
    • 三、原理
      • 1. SpringSecurity 过滤器链
      • 2. JWT校验登录的校验流程
      • 四、Spring Boot整合Redis、SpringSecurity、JWT的示例demo
      • 📑文章末尾

        Spring Security

        一、介绍

        Spring Security是一个强大且高度可定制的身份验证和访问控制框架。它是保护基于Spring的应用程序的实际标准。Spring Security是一个可以为Java应用程序提供全面安全服务的框架。同时,它也可以轻松扩展以满足自定义需求。

        二、主要功能

        Authentication (认证),就是用户登录

        Authorization (授权):一旦身份验证成功,判断用户拥有什么权限,可以访问什么资源

        防止跨站请求伪造(CSRF):Spring Security提供了内置的防护机制,可以防止跨站请求伪造攻击。

        密码存储:Spring Security提供了多种密码存储格式,包括明文、加密和哈希。

        集成其他安全框架:Spring Security可以与其他安全框架如OAuth2、JWT等进行集成,以提供更全面的安全解决方案。

        三、原理

        ​ SpringSecurity的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器。

        1. SpringSecurity 过滤器链

        在这里插入图片描述

        SpringSecurity 采用的是责任链的设计模式,它有一条很长的过滤器链。

        • SecurityContextPersistenceFilter:每次请求处理之前将该请求相关的安全上下文信息加载到 SecurityContextHolder 中。
        • LogoutFilter:用于处理退出登录。
        • UsernamePasswordAuthenticationFilter:用于处理基于表单的登录请求,从表单中获取用户名和密码。
        • BasicAuthenticationFilter:检测和处理 http basic 认证。
        • ExceptionTranslationFilter:处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException 。
        • FilterSecurityInterceptor:负责权限校验的过滤器,可以看做过滤器链的出口。
        • 流程说明:客户端发起一个请求,进入 Security 过滤器链。

          1.当到 LogoutFilter 的时候判断是否是登出路径,如果是登出路径则到 logoutHandler ,如果登出成功则到logoutSuccessHandler 登出成功处理,如果登出失败则由 ExceptionTranslationFilter ;如果不是登出路径则直接进入下一个过滤器。

          2.当到 UsernamePasswordAuthenticationFilter 的时候判断是否为登录路径,如果是,则进入该过滤器进行登录操作,如果登录失败则到 AuthenticationFailureHandler 登录失败处理器处理,如果登录成功则到 AuthenticationSuccessHandler 登录成功处理器处理,如果不是登录请求则不进入该过滤器。

          3.当到 FilterSecurityInterceptor 的时候会拿到 uri ,根据 uri 去找对应的鉴权管理器,鉴权管理器做鉴权工作,鉴权成功则到 Controller 层否则到 AccessDeniedHandler 鉴权失败处理器处理。

          2. JWT校验登录的校验流程

          在这里插入图片描述

          首先前端一样是把登录信息发送给后端,后端查询数据库校验用户的账号和密码是否正确,正确的话则使用jwt生成token,并且返回给前端。以后前端每次请求时,都需要携带token,后端获取token后,使用jwt进行验证用户的token是否无效或过期,验证成功后才去做相应的逻辑。

          四、Spring Boot整合Redis、SpringSecurity、JWT的示例demo

          1. 添加依赖项在 pom.xml 文件中添加以下依赖项:
             
              
                  org.springframework.boot
                  spring-boot-starter-data-redis
              
              
                  org.springframework.boot
                  spring-boot-starter-security
              
              
                  io.jsonwebtoken
                  jjwt
                  0.9.1
              
          `
          
          1. 创建Redis配置类
          @Configuration
          public class RedisConfig {
              @Value("${spring.redis.host}")
              private String host;
              @Value("${spring.redis.port}")
              private int port;
              @Bean
              public JedisConnectionFactory jedisConnectionFactory() {
                  return new JedisConnectionFactory(new RedisStandaloneConfiguration(host, port));
              }
              @Bean
              public RedisTemplate redisTemplate() {
                  final RedisTemplate template = new RedisTemplate<>();
                  template.setConnectionFactory(jedisConnectionFactory());
                  return template;
              }
          }
          
          1. 创建 JwtTokenUtil 类,用于生成和验证JWT令牌。
          @Component
          public class JwtTokenUtil implements Serializable {
              private static final long serialVersionUID = -2550185165626007488L;
              private static final String secret = "mySecret";
              public String getUsernameFromToken(String token) {
                  return getClaimFromToken(token, Claims::getSubject);
              }
              public Date getExpirationDateFromToken(String token) {
                  return getClaimFromToken(token, Claims::getExpiration);
              }
              public  T getClaimFromToken(String token, Function claimsResolver) {
                  final Claims claims = getAllClaimsFromToken(token);
                  return claimsResolver.apply(claims);
              }
              private Claims getAllClaimsFromToken(String token) {
                  return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
              }
              private Boolean isTokenExpired(String token) {
                  final Date expiration = getExpirationDateFromToken(token);
                  return expiration.before(new Date());
              }
              public String generateToken(UserDetails userDetails) {
                  Map claims = new HashMap<>();
                  return doGenerateToken(claims, userDetails.getUsername());
              }
              private String doGenerateToken(Map claims, String subject) {
                  return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
                          .setExpiration(new Date(System.currentTimeMillis() + 5 * 60 * 60 * 1000))
                          .signWith(SignatureAlgorithm.HS512, secret).compact();
              }
              public Boolean validateToken(String token, UserDetails userDetails) {
                  final String username = getUsernameFromToken(token);
                  return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
              }
          }` 
          
          1. 创建 JwtAuthenticationEntryPoint 类,用于处理未经授权的请求。
          @Component
          public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
              private static final long serialVersionUID = -7858869558953243875L;
              @Override
              public void commence(HttpServletRequest request, HttpServletResponse response,
                                   AuthenticationException authException) throws IOException {
                  response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
              }
          }` 
          
          1. 创建 JwtRequestFilter 类,用于解析和验证JWT令牌。
          @Component
          public class JwtRequestFilter extends OncePerRequestFilter {
              @Autowired
              private MyUserDetailsService myUserDetailsService;
              @Autowired
              private JwtTokenUtil jwtTokenUtil;
              @Override
              protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
                      throws ServletException, IOException {
                  final String requestTokenHeader = request.getHeader("Authorization");
                  String username = null;
                  String jwtToken = null;
                  if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
                      jwtToken = requestTokenHeader.substring(7);
                      try {
                          username = jwtTokenUtil.getUsernameFromToken(jwtToken);
                      } catch (IllegalArgumentException e) {
                          System.out.println("Unable to get JWT Token");
                      } catch (ExpiredJwtException e) {
                          System.out.println("JWT Token has expired");
                      }
                  } else {
                      logger.warn("JWT Token does not begin with Bearer String");
                  }
                  if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                      UserDetails userDetails = this.myUserDetailsService.loadUserByUsername(username);
                      if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
                          UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                                  userDetails, null, userDetails.getAuthorities());
                          usernamePasswordAuthenticationToken
                                  .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                          SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                      }
                  }
                  chain.doFilter(request, response);
              }
          }` 
          
          1. 配置Spring Security
          @Configuration
          @EnableWebSecurity
          public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
              @Autowired
              private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
              @Autowired
              private UserDetailsService jwtUserDetailsService;
              @Autowired
              private JwtRequestFilter jwtRequestFilter;
              @Autowired
              public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
                  auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
              }
              @Bean
              public PasswordEncoder passwordEncoder() {
                  return new BCryptPasswordEncoder();
              }
              @Bean
              @Override
              public AuthenticationManager authenticationManagerBean() throws Exception {
                  return super.authenticationManagerBean();
              }
              @Override
              protected void configure(HttpSecurity httpSecurity) throws Exception {
                  httpSecurity.csrf().disable()
                          .authorizeRequests().antMatchers("/authenticate").permitAll().
                                  anyRequest().authenticated().and().
                                  exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
                          .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
                  httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
              }
          }`
          

          以上是简单的Spring Boot整合Redis、Security、JWT和Redis的示例,可以根据自己的实际需求进行调整。

          📑文章末尾

          在这里插入图片描述