首页 > 解决方案 > Spring Security:通过扩展 CsrfRepository 自定义 CSRF 实现

问题描述

我正在尝试通过实现CsrfRepositorySpring Security 提供的接口在我的 Spring Boot 应用程序中创建自定义的 CSRF 实现。

下面是我的自定义存储库的样子:

public class CustomCookieCsrfTokenRepository implements CsrfTokenRepository {

    static final String DEFAULT_CSRF_COOKIE_NAME = "XSRF-TOKEN";

    static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf";

    static final String DEFAULT_CSRF_HEADER_NAME = "X-XSRF-TOKEN";

    @Override
    public CsrfToken generateToken(HttpServletRequest request) {
        return new DefaultCsrfToken(this.DEFAULT_CSRF_HEADER_NAME, this.DEFAULT_CSRF_PARAMETER_NAME, createNewToken());
    }

    @Override
    public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
        String tokenValue = token == null ? "" : token.getToken();
        Cookie cookie = new Cookie(this.DEFAULT_CSRF_COOKIE_NAME, tokenValue);
        cookie.setSecure(request.isSecure());
        response.addCookie(cookie);
    }

    @Override
    public CsrfToken loadToken(HttpServletRequest request) {
        Cookie cookie = WebUtils.getCookie(request, this.DEFAULT_CSRF_COOKIE_NAME);
        if (cookie == null) {
            return null;
        }
        String token = cookie.getValue();
        if (!StringUtils.hasLength(token)) {
            return null;
        }
        return new DefaultCsrfToken(this.DEFAULT_CSRF_HEADER_NAME, this.DEFAULT_CSRF_PARAMETER_NAME, token);
    }

    private String createNewToken() {
        String unsignedToken = UUID.randomUUID().toString();
        return RSAUtil.signMessage(unsignedToken, privateKey);
    }
}

问题:如您所见,我想使用私钥签署我的 cookie 值并使用公钥对其进行验证。问题是这个验证逻辑应该在哪里发生?我猜loadToken()方法可以具有验证签名的逻辑。这是正确的地方还是应该在其他地方进行?

有人可以提供一些关于如何以及在哪里处理这个问题的片段或示例吗?

标签: spring-bootspring-securitycsrf

解决方案


不,验证逻辑应该在您的自定义 CsrfTokenRepository 实现 的generateToken(HttpServletRequest request)中。
saveToken (CsrfToken token, HttpServletRequest request, HttpServletResponse response)应该保存令牌(或者当传递的 'token' 参数为空时删除保存的令牌)并且loadToken(HttpServletRequest request) 应该返回现有保存的令牌(由 saveToken 保存方法)用于当前请求/会话;

@Component
public class CustomCsrfTokenRepository implements CsrfTokenRepository {

    static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf";
    static final String DEFAULT_CSRF_HEADER_NAME = "X-XSRF-TOKEN";

    private String parameterName = DEFAULT_CSRF_PARAMETER_NAME;

    private String headerName = DEFAULT_CSRF_HEADER_NAME;

    private String cookieName = "USER_INFO";

    private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = CustomCsrfTokenRepository2.class
        .getName().concat(".CSRF_TOKEN");

    private String sessionAttributeName = DEFAULT_CSRF_TOKEN_ATTR_NAME;

    @Override
    public CsrfToken generateToken(HttpServletRequest request) {
        Cookie cookie = WebUtils.getCookie(request, this.cookieName);
        if (cookie == null) {
            return new DefaultCsrfToken(this.headerName, this.parameterName,
                createNewToken());
        }
        String cookieValue = cookie.getValue();
        String token = cookieValue.split("\\|")[0];
        if (!StringUtils.hasLength(token)) {
            return new DefaultCsrfToken(this.headerName, this.parameterName,
                createNewToken());
        }
        return new DefaultCsrfToken(this.headerName, this.parameterName, token);
    }

    @Override
    public void saveToken(CsrfToken token, HttpServletRequest request,
        HttpServletResponse response) {
        if (token == null) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                session.removeAttribute(this.sessionAttributeName);
            }
        }
        else {
            HttpSession session = request.getSession();
            session.setAttribute(this.sessionAttributeName, token);
        }
    }

    @Override
    public CsrfToken loadToken(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null) {
            return null;
        }
        return (CsrfToken) session.getAttribute(this.sessionAttributeName);
    }

    private String createNewToken() {
        return UUID.randomUUID().toString();
    }

}



您需要在 HttpSecurity 配置中设置 customCsrfRepoImpl bean,如下所示

@Configuration
@EnableWebSecurity
public class SecurityConfigurarion extends WebSecurityConfigurerAdapter implements WebMvcConfigurer {

    @Autowired
    private CsrfTokenRepository customCsrfTokenRepository; //your custom csrfToken repository impl class 

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {

        httpSecurity
            .csrf().csrfTokenRepository(customCsrfTokenRepository) //set your custom csrf impl in httpSecurity
            .and()
            .authorizeRequests()
                .antMatchers(permittedUrlsArr).permitAll()
                .anyRequest().authenticated()
                .and()
            .exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)
                .and()
            .logout()

    }

}

推荐阅读