PageSourceSearch

https://java2ai.com/assets/js/6de595de.6bd5d75c.js

js java2ai.com collected 2026-10-03 21:35:03 UTC 38,858 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkspring_ai_alibaba_website=self.webpackChunkspring_ai_alibaba_website||[]).push([[4052],{28453:(e,n,i)=>{i.d(n,{R:()=>o,x:()=>c});var t=i(96540);const r={},s=t.createContext(r);function o(e){const n=t.useContext(s);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:o(e.components),t.createElement(s.Provider,{value:n},e.children)}},72816:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>a,contentTitle:()=>c,default:()=>h,frontMatter:()=>o,metadata:()=>t,toc:()=>l});const t=JSON.parse('{"id":"mcps/mcp-security","title":"MCP Security","description":"\u6ce8\u610f\uff1a \u8fd9\u4ecd\u5728\u8fdb\u884c\u4e2d\u3002\u6587\u6863\u548c API \u53ef\u80fd\u5728\u672a\u6765\u7684\u7248\u672c\u4e2d\u66f4\u6539\u3002","source":"@site/integration/mcps/mcp-security.md","sourceDirName":"mcps","slug":"/mcps/mcp-security","permalink":"/integration/mcps/mcp-security","draft":false,"unlisted":false,"editUrl":"https://github.com/spring-ai-alibaba/website/tree/main/integration/mcps/mcp-security.md","tags":[],"version":"current","lastUpdatedBy":"Ken Liu","lastUpdatedAt":1768271906000,"frontMatter":{},"sidebar":"integrationSidebar","previous":{"title":"MCP Utilities","permalink":"/integration/mcps/mcp-helpers"},"next":{"title":"MCP Client Boot Starter","permalink":"/integration/mcps/mcp-client-boot-starter-docs"}}');var r=i(74848),s=i(28453);const o={},c="MCP Security",a={},l=[{value:"Overview",id:"overview",level:2},{value:"MCP Server Security",id:"mcp-server-security",level:2},{value:"Dependencies",id:"dependencies",level:3},{value:"OAuth 2.0 Configuration",id:"oauth-20-configuration",level:3},{value:"Basic OAuth 2.0 Setup",id:"basic-oauth-20-setup",level:4},{value:"Securing Tool Calls Only",id:"securing-tool-calls-only",level:4},{value:"API Key Authentication",id:"api-key-authentication",level:3},{value:"Known Limitations",id:"known-limitations",level:3},{value:"MCP Client Security",id:"mcp-client-security",level:2},{value:"Dependencies",id:"dependencies-1",level:3},{value:"Authorization Flows",id:"authorization-flows",level:3},{value:"Common Setup",id:"common-setup",level:3},{value:"HttpClient-Based Clients",id:"httpclient-based-clients",level:3},{value:"WebClient-Based Clients",id:"webclient-based-clients",level:3},{value:"Working Around Spring AI Autoconfiguration",id:"working-around-spring-ai-autoconfiguration",level:3},{value:"Option 1: Disable @Tool Auto-configuration",id:"option-1-disable-tool-auto-configuration",level:4},{value:"Option 2: Programmatic Client Configuration",id:"option-2-programmatic-client-configuration",level:4},{value:"Known Limitations",id:"known-limitations-1",level:3},{value:"MCP Authorization Server",id:"mcp-authorization-server",level:2},{value:"Dependencies",id:"dependencies-2",level:3},{value:"Configuration",id:"configuration",level:3},{value:"Known Limitations",id:"known-limitations-2",level:3},{value:"Samples and Integrations",id:"samples-and-integrations",level:2},{value:"Additional Resources",id:"additional-resources",level:2}];function u(e){const n={a:"a",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,s.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"mcp-security",children:"MCP Security"})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"\u6ce8\u610f\uff1a"})," \u8fd9\u4ecd\u5728\u8fdb\u884c\u4e2d\u3002\u6587\u6863\u548c API \u53ef\u80fd\u5728\u672a\u6765\u7684\u7248\u672c\u4e2d\u66f4\u6539\u3002"]}),"\n",(0,r.jsx)(n.p,{children:"Spring AI MCP Security \u6a21\u5757\u4e3a Spring AI \u4e2d\u7684 Model Context Protocol \u5b9e\u73b0\u63d0\u4f9b\u5168\u9762\u7684 OAuth 2.0 \u548c\u57fa\u4e8e API \u5bc6\u94a5\u7684\u5b89\u5168\u652f\u6301\u3002\u8fd9\u4e2a\u793e\u533a\u9a71\u52a8\u7684\u9879\u76ee\u4f7f\u5f00\u53d1\u8005\u80fd\u591f\u4f7f\u7528\u884c\u4e1a\u6807\u51c6\u7684\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u673a\u5236\u6765\u4fdd\u62a4 MCP \u670d\u52a1\u5668\u548c\u5ba2\u6237\u7aef\u3002"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"\u6ce8\u610f\uff1a"})," \u6b64\u6a21\u5757\u662f ",(0,r.jsx)(n.a,{href:"https://github.com/spring-ai-community/mcp-security",children:"spring-ai-community/mcp-security"})," \u9879\u76ee\u7684\u4e00\u90e8\u5206\uff0c\u76ee\u524d\u4ec5\u9002\u7528\u4e8e Spring AI \u7684 1.1.x \u5206\u652f\u3002\n\u8fd9\u662f\u4e00\u4e2a\u793e\u533a\u9a71\u52a8\u7684\u9879\u76ee\uff0c\u5c1a\u672a\u5f97\u5230 Spring AI \u6216 MCP \u9879\u76ee\u7684\u5b98\u65b9\u8ba4\u53ef\u3002"]}),"\n",(0,r.jsx)(n.h2,{id:"overview",children:"Overview"}),"\n",(0,r.jsx)(n.p,{children:"MCP Security \u6a21\u5757\u63d0\u4f9b\u4e09\u4e2a\u4e3b\u8981\u7ec4\u4ef6\uff1a"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.em,{children:"MCP Server Security"})," - \u7528\u4e8e Spring AI MCP \u670d\u52a1\u5668\u7684 OAuth 2.0 \u8d44\u6e90\u670d\u52a1\u5668\u548c API \u5bc6\u94a5\u8eab\u4efd\u9a8c\u8bc1"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.em,{children:"MCP Client Security"})," - \u7528\u4e8e Spring AI MCP \u5ba2\u6237\u7aef\u7684 OAuth 2.0 \u5ba2\u6237\u7aef\u652f\u6301"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.em,{children:"MCP Authorization Server"})," - \u5177\u6709 MCP \u7279\u5b9a\u529f\u80fd\u7684\u589e\u5f3a Spring Authorization Server"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"\u8be5\u9879\u76ee\u4f7f\u5f00\u53d1\u8005\u80fd\u591f\uff1a"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"\u4f7f\u7528 OAuth 2.0 \u8eab\u4efd\u9a8c\u8bc1\u548c\u57fa\u4e8e API \u5bc6\u94a5\u7684\u8bbf\u95ee\u6765\u4fdd\u62a4 MCP \u670d\u52a1\u5668"}),"\n",(0,r.jsx)(n.li,{children:"\u4f7f\u7528 OAuth 2.0 \u6388\u6743\u6d41\u7a0b\u914d\u7f6e MCP \u5ba2\u6237\u7aef"}),"\n",(0,r.jsx)(n.li,{children:"\u8bbe\u7f6e\u4e13\u95e8\u4e3a MCP \u5de5\u4f5c\u6d41\u8bbe\u8ba1\u7684\u6388\u6743\u670d\u52a1\u5668"}),"\n",(0,r.jsx)(n.li,{children:"\u4e3a MCP \u5de5\u5177\u548c\u8d44\u
16e90\u5b9e\u73b0\u7ec6\u7c92\u5ea6\u8bbf\u95ee\u63a7\u5236"}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"mcp-server-security",children:"MCP Server Security"}),"\n",(0,r.jsxs)(n.p,{children:["MCP Server Security \u6a21\u5757\u4e3a ",(0,r.jsx)(n.a,{href:"mcp-server-boot-starter-docs",children:"Spring AI's MCP servers"})," \u63d0\u4f9b OAuth 2.0 \u8d44\u6e90\u670d\u52a1\u5668\u529f\u80fd\u3002\n\u5b83\u8fd8\u63d0\u4f9b\u57fa\u4e8e API \u5bc6\u94a5\u7684\u8eab\u4efd\u9a8c\u8bc1\u7684\u57fa\u672c\u652f\u6301\u3002"]}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"\u91cd\u8981\u63d0\u793a\uff1a"})," \u6b64\u6a21\u5757\u4ec5\u4e0e\u57fa\u4e8e Spring WebMVC \u7684\u670d\u52a1\u5668\u517c\u5bb9\u3002"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"dependencies",children:"Dependencies"}),"\n",(0,r.jsx)(n.p,{children:"\u5c06\u4ee5\u4e0b\u4f9d\u8d56\u9879\u6dfb\u52a0\u5230\u9879\u76ee\u4e2d\uff1a"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Maven:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-xml",children:"<dependencies>\n    <dependency>\n        <groupId>org.springaicommunity</groupId>\n        <artifactId>mcp-server-security</artifactId>\n    </dependency>\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-security</artifactId>\n    </dependency>\n\n    \x3c!-- OPTIONAL: For OAuth2 support --\x3e\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>\n    </dependency>\n</dependencies>\n"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Gradle:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-groovy",children:"implementation 'org.springaicommunity:mcp-server-security'\nimplementation 'org.springframework.boot:spring-boot-starter-security'\n\n// OPTIONAL: For OAuth2 support\nimplementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'\n"})}),"\n",(0,r.jsx)(n.h3,{id:"oauth-20-configuration",children:"OAuth 2.0 Configuration"}),"\n",(0,r.jsx)(n.h4,{id:"basic-oauth-20-setup",children:"Basic OAuth 2.0 Setup"}),"\n",(0,r.jsxs)(n.p,{children:["\u9996\u5148\uff0c\u5728 ",(0,r.jsx)(n.code,{children:"application.properties"})," \u4e2d\u542f\u7528 MCP \u670d\u52a1\u5668\uff1a"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-properties",children:"spring.ai.mcp.server.name=my-cool-mcp-server\n# Supported protocols: STREAMABLE, STATELESS\nspring.ai.mcp.server.protocol=STREAMABLE\n"})}),"\n",(0,r.jsx)(n.p,{children:"\u7136\u540e\uff0c\u4f7f\u7528\u63d0\u4f9b\u7684 MCP configurer \u4f7f\u7528 Spring Security \u7684\u6807\u51c6 API \u914d\u7f6e\u5b89\u5168\uff1a"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:'@Configuration\n@EnableWebSecurity\nclass McpServerConfiguration {\n\n    @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")\n    private String issuerUrl;\n\n    @Bean\n    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n        return http\n                // Enforce authentication with token on EVERY request\n                .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())\n                // Configure OAuth2 on the MCP server\n                .with(\n                        McpServerOAuth2Configurer.mcpServerOAuth2(),\n                        (mcpAuthorization) -> {\n                            // REQUIRED: the issuerURI\n                            mcpAuthorization.authorizationServer(issuerUrl);\n                            // OPTIONAL: enforce the `aud` claim in the JWT token.\n                            // Not all authorization servers support resource indicators,\n                            // so it may be absent. Defaults to `false`.\n                            // See RFC 8707 Resource Indicators for OAuth 2.0\n                            // https://www.rfc-editor.org/rfc/rfc8707.html\n                            mcpAuthorization.validateAudienceClaim(true);\n                        }\n                )\n                .build();\n    }\n}\n'})}),"\n",(0,r.jsx)(n.h4,{id:"securing-tool-calls-only",children:"Securing Tool Calls Only"}),"\n",(0,r.jsxs)(n.p,{children:["\u60a8\u53ef\u4ee5\u914d\u7f6e\u670d\u52a1\u5668\u4ec5\u4fdd\u62a4\u5de5\u5177\u8c03\u7528\uff0c\u800c\u5c06\u5176\u4ed6 MCP \u64cd\u4f5c\uff08\u5982 ",(0,r.jsx)(n.code,{children:"initialize"})," \u548c ",(0,r.jsx)(n.code,{children:"tools/list"}),"\uff09\u516c\u5f00\uff1a"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:'@Configuration\n@EnableWebSecurity\n@EnableMethodSecurity // Enable annotation-driven security\nclass McpServerConfiguration {\n\n    @Value("${spring.security.oauth2.resourceserver.jwt.
1issuer-uri}")\n    private String issuerUrl;\n\n    @Bean\n    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n        return http\n                // Open every request on the server\n                .authorizeHttpRequests(auth -> {\n                    auth.requestMatcher("/mcp").permitAll();\n                    auth.anyRequest().authenticated();\n                })\n                // Configure OAuth2 on the MCP server\n                .with(\n                        McpResourceServerConfigurer.mcpServerOAuth2(),\n                        (mcpAuthorization) -> {\n                            // REQUIRED: the issuerURI\n                            mcpAuthorization.authorizationServer(issuerUrl);\n                        }\n                )\n                .build();\n    }\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:["\u7136\u540e\uff0c\u4f7f\u7528 ",(0,r.jsx)(n.code,{children:"@PreAuthorize"})," \u6ce8\u89e3\u548c",(0,r.jsx)(n.a,{href:"https://docs.spring.io/spring-security/reference/servlet/authorization/method-security.html",children:"\u65b9\u6cd5\u5b89\u5168"}),"\u6765\u4fdd\u62a4\u60a8\u7684\u5de5\u5177\u8c03\u7528\uff1a"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:'@Service\npublic class MyToolsService {\n\n    @PreAuthorize("isAuthenticated()")\n    @McpTool(name = "greeter", description = "A tool that greets you, in the selected language")\n    public String greet(\n            @ToolParam(description = "The language for the greeting (example: english, french, ...)") String language\n    ) {\n        if (!StringUtils.hasText(language)) {\n            language = "";\n        }\n        return switch (language.toLowerCase()) {\n            case "english" -> "Hello you!";\n            case "french" -> "Salut toi!";\n            default -> "I don\'t understand language \\"%s\\". So I\'m just going to say Hello!".formatted(language);\n        };\n    }\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:["\u60a8\u8fd8\u53ef\u4ee5\u4f7f\u7528 ",(0,r.jsx)(n.code,{children:"SecurityContextHolder"})," \u76f4\u63a5\u4ece\u5de5\u5177\u65b9\u6cd5\u8bbf\u95ee\u5f53\u524d\u8eab\u4efd\u9a8c\u8bc1\uff1a"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:'@McpTool(name = "greeter", description = "A tool that greets the user by name, in the selected language")\n@PreAuthorize("isAuthenticated()")\npublic String greet(\n        @ToolParam(description = "The language for the greeting (example: english, french, ...)") String language\n) {\n    if (!StringUtils.hasText(language)) {\n        language = "";\n    }\n    var authentication = SecurityContextHolder.getContext().getAuthentication();\n    var name = authentication.getName();\n    return switch (language.toLowerCase()) {\n        case "english" -> "Hello, %s!".formatted(name);\n        case "french" -> "Salut %s!".formatted(name);\n        default -> ("I don\'t understand language \\"%s\\". " +\n                    "So I\'m just going to say Hello %s!").formatted(language, name);\n    };\n}\n'})}),"\n",(0,r.jsx)(n.h3,{id:"api-key-authentication",children:"API Key Authentication"}),"\n",(0,r.jsxs)(n.p,{children:["MCP Server Security \u6a21\u5757\u8fd8\u652f\u6301\u57fa\u4e8e API \u5bc6\u94a5\u7684\u8eab\u4efd\u9a8c\u8bc1\u3002\u60a8\u9700\u8981\u63d0\u4f9b\u81ea\u5df1\u7684 ",(0,r.jsx)(n.code,{children:"ApiKeyEntityRepository"})," \u5b9e\u73b0\u6765\u5b58\u50a8 ",(0,r.jsx)(n.code,{children:"ApiKeyEntity"})," \u5bf9\u8c61\u3002"]}),"\n",(0,r.jsxs)(n.p,{children:["\u793a\u4f8b\u5b9e\u73b0\u63d0\u4f9b\u4e86 ",(0,r.jsx)(n.code,{children:"InMemoryApiKeyEntityRepository"})," \u4ee5\u53ca\u9ed8\u8ba4\u7684 ",(0,r.jsx)(n.code,{children:"ApiKeyEntityImpl"}),"\uff1a"]}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"\u8b66\u544a\uff1a"})," ",(0,r.jsx)(n.code,{children:"InMemoryApiKeyEntityRepository"})," \u4f7f\u7528 bcrypt \u5b58\u50a8 API \u5bc6\u94a5\uff0c\u8fd9\u5728\u8ba1\u7b97\u4e0a\u662f\u6602\u8d35\u7684\u3002\u5b83\u4e0d\u9002\u5408\u9ad8\u6d41\u91cf\u7684\u751f\u4ea7\u4f7f\u7528\u3002\u5bf9\u4e8e\u751f\u4ea7\u73af\u5883\uff0c\u8bf7\u5b9e\u73b0\u60a8\u81ea\u5df1\u7684 ",(0,r.jsx)(n.code,{children:"ApiKeyEntityRepository"}),"\u3002"]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:'@Configuration\n@EnableWebSecurity\nclass McpServerConfiguration {\n\n    @Bean\n    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n        return http.authorizeHttpRequests(authz -> authz.anyRequest().authenticated())\n                .with(\n                        mcpServerApiKey(),\n                        (apiKey) -> {\n                            // REQUIRED: the repo for API keys\n                            apiKey.apiKeyRepository(apiKeyRepository());\n\n                            // OPTIONAL: name of the header containing the API key.\n                            // Here for example, api keys will be sent with "CUSTOM-API-KEY: <value>"\n                            // Replaces .authenticationConverter(...) (see below)\n                            //\n                            // apiKey.headerName("CUSTOM-API-KEY");\n\n                            // OPTIONAL: custom converter for transforming an http request\n                            // into an authentication object. Useful when the header is\n                            // "Authorization: Bearer <value>".\n                            // Replaces .headerName(...) (see above)\n                            //\n                            // apiKey.authenticationConverter(request -> {\n                            //     var key = extractKey(request);\n                            //     return ApiKeyAuthenticationToken.unauthenticated(key);\n                            // });\n                        }\n                )\n                .build();\n    }\n\n    /**\n     * Provide a repository of {@link ApiKeyEntity}.\n     */\n    private ApiKeyEntityRepository<ApiKeyEntityImpl>
1 apiKeyRepository() {\n        var apiKey = ApiKeyEntityImpl.builder()\n                .name("test api key")\n                .id("api01")\n                .secret("mycustomapikey")\n                .build();\n\n        return new InMemoryApiKeyEntityRepository<>(List.of(apiKey));\n    }\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:["\u4f7f\u7528\u6b64\u914d\u7f6e\uff0c\u60a8\u53ef\u4ee5\u4f7f\u7528\u6807\u5934 ",(0,r.jsx)(n.code,{children:"X-API-key: api01.mycustomapikey"})," \u8c03\u7528 MCP \u670d\u52a1\u5668\u3002"]}),"\n",(0,r.jsx)(n.h3,{id:"known-limitations",children:"Known Limitations"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"\u91cd\u8981\u63d0\u793a\uff1a"})}),"\n"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["\u4e0d\u652f\u6301\u5df2\u5f03\u7528\u7684 SSE \u4f20\u8f93\u3002\u4f7f\u7528 ",(0,r.jsx)(n.a,{href:"mcp-streamable-http-server-boot-starter-docs",children:"Streamable HTTP"})," \u6216 ",(0,r.jsx)(n.a,{href:"mcp-stateless-server-boot-starter-docs",children:"stateless transport"}),"\u3002"]}),"\n",(0,r.jsx)(n.li,{children:"\u4e0d\u652f\u6301\u57fa\u4e8e WebFlux \u7684\u670d\u52a1\u5668\u3002"}),"\n",(0,r.jsx)(n.li,{children:"\u4e0d\u652f\u6301 Opaque tokens\u3002\u4f7f\u7528 JWT\u3002"}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"mcp-client-security",children:"MCP Client Security"}),"\n",(0,r.jsxs)(n.p,{children:["MCP Client Security \u6a21\u5757\u4e3a ",(0,r.jsx)(n.a,{href:"mcp-client-boot-starter-docs",children:"Spring AI's MCP clients"})," \u63d0\u4f9b OAuth 2.0 \u652f\u6301\uff0c\u652f\u6301\u57fa\u4e8e HttpClient \u7684\u5ba2\u6237\u7aef\uff08\u6765\u81ea ",(0,r.jsx)(n.code,{children:"spring-ai-starter-mcp-client"}),"\uff09\u548c\u57fa\u4e8e WebClient \u7684\u5ba2\u6237\u7aef\uff08\u6765\u81ea ",(0,r.jsx)(n.code,{children:"spring-ai-starter-mcp-client-webflux"}),"\uff09\u3002"]}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"\u91cd\u8981\u63d0\u793a\uff1a"})," \u6b64\u6a21\u5757\u4ec5\u652f\u6301 ",(0,r.jsx)(n.code,{children:"McpSyncClient"}),"\u3002"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"dependencies-1",children:"Dependencies"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Maven:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-xml",children:"<dependency>\n    <groupId>org.springaicommunity</groupId>\n    <artifactId>mcp-client-security</artifactId>\n</dependency>\n"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Gradle:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-groovy",children:"implementation 'org.springaicommunity:mcp-client-security'\n"})}),"\n",(0,r.jsx)(n.h3,{id:"authorization-flows",children:"Authorization Flows"}),"\n",(0,r.jsx)(n.p,{children:"\u4e09\u79cd OAuth 2.0 \u6d41\u7a0b\u53ef\u7528\u4e8e\u83b7\u53d6\u4ee4\u724c\uff1a"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.em,{children:"Authorization Code Flow"})," - \u7528\u4e8e\u7528\u6237\u7ea7\u6743\u9650\uff0c\u5f53\u6bcf\u4e2a MCP \u8bf7\u6c42\u90fd\u5728\u7528\u6237\u8bf7\u6c42\u7684\u4e0a\u4e0b\u6587\u4e2d\u8fdb\u884c\u65f6"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.em,{children:"Client Credentials Flow"})," - \u7528\u4e8e\u673a\u5668\u5230\u673a\u5668\u7684\u7528\u4f8b\uff0c\u5176\u4e2d\u6ca1\u6709\u4eba\u5728\u5faa\u73af\u4e2d"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.em,{children:"Hybrid Flow"})," - \u7ed3\u5408\u4e24\u79cd\u6d41\u7a0b\uff0c\u9002\u7528\u4e8e\u67d0\u4e9b\u64cd\u4f5c\uff08\u5982 ",(0,r.jsx)(n.code,{children:"initialize"})," \u6216 ",(0,r.jsx)(n.code,{children:"tools/list"}),"\uff09\u5728\u6ca1\u6709\u7528\u6237\u5728\u573a\u7684\u60c5\u51b5\u4e0b\u53d1\u751f\uff0c\u4f46\u5de5\u5177\u8c03\u7528\u9700\u8981\u7528\u6237\u7ea7\u6743\u9650\u7684\u573a\u666f"]}),"\n"]}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"\u63d0\u793a\uff1a"})," \u5f53\u60a8\u6709\u7528\u6237\u7ea7\u6743\u9650\u4e14\u6240\u6709 MCP \u8bf7\u6c42\u90fd\u5728\u7528\u6237\u4e0a\u4e0b\u6587\u4e2d\u53d1\u751f\u65f6\uff0c\u4f7f\u7528 authorization code flow\u3002\u5bf9\u4e8e\u673a\u5668\u5230\u673a\u5668\u7684\u901a\u4fe1\uff0c\u4f7f\u7528 client credentials\u3002\u5f53\u4f7f\u7528 Spring Boot \u5c5e\u6027\u8fdb\u884c MCP \u5ba2\u6237\u7aef\u914d\u7f6e\u65f6\uff0c\u4f7f\u7528 hybrid flow\uff0c\u56e0\u4e3a\u5de5\u5177\u53d1\u73b0\u5728\u542f\u52a8\u
165f6\u53d1\u751f\uff0c\u6ca1\u6709\u7528\u6237\u5728\u573a\u3002"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"common-setup",children:"Common Setup"}),"\n",(0,r.jsxs)(n.p,{children:["\u5bf9\u4e8e\u6240\u6709\u6d41\u7a0b\uff0c\u5728 ",(0,r.jsx)(n.code,{children:"application.properties"})," \u4e2d\u6fc0\u6d3b Spring Security \u7684 OAuth2 \u5ba2\u6237\u7aef\u652f\u6301\uff1a"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-properties",children:"# Ensure MCP clients are sync\nspring.ai.mcp.client.type=SYNC\n\n# For authorization_code or hybrid flow\nspring.security.oauth2.client.registration.authserver.client-id=<THE CLIENT ID>\nspring.security.oauth2.client.registration.authserver.client-secret=<THE CLIENT SECRET>\nspring.security.oauth2.client.registration.authserver.authorization-grant-type=authorization_code\nspring.security.oauth2.client.registration.authserver.provider=authserver\n\n# For client_credentials or hybrid flow\nspring.security.oauth2.client.registration.authserver-client-credentials.client-id=<THE CLIENT ID>\nspring.security.oauth2.client.registration.authserver-client-credentials.client-secret=<THE CLIENT SECRET>\nspring.security.oauth2.client.registration.authserver-client-credentials.authorization-grant-type=client_credentials\nspring.security.oauth2.client.registration.authserver-client-credentials.provider=authserver\n\n# Authorization server configuration\nspring.security.oauth2.client.provider.authserver.issuer-uri=<THE ISSUER URI OF YOUR AUTH SERVER>\n"})}),"\n",(0,r.jsx)(n.p,{children:"\u7136\u540e\uff0c\u521b\u5efa\u4e00\u4e2a\u914d\u7f6e\u7c7b\u6765\u6fc0\u6d3b OAuth2 \u5ba2\u6237\u7aef\u529f\u80fd\uff1a"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:"@Configuration\n@EnableWebSecurity\nclass SecurityConfiguration {\n\n    @Bean\n    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n        return http\n                // in this example, the client app has no security on its endpoints\n                .authorizeHttpRequests(auth -> auth.anyRequest().permitAll())\n                // turn on OAuth2 support\n                .oauth2Client(Customizer.withDefaults())\n                .build();\n    }\n}\n"})}),"\n",(0,r.jsx)(n.h3,{id:"httpclient-based-clients",children:"HttpClient-Based Clients"}),"\n",(0,r.jsxs)(n.p,{children:["\u4f7f\u7528 ",(0,r.jsx)(n.code,{children:"spring-ai-starter-mcp-client"})," \u65f6\uff0c\u914d\u7f6e ",(0,r.jsx)(n.code,{children:"McpSyncHttpClientRequestCustomizer"})," bean\uff1a"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:'@Configuration\nclass McpConfiguration {\n\n    @Bean\n    McpSyncClientCustomizer syncClientCustomizer() {\n        return (name, syncSpec) ->\n                syncSpec.transportContextProvider(\n                        new AuthenticationMcpTransportContextProvider()\n                );\n    }\n\n    @Bean\n    McpSyncHttpClientRequestCustomizer requestCustomizer(\n            OAuth2AuthorizedClientManager clientManager\n    ) {\n        // The clientRegistration name, "authserver",\n        // must match the name in application.properties\n        return new OAuth2AuthorizationCodeSyncHttpRequestCustomizer(\n                clientManager,\n                "authserver"\n        );\n    }\n}\n'})}),"\n",(0,r.jsx)(n.p,{children:"\u53ef\u7528\u7684 customizers\uff1a"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"OAuth2AuthorizationCodeSyncHttpRequestCustomizer"})," - \u7528\u4e8e authorization code flow"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"OAuth2ClientCredentialsSyncHttpRequestCustomizer"})," - \u7528\u4e8e client credentials flow"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"OAuth2HybridSyncHttpRequestCustomizer"})," - \u7528\u4e8e hybrid flow"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"webclient-based-clients",children:"WebClient-Based Clients"}),"\n",(0,r.jsxs)(n.p,{children:["\u4f7f\u7528 ",(0,r.jsx)(n.code,{children:"spring-ai-starter-mcp-client-webflux"})," \u65f6\uff0c\u4f7f\u7528 MCP ",(0,r.jsx)(n.code,{children:"ExchangeFilterFunction"}
1)," \u914d\u7f6e ",(0,r.jsx)(n.code,{children:"WebClient.Builder"}),"\uff1a"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:'@Configuration\nclass McpConfiguration {\n\n    @Bean\n    McpSyncClientCustomizer syncClientCustomizer() {\n        return (name, syncSpec) ->\n                syncSpec.transportContextProvider(\n                        new AuthenticationMcpTransportContextProvider()\n                );\n    }\n\n    @Bean\n    WebClient.Builder mcpWebClientBuilder(OAuth2AuthorizedClientManager clientManager) {\n        // The clientRegistration name, "authserver", must match the name in application.properties\n        return WebClient.builder().filter(\n                new McpOAuth2AuthorizationCodeExchangeFilterFunction(\n                        clientManager,\n                        "authserver"\n                )\n        );\n    }\n}\n'})}),"\n",(0,r.jsx)(n.p,{children:"\u53ef\u7528\u7684\u8fc7\u6ee4\u5668\u51fd\u6570\uff1a"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"McpOAuth2AuthorizationCodeExchangeFilterFunction"})," - \u7528\u4e8e authorization code flow"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"McpOAuth2ClientCredentialsExchangeFilterFunction"})," - \u7528\u4e8e client credentials flow"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"McpOAuth2HybridExchangeFilterFunction"})," - \u7528\u4e8e hybrid flow"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"working-around-spring-ai-autoconfiguration",children:"Working Around Spring AI Autoconfiguration"}),"\n",(0,r.jsx)(n.p,{children:"Spring AI \u7684\u81ea\u52a8\u914d\u7f6e\u5728\u542f\u52a8\u65f6\u521d\u59cb\u5316 MCP \u5ba2\u6237\u7aef\uff0c\u8fd9\u53ef\u80fd\u4f1a\u5bfc\u81f4\u57fa\u4e8e\u7528\u6237\u8eab\u4efd\u9a8c\u8bc1\u7684\u95ee\u9898\u3002\u8981\u907f\u514d\u8fd9\u79cd\u60c5\u51b5\uff1a"}),"\n",(0,r.jsx)(n.h4,{id:"option-1-disable-tool-auto-configuration",children:"Option 1: Disable @Tool Auto-configuration"}),"\n",(0,r.jsxs)(n.p,{children:["\u901a\u8fc7\u53d1\u5e03\u4e00\u4e2a\u7a7a\u7684 ",(0,r.jsx)(n.code,{children:"ToolCallbackResolver"})," bean \u6765\u7981\u7528 Spring AI \u7684 ",(0,r.jsx)(n.code,{children:"@Tool"})," \u81ea\u52a8\u914d\u7f6e\uff1a"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:"@Configuration\npublic class McpConfiguration {\n\n    @Bean\n    ToolCallbackResolver resolver() {\n        return new StaticToolCallbackResolver(List.of());\n    }\n}\n"})}),"\n",(0,r.jsx)(n.h4,{id:"option-2-programmatic-client-configuration",children:"Option 2: Programmatic Client Configuration"}),"\n",(0,r.jsx)(n.p,{children:"\u4f7f\u7528\u7a0b\u5e8f\u5316\u914d\u7f6e\u800c\u4e0d\u662f Spring Boot \u5c5e\u6027\u6765\u914d\u7f6e MCP \u5ba2\u6237\u7aef\u3002\u5bf9\u4e8e\u57fa\u4e8e HttpClient \u7684\u5ba2\u6237\u7aef\uff1a"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:'@Bean\nMcpSyncClient client(\n        ObjectMapper objectMapper,\n        McpSyncHttpClientRequestCustomizer requestCustomizer,\n        McpClientCommonProperties commonProps\n) {\n    var transport = HttpClientStreamableHttpTransport.builder(mcpServerUrl)\n            .clientBuilder(HttpClient.newBuilder())\n            .jsonMapper(new JacksonMcpJsonMapper(objectMapper))\n            .httpRequestCustomizer(requestCustomizer)\n            .build();\n\n    var clientInfo = new McpSchema.Implementation("client-name", commonProps.getVersion());\n\n    return McpClient.sync(transport)\n            .clientInfo(clientInfo)\n            .requestTimeout(commonProps.getRequestTimeout())\n            .transportContextProvider(new AuthenticationMcpTransportContextProvider())\n            .build();\n}\n'})}),"\n",(0,r.jsx)(n.p,{children:"\u5bf9\u4e8e\u57fa\u4e8e WebClient \u7684\u5ba2\u6237\u7aef\uff1a"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:'@Bean\nMcpSyncClient client(\n        WebClient.Builder mcpWebClientBuilder,\n        ObjectMapper objectMapper,\n        McpClientCommonProperties commonProperties\n) {\n    var builder = mcpWebClientBuilder.baseUrl(mcpServerUrl);\n    var transport = WebClientStreamableHttpTransport.builder(builder)\n            .jsonMapper(new JacksonMcpJsonMapper(objectMapper))\n            .build();\n\n    var clientInfo = new McpSchema.Implementation("clientName", commonProperties.getVersion());\n\n    return McpClient.sync(transport)\n            .clientInfo(clientInfo)\n            .requestTimeout(commonProperties.getRequestTimeout())\n            .transportContextProvider(new AuthenticationMcpTransportContextProvider())\n            .build();\n}\n'})}),"\n",(0,r.jsx)(n.p,{children:"\u7136\u540e\u5c06\u5ba2\u6237\u7aef\u6dfb\u52a0\u5230\u60a8\u7684\u804a\u5929\u5ba2\u6237\u7aef\uff1a"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:'var chatResponse = chatClient.prompt("Prompt the LLM to do the thing")\n        .toolCallbacks(new SyncMcpToolCallbackProvider(mcpClient1, mcpClient2, mcpClient3))\n        .call()\n        .content();\n'})}),"\n",(0,r.jsx)(n.h3,{id:"known-limitations-1",children:"Known Limitations"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"\u91cd\u8981\u63d0\u793a\uff1a"})}),"\n"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"\u4e0d\u652f\u6301 Spring WebFlux \u670d\u52a1\u5668\u3002"}),"\n",(0,r.jsx)(n.li,{children:"Spring AI \u81ea\u52a8\u914d\u7f6e\u5728\u5e94\u7528\u542f\u52a8\u
165f6\u521d\u59cb\u5316 MCP \u5ba2\u6237\u7aef\uff0c\u9700\u8981\u9488\u5bf9\u57fa\u4e8e\u7528\u6237\u8eab\u4efd\u9a8c\u8bc1\u7684\u53d8\u901a\u65b9\u6cd5\u3002"}),"\n",(0,r.jsxs)(n.li,{children:["\u4e0e\u670d\u52a1\u5668\u6a21\u5757\u4e0d\u540c\uff0c\u5ba2\u6237\u7aef\u5b9e\u73b0\u652f\u6301\u4f7f\u7528 ",(0,r.jsx)(n.code,{children:"HttpClient"})," \u548c ",(0,r.jsx)(n.code,{children:"WebClient"})," \u7684 SSE \u4f20\u8f93\u3002"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"mcp-authorization-server",children:"MCP Authorization Server"}),"\n",(0,r.jsxs)(n.p,{children:["MCP Authorization Server \u6a21\u5757\u589e\u5f3a\u4e86 ",(0,r.jsx)(n.a,{href:"https://docs.spring.io/spring-security/reference/7.0/servlet/oauth2/authorization-server/index.html",children:"Spring Security's OAuth 2.0 Authorization Server"}),"\uff0c\u5177\u6709\u4e0e ",(0,r.jsx)(n.a,{href:"https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization",children:"MCP authorization spec"})," \u76f8\u5173\u7684\u529f\u80fd\uff0c\u4f8b\u5982 Dynamic Client Registration \u548c Resource Indicators\u3002"]}),"\n",(0,r.jsx)(n.h3,{id:"dependencies-2",children:"Dependencies"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Maven:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-xml",children:"<dependency>\n    <groupId>org.springaicommunity</groupId>\n    <artifactId>mcp-authorization-server</artifactId>\n</dependency>\n"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Gradle:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-groovy",children:"implementation 'org.springaicommunity:mcp-authorization-server'\n"})}),"\n",(0,r.jsx)(n.h3,{id:"configuration",children:"Configuration"}),"\n",(0,r.jsxs)(n.p,{children:["\u5728 ",(0,r.jsx)(n.code,{children:"application.yml"})," \u4e2d\u914d\u7f6e\u6388\u6743\u670d\u52a1\u5668\uff1a"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'spring:\n  application:\n    name: sample-authorization-server\n  security:\n    oauth2:\n      authorizationserver:\n        client:\n          default-client:\n            token:\n              access-token-time-to-live: 1h\n            registration:\n              client-id: "default-client"\n              client-secret: "{noop}default-secret"\n              client-authentication-methods:\n                - "client_secret_basic"\n                - "none"\n              authorization-grant-types:\n                - "authorization_code"\n                - "client_credentials"\n              redirect-uris:\n                - "http://127.0.0.1:8080/authorize/oauth2/code/authserver"\n                - "http://localhost:8080/authorize/oauth2/code/authserver"\n                # mcp-inspector\n                - "http://localhost:6274/oauth/callback"\n                # claude code\n                - "https://claude.ai/api/mcp/auth_callback"\n    user:\n      # A single user, named "user"\n      name: user\n      password: password\n\nserver:\n  servlet:\n    session:\n      cookie:\n        # Override the default cookie name (JSESSIONID).\n        # This allows running multiple Spring apps on localhost, and they\'ll each have their own cookie.\n        # Otherwise, since the cookies do not take the port into account, they are confused.\n        name: MCP_AUTHORIZATION_SERVER_SESSIONID\n'})}),"\n",(0,r.jsx)(n.p,{children:"\u7136\u540e\u4f7f\u7528\u5b89\u5168\u8fc7\u6ee4\u5668\u94fe\u6fc0\u6d3b\u6388\u6743\u670d\u52a1\u5668\u529f\u80fd\uff1a"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:'@Bean\nSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n    return http\n            // all requests must be authenticated\n            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())\n            // enable authorization server customizations\n            .with(McpAuthorizationServerConfigurer.mcpAuthorizationServer(), withDefaults())\n            // enable form-based login, for user "user"/"password"\n            .formLogin(withDefaults())\n            .build();\n}\n'})}),"\n",(0,r.jsx)(n.h3,{id:"known-limitations-2",children:"Known Limitations"}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"\u91cd\u8981\u63d0\u793a\uff1a"})}),"\n"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"\u4e0d\u652f\u6301 Spring WebFlux \u670d\u52a1\u5668\u3002"}),"\n",(0,r.jsxs)(n.li,{children:["\u6bcf\u4e2a\u5ba2\u6237\u7aef\u652f\u6301\u6240\u6709 ",(0,r.jsx)(n.code,{children:"resource"})," \u6807\u8bc6\u7b26\u3002"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"samples-and-integrations",children:"Samples and Integrations"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/spring-ai-community/mcp-security/tree/main/samples",children:"samples directory"})," \u5305\u542b\u6b64\u9879\u76ee\u4e2d\u6240\u6709\u6a21\u5757\u7684\u5de5\u4f5c\u793a\u4f8b\uff0c\u5305\u62ec\u96c6\u62
110\u6d4b\u8bd5\u3002"]}),"\n",(0,r.jsxs)(n.p,{children:["\u4f7f\u7528 ",(0,r.jsx)(n.code,{children:"mcp-server-security"})," \u548c\u652f\u6301\u7684 ",(0,r.jsx)(n.code,{children:"mcp-authorization-server"}),"\uff0c\u60a8\u53ef\u4ee5\u4e0e\u4ee5\u4e0b\u5185\u5bb9\u96c6\u6210\uff1a"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Cursor"}),"\n",(0,r.jsx)(n.li,{children:"Claude Desktop"}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"https://modelcontextprotocol.io/docs/tools/inspector",children:"MCP Inspector"})}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"\u6ce8\u610f\uff1a"})," \u4f7f\u7528 ",(0,r.jsx)(n.a,{href:"https://modelcontextprotocol.io/docs/tools/inspector",children:"MCP Inspector"})," \u65f6\uff0c\u60a8\u53ef\u80fd\u9700\u8981\u7981\u7528 CSRF \u548c CORS \u4fdd\u62a4\u3002"]}),"\n",(0,r.jsx)(n.h2,{id:"additional-resources",children:"Additional Resources"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization#communication-security",children:"MCP Authorization Specification"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"https://github.com/spring-ai-community/mcp-security",children:"MCP Security GitHub Repository"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"https://github.com/spring-ai-community/mcp-security/tree/main/samples",children:"Sample Applications"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization",children:"MCP Authorization Specification"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/index.html",children:"Spring Security OAuth 2.0 Resource Server"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"https://docs.spring.io/spring-security/reference/servlet/oauth2/client/index.html",children:"Spring Security OAuth 2.0 Client"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"https://docs.spring.io/spring-security/reference/7.0/servlet/oauth2/authorization-server/index.html",children:"Spring Authorization Server"})}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(u,{...e})}):u(e)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.