一个注解搞定 Spring Security 忽略拦截

Posted Java知音_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个注解搞定 Spring Security 忽略拦截相关的知识,希望对你有一定的参考价值。

点击关注公众号,实用技术文章及时了解

  • Spring Security 拦截问题

  • 一个注解搞定Spring Security 忽略拦截

    • 基于注解形式

    • 基于配置形式

  • 总结

源码地址

  • https://github.com/galaxy-sea/galaxy-blogs/tree/master/code/security-annotation

Spring Security 拦截问题

Spring Security是干啥的我就不想解释了, 毕竟百度上面的讲解一大堆解说的可是会比我细致😄。

开发的时候我们经常需要对部分的@RequestMapping设置为 public api 不需要登陆也可以访问如登陆接口等等。偏爱Spring注解编程,那我们就基于注解的方式来忽略Spring Security的拦截吧。

public class SecurityConfigurer extends WebSecurityConfigurerAdapter 
  WebSecurity.IgnoredRequestConfigurer ignoring = web.ignoring();
     ignoring.antMatchers("/login");
     ignoring.antMatchers("/public api...");

就如我们上面代码展示的一样,我们经常需要对Spring Security的忽略名单进行硬编码配置或者配置文件配置我都感觉挺繁琐的,Spring都已经提倡基于注解编程了,百度到的内容居然还是教我用硬编码所以下面我们就机遇注解来实现Spring Security忽略拦截的实现吧。

一个注解搞定Spring Security 忽略拦截

本章节我们就用注解的方式来实现Spring Security忽略拦截吧。本章会分别介绍基于注解方式和配置文件方式

  • 注解主要针对@RequestMapping进行忽略

  • 配置文件主要针对静态文件(js,html,css,...)进行忽略

环境配置

  • Spring Boot 2.5.X

    • Spring MVC

    • Spring Security

    • lombok

  • Java 1.8

因为是单纯的演示一下如何基于注解忽略Spring Security拦截,所以只会使用最小demo而不是大篇长论的去魔改Spring Security(其实是我懒得写啦反正百度都有😄)
老规矩下面直接放代码吧。

代码就需要大家去https://start.spring.io/生成一下啦,然后改一下Spring Boot的版本号,或者去我的GitHub直接clone代码,开头已经贴出!

SecurityConfiguration 配置类

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter 

  @Override
  public void configure(WebSecurity web) 
    WebSecurity.IgnoredRequestConfigurer ignoring = web.ignoring();
    ignoring.antMatchers(HttpMethod.GET, "/hello/hardcode");
  

HelloController用于测试Spring Security拦截器

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author changjin wei(魏昌进)
 * @since 2022/12/15
 */
@RestController
@RequestMapping("hello")
public class HelloController 

  @GetMapping("/security")
  public String security()
    return "Hello, Security";
  

  @GetMapping("/hardcode")
  public String hardcode()
    return "Hello, hardcode";
  

测试结果

# 测试结果1
curl http://127.0.0.1:8080/hello/security -i
HTTP/1.1 401 
Content-Type: application/json

"timestamp":"2022-12-16T10:54:44.403+00:00","status":401,"error":"Unauthorized","path":"/hello/security"

# 测试结果2
curl http://127.0.0.1:8080/hello/hardcode -i
HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8

Hello, hardcod

基于注解形式

其实硬编码这种处理方式如果public api较少的情况下还是没有问题的,但是public api变多了就不是很友好了。

那么现在我们就基于注解的形式实现一下吧,代码量及其精简的哦。

首先我们创建一个IgnoreWebSecurity注解用户将api设置为public api, 然后我们改造一下SecurityConfiguration类。

IgnoreWebSecurity

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author changjin wei(魏昌进)
 * @since 2022/1/3
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface IgnoreWebSecurity 

改造 SecurityConfiguration

/**
 * @author changjin wei(魏昌进)
 * @since 2022/12/15
 */
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfiguration extends WebSecurityConfigurerAdapter 


  private final RequestMappingHandlerMapping requestMappingHandlerMapping;


  @Override
  public void configure(WebSecurity web) 
    WebSecurity.IgnoredRequestConfigurer ignoring = web.ignoring();
    ignoring.antMatchers(HttpMethod.GET, "/hello/hardcode");
    this.ignoreAnnotation(ignoring, this.requestMappingHandlerMapping);
  

  private void ignoreAnnotation(WebSecurity.IgnoredRequestConfigurer ignoring, RequestMappingHandlerMapping requestMappingHandlerMapping) 
    Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
    for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) 
      HandlerMethod handlerMethod = entry.getValue();
      if (handlerMethod.hasMethodAnnotation(IgnoreWebSecurity.class)) 
        Set<String> patternValues = entry.getKey().getPatternValues();
        Set<RequestMethod> methods = entry.getKey().getMethodsCondition().getMethods();
        if (CollectionUtils.isEmpty(methods)) 
          // RequestMapping没有指定method
          ignoring.antMatchers(patternValues.toArray(new String[0]));
         else 
          for (RequestMethod method : methods) 
            // RequestMapping指定了method
            ignoring.antMatchers(HttpMethod.resolve(method.name()), patternValues.toArray(new String[0]));
          
        
      
    
  

HelloController 增加一下测试方法

/**
 * @author changjin wei(魏昌进)
 * @since 2022/12/15
 */
