当前位置:   article > 正文

spring security webflux 跨域防护_serveraccessdeniedhandler

serveraccessdeniedhandler

spring security webflux 跨域防护

 

官网:https://docs.spring.io/spring-security/site/docs/5.3.2.RELEASE/reference/html5/#webflux-csrf-using

 

跨域防护:客户端的请求携带 csrf token,后端接到请求后,与存储在session中的csrf token比对,若一致则请求通过,否则抛出异常,spring security 跨域防护默认开启

 

**************************

相关类及接口

 

ServerHttpSecurity

  1. public class ServerHttpSecurity {
  2. **************
  3. 内部类:ServerHttpSecurity.CsrfSpec
  4. public class CsrfSpec {
  5. private CsrfWebFilter filter; //拦截验证csrf token
  6. private ServerCsrfTokenRepository csrfTokenRepository; //后端存储csrf token
  7. private boolean specifiedRequireCsrfProtectionMatcher;
  8. public ServerHttpSecurity.CsrfSpec accessDeniedHandler(ServerAccessDeniedHandler accessDeniedHandler) {
  9. this.filter.setAccessDeniedHandler(accessDeniedHandler);
  10. return this;
  11. }
  12. public ServerHttpSecurity.CsrfSpec csrfTokenRepository(ServerCsrfTokenRepository csrfTokenRepository) {
  13. this.csrfTokenRepository = csrfTokenRepository;
  14. return this;
  15. }
  16. public ServerHttpSecurity.CsrfSpec requireCsrfProtectionMatcher(ServerWebExchangeMatcher requireCsrfProtectionMatcher) {
  17. this.filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
  18. this.specifiedRequireCsrfProtectionMatcher = true;
  19. return this;
  20. }
  21. public ServerHttpSecurity.CsrfSpec tokenFromMultipartDataEnabled(boolean enabled) {
  22. this.filter.setTokenFromMultipartDataEnabled(enabled);
  23. return this;
  24. }
  25. public ServerHttpSecurity and() {
  26. return ServerHttpSecurity.this;
  27. }
  28. public ServerHttpSecurity disable() {
  29. ServerHttpSecurity.this.csrf = null;
  30. return ServerHttpSecurity.this;
  31. }
  32. protected void configure(ServerHttpSecurity http) {
  33. if (this.csrfTokenRepository != null) {
  34. this.filter.setCsrfTokenRepository(this.csrfTokenRepository);
  35. if (ServerHttpSecurity.this.logout != null) {
  36. ServerHttpSecurity.this.logout.addLogoutHandler(new CsrfServerLogoutHandler(this.csrfTokenRepository));
  37. }
  38. }
  39. http.addFilterAt(this.filter, SecurityWebFiltersOrder.CSRF);
  40. }
  41. private CsrfSpec() {
  42. this.filter = new CsrfWebFilter();
  43. this.csrfTokenRepository = new WebSessionServerCsrfTokenRepository();
  44. }
  45. }

 

CsrfWebFilter:拦截验证csrf token,默认不拦截ALLOWED_METHODS包含的方法(GET、HEAD等)

  1. public class CsrfWebFilter implements WebFilter {
  2. public static final ServerWebExchangeMatcher DEFAULT_CSRF_MATCHER = new CsrfWebFilter.DefaultRequireCsrfProtectionMatcher();
  3. private static final String SHOULD_NOT_FILTER = "SHOULD_NOT_FILTER" + CsrfWebFilter.class.getName();
  4. private ServerWebExchangeMatcher requireCsrfProtectionMatcher;
  5. private ServerCsrfTokenRepository csrfTokenRepository;
  6. private ServerAccessDeniedHandler accessDeniedHandler;
  7. private boolean isTokenFromMultipartDataEnabled;
  8. public CsrfWebFilter() {
  9. this.requireCsrfProtectionMatcher = DEFAULT_CSRF_MATCHER;
  10. this.csrfTokenRepository = new WebSessionServerCsrfTokenRepository();
  11. this.accessDeniedHandler = new HttpStatusServerAccessDeniedHandler(HttpStatus.FORBIDDEN);
  12. } //服务端使用WebSessionServerCsrfTokenRepository存储csrf token
  13. public void setAccessDeniedHandler(ServerAccessDeniedHandler accessDeniedHandler) {
  14. Assert.notNull(accessDeniedHandler, "accessDeniedHandler");
  15. this.accessDeniedHandler = accessDeniedHandler;
  16. }
  17. public void setCsrfTokenRepository(ServerCsrfTokenRepository csrfTokenRepository) {
  18. Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
  19. this.csrfTokenRepository = csrfTokenRepository;
  20. }
  21. public void setRequireCsrfProtectionMatcher(ServerWebExchangeMatcher requireCsrfProtectionMatcher) {
  22. Assert.notNull(requireCsrfProtectionMatcher, "requireCsrfProtectionMatcher cannot be null");
  23. this.requireCsrfProtectionMatcher = requireCsrfProtectionMatcher;
  24. }
  25. public void setTokenFromMultipartDataEnabled(boolean tokenFromMultipartDataEnabled) {
  26. this.isTokenFromMultipartDataEnabled = tokenFromMultipartDataEnabled;
  27. }
  28. public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
  29. return Boolean.TRUE.equals(exchange.getAttribute(SHOULD_NOT_FILTER)) ? chain.filter(exchange).then(Mono.empty()) : this.requireCsrfProtectionMatcher.matches(exchange).filter((matchResult) -> {
  30. return matchResult.isMatch();
  31. }).filter((matchResult) -> {
  32. return !exchange.getAttributes().containsKey(CsrfToken.class.getName());
  33. }).flatMap((m) -> {
  34. return this.validateToken(exchange);
  35. }).flatMap((m) -> {
  36. return this.continueFilterChain(exchange, chain);
  37. }).switchIfEmpty(this.continueFilterChain(exchange, chain).then(Mono.empty())).onErrorResume(CsrfException.class, (e) -> {
  38. return this.accessDeniedHandler.handle(exchange, e);
  39. });
  40. }
  41. public static void skipExchange(ServerWebExchange exchange) {
  42. exchange.getAttributes().put(SHOULD_NOT_FILTER, Boolean.TRUE);
  43. }
  44. private Mono<Void> validateToken(ServerWebExchange exchange) {
  45. //从请求中加在csrf token,与session 中的csrf进行比对,验证csrf token
  46. return this.csrfTokenRepository.loadToken(exchange).switchIfEmpty(Mono.defer(() -> {
  47. return Mono.error(new CsrfException("CSRF Token has been associated to this client"));
  48. })).filterWhen((expected) -> {
  49. return this.containsValidCsrfToken(exchange, expected);
  50. }).switchIfEmpty(Mono.defer(() -> {
  51. return Mono.error(new CsrfException("Invalid CSRF Token"));
  52. })).then();
  53. }
  54. private Mono<Boolean> containsValidCsrfToken(ServerWebExchange exchange, CsrfToken expected) {
  55. return exchange.getFormData().flatMap((data) -> {
  56. return Mono.justOrEmpty(data.getFirst(expected.getParameterName()));
  57. }).switchIfEmpty(Mono.justOrEmpty(exchange.getRequest().getHeaders().getFirst(expected.getHeaderName()))).switchIfEmpty(this.tokenFromMultipartData(exchange, expected)).map((actual) -> {
  58. return actual.equals(expected.getToken());
  59. });
  60. }
  61. private Mono<String> tokenFromMultipartData(ServerWebExchange exchange, CsrfToken expected) {
  62. if (!this.isTokenFromMultipartDataEnabled) {
  63. return Mono.empty();
  64. } else {
  65. ServerHttpRequest request = exchange.getRequest();
  66. HttpHeaders headers = request.getHeaders();
  67. MediaType contentType = headers.getContentType();
  68. return !contentType.includes(MediaType.MULTIPART_FORM_DATA) ? Mono.empty() : exchange.getMultipartData().map((d) -> {
  69. return (Part)d.getFirst(expected.getParameterName());
  70. }).cast(FormFieldPart.class).map(FormFieldPart::value);
  71. }
  72. }
  73. private Mono<Void> continueFilterChain(ServerWebExchange exchange, WebFilterChain chain) {
  74. return Mono.defer(() -> {
  75. Mono<CsrfToken> csrfToken = this.csrfToken(exchange);
  76. exchange.getAttributes().put(CsrfToken.class.getName(), csrfToken);
  77. return chain.filter(exchange);
  78. });
  79. }
  80. private Mono<CsrfToken> csrfToken(ServerWebExchange exchange) {
  81. return this.csrfTokenRepository.loadToken(exchange).switchIfEmpty(this.generateToken(exchange));
  82. }
  83. private Mono<CsrfToken> generateToken(ServerWebExchange exchange) {
  84. return this.csrfTokenRepository.generateToken(exchange).delayUntil((token) -> {
  85. return this.csrfTokenRepository.saveToken(exchange, token);
  86. });
  87. }
  88. private static class DefaultRequireCsrfProtectionMatcher implements ServerWebExchangeMatcher {
  89. private static final Set<HttpMethod> ALLOWED_METHODS;
  90. private DefaultRequireCsrfProtectionMatcher() {
  91. }
  92. public Mono<MatchResult> matches(ServerWebExchange exchange) {
  93. return Mono.just(exchange.getRequest()).map((r) -> {
  94. return r.getMethod();
  95. }).filter((m) -> {
  96. return ALLOWED_METHODS.contains(m);
  97. }).flatMap((m) -> {
  98. return MatchResult.notMatch();
  99. }).switchIfEmpty(MatchResult.match());
  100. }
  101. static {
  102. ALLOWED_METHODS = new HashSet(Arrays.asList(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.TRACE, HttpMethod.OPTIONS));
  103. } //默认不拦截 ALLOWED_METHODS 包含的方法
  104. }
  105. }

 

