fix(auth): require authentication for skill downloads

- Remove download endpoints from permitAll list in SecurityConfig
- Add authentication checks to download tests
- Add login redirect for unauthenticated download attempts in frontend

This prevents unauthorized access to skill package downloads while
maintaining public access to skill metadata and file listings.
This commit is contained in:
wowo-zZ 2026-03-14 16:54:10 +08:00
parent b886bd8aed
commit 56e7baed15
3 changed files with 24 additions and 6 deletions

View file

@ -1,6 +1,8 @@
package com.iflytek.skillhub.controller.portal;
import static org.mockito.BDDMockito.given;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -52,7 +54,9 @@ class SkillControllerDownloadTest {
"https://download.example/presigned"
));
mockMvc.perform(get("/api/v1/skills/global/demo-skill/versions/1.0.0/download"))
mockMvc.perform(get("/api/v1/skills/global/demo-skill/versions/1.0.0/download")
.with(user("test-user"))
.with(csrf()))
.andExpect(status().isFound())
.andExpect(header().string("Location", "https://download.example/presigned"));
}
@ -68,8 +72,21 @@ class SkillControllerDownloadTest {
null
));
mockMvc.perform(get("/api/v1/skills/global/demo-skill/versions/1.0.0/download"))
mockMvc.perform(get("/api/v1/skills/global/demo-skill/versions/1.0.0/download")
.with(user("test-user"))
.with(csrf()))
.andExpect(status().isOk())
.andExpect(header().string("Content-Disposition", "attachment; filename=\"demo-skill-1.0.0.zip\""));
}
@Test
void downloadVersion_requiresAuthentication() throws Exception {
mockMvc.perform(get("/api/v1/skills/global/demo-skill/versions/1.0.0/download")
.with(csrf()))
.andDo(result -> {
System.out.println("Status: " + result.getResponse().getStatus());
System.out.println("Body: " + result.getResponse().getContentAsString());
})
.andExpect(status().isUnauthorized());
}
}

View file

@ -96,12 +96,9 @@ public class SecurityConfig {
"/api/v1/skills/*/*/versions/*/files",
"/api/v1/skills/*/*/versions/*/file",
"/api/v1/skills/*/*/resolve",
"/api/v1/skills/*/*/download",
"/api/v1/skills/*/*/versions/*/download",
"/api/v1/skills/*/*/tags",
"/api/v1/skills/*/*/tags/*/files",
"/api/v1/skills/*/*/tags/*/file",
"/api/v1/skills/*/*/tags/*/download"
"/api/v1/skills/*/*/tags/*/file"
).permitAll()
.requestMatchers(HttpMethod.GET, "/api/v1/namespaces", "/api/v1/namespaces/*").permitAll()
.requestMatchers("/api/v1/admin/**").hasAnyRole("SUPER_ADMIN", "SKILL_ADMIN", "USER_ADMIN", "AUDITOR")

View file

@ -56,6 +56,10 @@ export function SkillDetailPage() {
})
const handleDownload = () => {
if (!user) {
requireLogin()
return
}
if (!latestVersion) {
return
}