@RestController
@RequestMapping("hello")
public class HelloController 

  /** 注解方式 */
  @GetMapping("/annotation")
  @IgnoreWebSecurity
  public String annotation() 
    return "Hello, annotation";
  


  /** 测试PathVariable参数 */
  @GetMapping("/path/annotation")
  @IgnoreWebSecurity
  public String annotationPath(@PathVariable String path) 
    return "Hello, annotation. path: " + path;
  

测试结果

# 测试注解
curl http://127.0.0.1:8080/hello/annotation -i
HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 17
Date: Sun, 18 Dec 2022 12:25:05 GMT

Hello, annotatio


# 测试注解
curl http://127.0.0.1:8080/hello/public_api/annotation -i
HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 35
Date: Sun, 18 Dec 2022 12:25:35 GMT

Hello, annotation. path: public_api

通过解析RequestMappingHandlerMapping我们就可以将api设置为public api了。这种方式大大简便的配置。

基于配置形式

上一章我们通过注解的方式去将api设置为public api但是这种方式对静态资源(html,js,css等)并不是很友好,好需要通过配置的文件的方式将一些静态资源设置为public,所以本章节通过配置文件方式来实现public api的忽略拦截

创建一个配置文件IgnoreWebSecurityProperties来配置

IgnoreWebSecurityProperties

/**
 * @author changjin wei(魏昌进)
 * @since 2022/12/15
 */
@Data
@ConfigurationProperties(prefix = "security.ignoring")
public class IgnoreWebSecurityProperties 
  /** 需要忽略的 URL 格式,不考虑请求方法 */
  private String[] pattern = ;

  /** 需要忽略的 GET 请求 */
  private String[] get = ;

  /**  需要忽略的 POST 请求 */
  private String[] post = ;

  /**  需要忽略的 DELETE 请求 */
  private String[] delete = ;

  /**  需要忽略的 PUT 请求 */
  private String[] put = ;

  /** 需要忽略的 HEAD 请求 */
  private String[] head = ;

  /** 需要忽略的 PATCH 请求 */
  private String[] patch = ;

  /** 需要忽略的 OPTIONS 请求 */
  private String[] options = ;

  /** 需要忽略的 TRACE 请求 */
  private String[] trace = ;

改造 IgnoreWebSecurityProperties

/**
 * @author changjin wei(魏昌进)
 * @since 2022/12/15
 */
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableConfigurationProperties(IgnoreWebSecurityProperties.class)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter 

  private final IgnoreWebSecurityProperties ignoreWebSecurityProperties;
  private final RequestMappingHandlerMapping requestMappingHandlerMapping;

  @Override
  public void configure(WebSecurity web) 
    WebSecurity.IgnoredRequestConfigurer ignoring = web.ignoring();
    ignoring.antMatchers(HttpMethod.GET, "/hello/hardcode");
    this.ignoreAnnotation(ignoring, this.requestMappingHandlerMapping);
    this.ignoreProperties(ignoring, this.ignoreWebSecurityProperties);
  
    ...
  private void ignoreProperties(WebSecurity.IgnoredRequestConfigurer ignoring, IgnoreWebSecurityProperties ignoreWebSecurityProperties) 
    ignoring.antMatchers(HttpMethod.GET, ignoreWebSecurityProperties.getGet())
            .antMatchers(HttpMethod.POST, ignoreWebSecurityProperties.getPost())
            .antMatchers(HttpMethod.DELETE, ignoreWebSecurityProperties.getDelete())
            .antMatchers(HttpMethod.PUT, ignoreWebSecurityProperties.getPut())
            .antMatchers(HttpMethod.HEAD, ignoreWebSecurityProperties.getHead())
            .antMatchers(HttpMethod.PATCH, ignoreWebSecurityProperties.getPatch())
            .antMatchers(HttpMethod.OPTIONS, ignoreWebSecurityProperties.getOptions())
            .antMatchers(HttpMethod.TRACE, ignoreWebSecurityProperties.getTrace())
            .antMatchers(ignoreWebSecurityProperties.getPattern());
  

改造HelloController

/**
 * @author changjin wei(魏昌进)
 * @since 2022/12/15
 */
@RestController
@RequestMapping("hello")
public class HelloController 
  /** 测试PathVariable参数 */
  @GetMapping("/properties")
  @IgnoreWebSecurity
  public String properties()
    return "Hello, properties";
  

  /** 测试PathVariable参数 */
  @GetMapping("/path/properties")
  @IgnoreWebSecurity
  public String propertiesPath(@PathVariable String path)
    return "Hello, properties. path: " + path;
  

application.yml

security:
  ignoring:
    get:
      - /hello/path/properties

测试结果

# 配置文件测试
curl http://127.0.0.1:8080/hello/properties -i
HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 17
Date: Sun, 18 Dec 2022 12:35:14 GMT

Hello, properties

# 配置文件测试
curl http://127.0.0.1:8080/hello/public_api/properties -i
HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 35
Date: Sun, 18 Dec 2022 12:36:01 GMT

Hello, properties. path: public_api

总结

其实无论是介于注解方式还是配置文件方式都各有优缺点看大家是否的选择了,通过这种方式可以减少代码硬编码的问题。

推荐
Java面试题宝典
技术内卷群,一起来学习!!


PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!

以上是关于一个注解搞定 Spring Security 忽略拦截的主要内容,如果未能解决你的问题,请参考以下文章

spring boot整合security 4,怎么设置忽略的静态资源?

Spring Security 权限管理

Spring Security 权限管理

spring的注解@Component@Bean,@Autowire一遍搞定

Spring-Security:忽略服务器名称的别名并强制重新登录

Spring Boot 一个注解搞定重试机制,不能太优雅了。。。