WebSessionServerCsrfTokenRepository:session 中存储csrf token

  1. public class WebSessionServerCsrfTokenRepository implements ServerCsrfTokenRepository {
  2. private static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf"; //csrf token若在表单中,参数名默认为:_csrf
  3. private static final String DEFAULT_CSRF_HEADER_NAME = "X-CSRF-TOKEN"; //csrf token若在header中,参数名默认为:X-CSRF-TOKEN
  4. private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = WebSessionServerCsrfTokenRepository.class.getName().concat(".CSRF_TOKEN");
  5. //session 中存储的csrf token的名称默认为:WebSessionServerCsrfTokenRepository类名+“.CSRF_TOKEN”
  6. private String parameterName = "_csrf";
  7. private String headerName = "X-CSRF-TOKEN";
  8. private String sessionAttributeName;
  9. public WebSessionServerCsrfTokenRepository() {
  10. this.sessionAttributeName = DEFAULT_CSRF_TOKEN_ATTR_NAME;
  11. }
  12. public Mono<CsrfToken> generateToken(ServerWebExchange exchange) {
  13. return Mono.fromCallable(() -> {
  14. return this.createCsrfToken();
  15. });
  16. }
  17. public Mono<Void> saveToken(ServerWebExchange exchange, CsrfToken token) {
  18. return exchange.getSession().doOnNext((session) -> {
  19. this.putToken(session.getAttributes(), token);
  20. }).flatMap((session) -> {
  21. return session.changeSessionId();
  22. });
  23. }
  24. private void putToken(Map<String, Object> attributes, CsrfToken token) {
  25. if (token == null) {
  26. attributes.remove(this.sessionAttributeName);
  27. } else {
  28. attributes.put(this.sessionAttributeName, token);
  29. }
  30. }
  31. public Mono<CsrfToken> loadToken(ServerWebExchange exchange) {
  32. return exchange.getSession().filter((s) -> {
  33. return s.getAttributes().containsKey(this.sessionAttributeName);
  34. }).map((s) -> {
  35. return (CsrfToken)s.getAttribute(this.sessionAttributeName);
  36. });
  37. }
  38. public void setParameterName(String parameterName) {
  39. Assert.hasLength(parameterName, "parameterName cannot be null or empty");
  40. this.parameterName = parameterName;
  41. }
  42. public void setHeaderName(String headerName) {
  43. Assert.hasLength(headerName, "headerName cannot be null or empty");
  44. this.headerName = headerName;
  45. }
  46. public void setSessionAttributeName(String sessionAttributeName) {
  47. Assert.hasLength(sessionAttributeName, "sessionAttributename cannot be null or empty");
  48. this.sessionAttributeName = sessionAttributeName;
  49. }
  50. private CsrfToken createCsrfToken() {
  51. return new DefaultCsrfToken(this.headerName, this.parameterName, this.createNewToken());
  52. }
  53. private String createNewToken() {
  54. return UUID.randomUUID().toString();
  55. }
  56. }

 

 

