Spring安全和过滤器链

huangapple go评论82阅读模式
英文:

Spring security and filter chain

问题

早上好。
我在关于Spring Security的filterchain和security配置方面遇到了困难

@Configuration
public class SecurityConfiguration {
    @Autowired
    private CustomHeaderVerificationFilter customHeaderFilter;
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http
                    .addFilterBefore(customHeaderFilter, BasicAuthenticationFilter.class)
                    .authorizeRequests()
                    .requestMatchers(HttpMethod.GET, "/myapp/login").permitAll()
                    .anyRequest().permitAll();;

            return http.build();
        }
    }

如您所见,逻辑将是这样的,每个调用都将由customHeaderFilter进行过滤,然后如果调用是来自httpGET的myapp/login,我将允许它,不需要其他身份验证,否则...我允许所有(原因在后面)

@Component
public class CustomHeaderVerificationFilter extends OncePerRequestFilter {

    @Autowired
    Dao engine;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            String headerValue = request.getHeader("Authorization");
        try {
            if (headerValue != null && calculateMatch(headerValue.split("Bearer:")[1],request.getHeader("realmName"))) {
                filterChain.doFilter(request, response);
            } else {
              throw new NullPointerException();
            }
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (NullPointerException e2){
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.setContentType("application/json");
            response.getWriter().write("Errore credenziali errate");
        }
    }

        private boolean calculateMatch(String fromFront,String realmNameHeader) throws NoSuchAlgorithmException,NullPointerException {
        //获取后端的密钥

                String realmSecret = engine.getSecret(realmNameHeader);
                MessageDigest digest = MessageDigest.getInstance("SHA-256");
                String input = realmSecret + ":" + realmNameHeader;
                byte[] hashBytes = digest.digest(input.getBytes(StandardCharsets.UTF_8));
                StringBuilder hexString = new StringBuilder();
                for (byte b : hashBytes) {
                    String hex = Integer.toHexString(0xff & b);
                    if (hex.length() == 1) {
                        hexString.append('0');
                    }
                    hexString.append(hex);
                }
                String s = hexString.toString();
                return fromFront.equals(hexString.toString());

        }

}

(一个相当基本的非JWT令牌过滤器,我认为这不是问题)。

然而,每次我使用POST调用时,我都会收到未授权的403错误。
Csrf应该是“问题”,我找到的几乎所有文档都只是禁用它,但我想要掌握它并集成到我的应用程序中。
我不明白我的POST请求是否必须在之前进行GET调用以请求CSRF令牌,还是在调用的握手部分完成。
我尝试了各种方法,但很多方法都使用.csrf(),而它已被弃用,还使用自定义过滤器只检查长度,但出于某种有趣的原因,只有我的GET请求会进入此过滤器。
所以:流程中的错误在哪里?我需要按逻辑检查CSRF过滤器吗?我必须将其保存为JWT令牌?


<details>
<summary>英文:</summary>

Good morning.
I&#39;m struggling about filterchain and security configuration for spring security

@Configuration
public class SecurityConfiguration {
@Autowired
private CustomHeaderVerificationFilter customHeaderFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.addFilterBefore(customHeaderFilter, BasicAuthenticationFilter.class)
.authorizeRequests()
.requestMatchers(HttpMethod.GET, "/myapp/login").permitAll()
.anyRequest().permitAll();;

        return http.build();
    }
}

As you can see, the logic will be soemthing like, every call will be filtred by customHeaderFilter, then if the call is from myapp/login as httpGET i will permit and no other authentication will be needed, otherwise...i permit all (reason lie ahead)

@Component
public class CustomHeaderVerificationFilter extends OncePerRequestFilter {

@Autowired
Dao engine;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String headerValue = request.getHeader(&quot;Authorization&quot;);
    try {
        if (headerValue != null &amp;&amp; calculateMatch(headerValue.split(&quot;Bearer:&quot;)[1],request.getHeader(&quot;realmName&quot;))) {
            filterChain.doFilter(request, response);
        } else {
          throw new NullPointerException();
        }
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException(e);
    } catch (NullPointerException e2){
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType(&quot;application/json&quot;);
        response.getWriter().write(&quot;Errore credenziali errate&quot;);
    }
}

    private boolean calculateMatch(String fromFront,String realmNameHeader) throws NoSuchAlgorithmException,NullPointerException {
    //prendo la secretdalBack

            String realmSecret = engine.getSecret(realmNameHeader);
            MessageDigest digest = MessageDigest.getInstance(&quot;SHA-256&quot;);
            String input = realmSecret + &quot;:&quot; + realmNameHeader;
            byte[] hashBytes = digest.digest(input.getBytes(StandardCharsets.UTF_8));
            StringBuilder hexString = new StringBuilder();
            for (byte b : hashBytes) {
                String hex = Integer.toHexString(0xff &amp; b);
                if (hex.length() == 1) {
                    hexString.append(&#39;0&#39;);
                }
                hexString.append(hex);
            }
            String s = hexString.toString();
            return fromFront.equals(hexString.toString());

    }

}

(a quite basic filter for non jwt bearer, this is not the issue, i suppose).