**************************

示例

 

********************

controller 层

 

RedirectController

  1. @Controller
  2. public class RedirectController {
  3. @RequestMapping("/hello")
  4. public String hello2(){
  5. return "hello";
  6. }
  7. }

 

HelloConreoller

  1. @RestController
  2. public class HelloController {
  3. @RequestMapping("/hello2")
  4. public void hello2(ServerWebExchange exchange){
  5. AtomicReference<String> result = new AtomicReference<>();
  6. exchange.getFormData().subscribe(map ->{
  7. System.out.println("name:"+map.getFirst("name"));
  8. System.out.println("age:"+map.getFirst("age"));
  9. result.set(map.getFirst("name")+" "+map.getFirst("age"));
  10. });
  11. return result.get();
  12. }
  13. }

 

********************

前端页面

 

hello.html

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns="http://www.w3.org/1999/xhtml"
  3. xmlns:th="http://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
  4. <head>
  5. <meta charset="UTF-8">
  6. <title>Title</title>
  7. </head>
  8. <body>
  9. <form th:action="@{/hello8}" method="post" th:align="center">
  10. name:<input type="text" name="name"><br>
  11. age :<input type="text" name="age"><br>
  12. <button> 提交 </button>
  13. </form>
  14. </body>

 

 

**************************

使用测试

 

localhost:8080/hello

                        

 

默认开启csrf,thymeleaf会自动添加csrf token

                       

 

提交数据

                      

控制台输出

  1. name:瓜田李下
  2. age:20

 

 

如果直接在postman中提交数据,则报错

                     

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/68920?site
推荐阅读
相关标签
  

闽ICP备14008679号