nevertheless, every post call i use, i will recive a unauthorize 403 error.
Csrf should be the &quot;problem&quot; and quite all the documentation i found, they just skip it disabling: but i will to master it and integrate in my app.
I did not understood if my POST must be preceded by a GET call to ask a csrf token, or is done in the handshaking part of call.
I test various approach but lot of them utilyze .csrf() and it&#39;s deprecated, also whit custom filter who just check the lenght, but for a funny reason, only my get will enter in this filter.
So: where is the error in the flow? i need to check by logic the csrf filter? i must save it as jwt token?





</details>


# 答案1
**得分**: 1

你所遇到的错误是由于 CSRF 配置引起的,你需要在所有不安全的请求中都包含 CSRF 令牌(即任何会改变状态的请求)。

CSRF 攻击之所以有效,是因为浏览器请求自动包含所有的 Cookie,包括会话 Cookie。因此,如果用户已经在站点上进行了身份验证,并且用户访问了一个恶意网站,那么这个恶意网站可以向我们的网站发送一个 POST 请求(执行任何操作),而我们的网站无法区分合法授权的请求和伪造的身份验证请求。因此,我们希望为用户提供一个唯一的令牌,用户可以保存(远离 Cookie),并且用户在每个不安全的请求中都使用该令牌(即任何会改变状态的请求)。

何时以及如何获取 CSRF 令牌?

1- 在 Spring Security 5 中,默认行为是 CsrfToken 将在每个响应上加载。所以,默认情况下,你会在每个来自服务器的响应中得到一个 XSRF-TOKEN Cookie。

2- 在 Spring Security 6 中,默认情况下,CsrfToken 的查找将被推迟,直到需要它为止。所以,除非你发送一个改变状态的请求(POST、PUT 等),否则不会设置 Cookie。你可以通过以下配置来克服这个问题,以获取每个请求的 csrfToken。

```java
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
	
    CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
	CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
	// 设置 CsrfToken 将要填充的属性名称
	requestHandler.setCsrfRequestAttributeName("_csrf");
	http
		.csrf((csrf) -> csrf
			.csrfTokenRepository(tokenRepository)
			.csrfTokenRequestHandler(requestHandler)
		)
		.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class);

	return http.build();
}

private static final class CsrfCookieFilter extends OncePerRequestFilter {

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
		// 通过加载延迟令牌来将令牌值呈现为 Cookie
		csrfToken.getToken();

		filterChain.doFilter(request, response);
	}
}

3- 另外,你也可以创建一个返回 CSRF 令牌的端点,需要时调用它,像这样:

@GetMapping("/csrf")
public CsrfToken getCsrfToken(CsrfToken csrf) {
    return csrf;
}

以上三种方法都是可接受的解决方案。在第一和第二种方法中,你从 Cookie 中获取 XSRF-TOKEN,而在第三种方法中,你从响应体中获取令牌,然后将其用作下一个不安全请求的 X-CSRF-TOKEN 头。

可能有用的链接:

cookieToHeader

https://docs.spring.io/spring-security/reference/features/exploits/csrf.html

https://docs.spring.io/spring-security/reference/5.8/migration/servlet/exploits.html#_defer_loading_csrftoken

英文:

the error you are facing is due to csrf configuration, you need to have csrf token in all your unsafe requests(any request that changes state)

A CSRF attack works because browser requests automatically include all cookies including session cookies. Therefore, if the user is authenticated to the site,
and the user enters an evil website this evil website can send a post request (does whatever) to our website and our site cannot distinguish between legitimate authorized requests and forged authenticated requests.
so we want to give the user a unique token that he can save (away from cookies) and the user uses that token in each unsafe request (ie any request that changes the state)

when and how to get csrf token ?

1- In Spring Security 5, the default behavior is that the CsrfToken will be loaded on every response. so you by default you get an XSRF-TOKEN cookie within each response from the server.

2- In Spring Security 6, the default is that the lookup of the CsrfToken will be deferred until it is needed. so the cookie won't be set unless you send a state changing request (post, put, ...)
you can overcome this and get csrfToken for each request with the following configurations .

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
	
    CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
	CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
	// set the name of the attribute the CsrfToken will be populated on
	requestHandler.setCsrfRequestAttributeName(&quot;_csrf&quot;);
	http
		.csrf((csrf) -&gt; csrf
			.csrfTokenRepository(tokenRepository)
			.csrfTokenRequestHandler(requestHandler)
		)
		.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class);

	return http.build();
}

private static final class CsrfCookieFilter extends OncePerRequestFilter {

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
		// Render the token value to a cookie by causing the deferred token to be loaded
		csrfToken.getToken();

		filterChain.doFilter(request, response);
	}
}

3- also, you can have an endpoint that returns csrf token and you hit it whenever you need. like this

@GetMapping(&quot;/csrf&quot;)
public CsrfToken getCsrfToken(CsrfToken csrf) {
    return csrf;
}

all 3 are acceptable solutions.
in the first and second you fetch XSRF-TOKEN from cookies and
in the third you fetch the token from the response body, then use it as X-CSRF-TOKEN header with next unsafe requests.

links that may help :

cookieToHeader

https://docs.spring.io/spring-security/reference/features/exploits/csrf.html

https://docs.spring.io/spring-security/reference/5.8/migration/servlet/exploits.html#_defer_loading_csrftoken

huangapple
  • 本文由 发表于 2023年6月22日 18:04:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/76530759.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定