【问题标题】:Spring Test & Security: How to mock authentication?Spring Test & Security:如何模拟身份验证?
【发布时间】:2013-02-18 15:43:01
【问题描述】:

我试图弄清楚如何对我的控制器的 URL 是否得到适当保护进行单元测试。以防万一有人更改并意外删除了安全设置。

我的控制器方法如下所示:

@RequestMapping("/api/v1/resource/test") 
@Secured("ROLE_USER")
public @ResonseBody String test() {
    return "test";
}

我这样设置了一个 WebTestEnvironment:

import javax.annotation.Resource;
import javax.naming.NamingException;
import javax.sql.DataSource;

import org.junit.Before;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration({ 
        "file:src/main/webapp/WEB-INF/spring/security.xml",
        "file:src/main/webapp/WEB-INF/spring/applicationContext.xml",
        "file:src/main/webapp/WEB-INF/spring/servlet-context.xml" })
public class WebappTestEnvironment2 {

    @Resource
    private FilterChainProxy springSecurityFilterChain;

    @Autowired
    @Qualifier("databaseUserService")
    protected UserDetailsService userDetailsService;

    @Autowired
    private WebApplicationContext wac;

    @Autowired
    protected DataSource dataSource;

    protected MockMvc mockMvc;

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    protected UsernamePasswordAuthenticationToken getPrincipal(String username) {

        UserDetails user = this.userDetailsService.loadUserByUsername(username);

        UsernamePasswordAuthenticationToken authentication = 
                new UsernamePasswordAuthenticationToken(
                        user, 
                        user.getPassword(), 
                        user.getAuthorities());

        return authentication;
    }

    @Before
    public void setupMockMvc() throws NamingException {

        // setup mock MVC
        this.mockMvc = MockMvcBuilders
                .webAppContextSetup(this.wac)
                .addFilters(this.springSecurityFilterChain)
                .build();
    }
}

在我的实际测试中,我尝试做这样的事情:

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Test;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;

import eu.ubicon.webapp.test.WebappTestEnvironment;

public class CopyOfClaimTest extends WebappTestEnvironment {

    @Test
    public void signedIn() throws Exception {

        UsernamePasswordAuthenticationToken principal = 
                this.getPrincipal("test1");

        SecurityContextHolder.getContext().setAuthentication(principal);        

        super.mockMvc
            .perform(
                    get("/api/v1/resource/test")
//                    .principal(principal)
                    .session(session))
            .andExpect(status().isOk());
    }

}

我在这里捡到的:

但是,如果仔细观察,这仅在不向 URL 发送实际请求时才有帮助,而仅在功能级别测试服务时才有用。在我的情况下,抛出了“拒绝访问”异常:

org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:83) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:206) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
    at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:60) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) ~[spring-aop-3.2.1.RELEASE.jar:3.2.1.RELEASE]
        ...

以下两条日志消息值得注意,基本上是说没有用户通过身份验证,表明设置Principal 不起作用,或者它被覆盖了。

14:20:34.454 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Secure object: ReflectiveMethodInvocation: public java.util.List test.TestController.test(); target is of class [test.TestController]; Attributes: [ROLE_USER]
14:20:34.454 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS

【问题讨论】:

标签: spring security model-view-controller testing junit


【解决方案1】:

寻找答案我找不到任何既简单又灵活的方法,然后我找到了Spring Security Reference,我意识到有近乎完美的解决方案。 AOP 解决方案通常是最适合测试的解决方案,Spring 为其提供了@WithMockUser@WithUserDetails@WithSecurityContext,在这个神器中:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <version>4.2.2.RELEASE</version>
    <scope>test</scope>
</dependency>

在大多数情况下,@WithUserDetails 收集了我需要的灵活性和功能。

@WithUserDetails 是如何工作的?

基本上,您只需要创建一个自定义UserDetailsService,其中包含您要测试的所有可能的用户配置文件。例如

@TestConfiguration
public class SpringSecurityWebAuxTestConfig {

    @Bean
    @Primary
    public UserDetailsService userDetailsService() {
        User basicUser = new UserImpl("Basic User", "user@company.com", "password");
        UserActive basicActiveUser = new UserActive(basicUser, Arrays.asList(
                new SimpleGrantedAuthority("ROLE_USER"),
                new SimpleGrantedAuthority("PERM_FOO_READ")
        ));

        User managerUser = new UserImpl("Manager User", "manager@company.com", "password");
        UserActive managerActiveUser = new UserActive(managerUser, Arrays.asList(
                new SimpleGrantedAuthority("ROLE_MANAGER"),
                new SimpleGrantedAuthority("PERM_FOO_READ"),
                new SimpleGrantedAuthority("PERM_FOO_WRITE"),
                new SimpleGrantedAuthority("PERM_FOO_MANAGE")
        ));

        return new InMemoryUserDetailsManager(Arrays.asList(
                basicActiveUser, managerActiveUser
        ));
    }
}

现在我们的用户已经准备好了,想象一下我们要测试这个控制器功能的访问控制:

@RestController
@RequestMapping("/foo")
public class FooController {

    @Secured("ROLE_MANAGER")
    @GetMapping("/salute")
    public String saluteYourManager(@AuthenticationPrincipal User activeUser)
    {
        return String.format("Hi %s. Foo salutes you!", activeUser.getUsername());
    }
}

这里我们有一个 get mapped function 到路由 /foo/salute 并且我们正在使用 @Secured 注释测试基于角色的安全性,尽管您可以测试@PreAuthorize@PostAuthorize 也是如此。 让我们创建两个测试,一个检查有效用户是否可以看到这个敬礼响应,另一个检查它是否真的被禁止。

@RunWith(SpringRunner.class)
@SpringBootTest(
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
        classes = SpringSecurityWebAuxTestConfig.class
)
@AutoConfigureMockMvc
public class WebApplicationSecurityTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    @WithUserDetails("manager@company.com")
    public void givenManagerUser_whenGetFooSalute_thenOk() throws Exception
    {
        mockMvc.perform(MockMvcRequestBuilders.get("/foo/salute")
                .accept(MediaType.ALL))
                .andExpect(status().isOk())
                .andExpect(content().string(containsString("manager@company.com")));
    }

    @Test
    @WithUserDetails("user@company.com")
    public void givenBasicUser_whenGetFooSalute_thenForbidden() throws Exception
    {
        mockMvc.perform(MockMvcRequestBuilders.get("/foo/salute")
                .accept(MediaType.ALL))
                .andExpect(status().isForbidden());
    }
}

如您所见,我们导入了SpringSecurityWebAuxTestConfig 来为我们的用户提供测试。每一个都在其对应的测试用例上使用,只需使用简单的注解,减少代码和复杂性。

更好地使用 @WithMockUser 以获得更简单的基于角色的安全性

如您所见,@WithUserDetails 具有您大多数应用程序所需的所有灵活性。它允许您使用具有任何 GrantedAuthority 的自定义用户,例如角色或权限。但是,如果您只是使用角色,测试会更容易,并且您可以避免构建自定义UserDetailsService。在这种情况下,请使用@WithMockUser 指定用户、密码和角色的简单组合。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@WithSecurityContext(
    factory = WithMockUserSecurityContextFactory.class
)
public @interface WithMockUser {
    String value() default "user";

    String username() default "";

    String[] roles() default {"USER"};

    String password() default "password";
}

注释定义了一个非常基本的用户的默认值。在我们的例子中,我们正在测试的路由只要求经过身份验证的用户是经理,我们可以使用SpringSecurityWebAuxTestConfig 退出并执行此操作。

@Test
@WithMockUser(roles = "MANAGER")
public void givenManagerUser_whenGetFooSalute_thenOk() throws Exception
{
    mockMvc.perform(MockMvcRequestBuilders.get("/foo/salute")
            .accept(MediaType.ALL))
            .andExpect(status().isOk())
            .andExpect(content().string(containsString("user")));
}

请注意,现在我们得到的是 @WithMockUser 提供的默认用户,而不是用户 ma​​nager@company.comuser;但这并不重要,因为我们真正关心的是他的角色:ROLE_MANAGER

结论

如您所见,使用 @WithUserDetails@WithMockUser 之类的注释,我们可以在不同的经过身份验证的用户场景之间切换,而无需构建与我们的架构不同的类来进行简单的测试。它还建议您查看@WithSecurityContext 如何工作以获得更大的灵活性。

【讨论】:

  • 如何模拟多个用户?比如第一个请求是tom,第二个是jerry?
  • 您可以创建一个函数,您的测试是使用 tom 并创建另一个具有相同逻辑的测试并使用 Jerry 进行测试。每个测试都会有一个特定的结果,因此会有不同的断言,如果测试失败,它将通过其名称告诉您哪个用户/角色没有工作。请记住,在一个请求中,用户只能是一个,因此在一个请求中指定多个用户是没有意义的。
  • 对不起,我的意思是这样的示例场景:我们测试,tom 创建了一篇秘密文章,然后 jerry 尝试阅读它,而 jerry 不应该看到它(因为它是秘密的)。所以在这种情况下,它是 one 单元测试...
  • 它看起来很像答案中给出的BasicUserManager User 场景。关键概念是,我们实际上关心他们的角色,而不是关心用户,但是位于同一个单元测试中的每个测试实际上代表不同的查询。由不同用户(具有不同角色)对同一端点完成。
【解决方案2】:

自 Spring 4.0+ 起,最好的解决方案是使用 @WithMockUser 注释测试方法

@Test
@WithMockUser(username = "user1", password = "pwd", roles = "USER")
public void mytest1() throws Exception {
    mockMvc.perform(get("/someApi"))
        .andExpect(status().isOk());
}

记得在你的项目中添加以下依赖

'org.springframework.security:spring-security-test:4.2.3.RELEASE'

【讨论】:

  • 春天真是太棒了。谢谢
  • 好答案。此外 - 你不需要使用 mockMvc,但如果你正在使用例如来自 springframework.data 的 PagingAndSortingRepository - 您可以直接从存储库中调用方法(使用 EL @PreAuthorize(......) 进行注释)
  • 谢谢,@WithMockUser(roles = "YOUR_ROLE") 会变得更好
  • 如何添加 orgId 以及
  • 只是一个小小的提醒,你不必显式地将版本添加到你的 spring 依赖项中
【解决方案3】:

事实证明,作为 Spring Security 过滤器链的一部分的 SecurityContextPersistenceFilter 总是重置我的 SecurityContext,我将其设置为调用 SecurityContextHolder.getContext().setAuthentication(principal)(或使用 .principal(principal) 方法)。此过滤器将SecurityContextHolder 中的SecurityContext 设置为来自SecurityContextRepository OVERWRITINGSecurityContext,这是我之前设置的。默认情况下,存储库是 HttpSessionSecurityContextRepositoryHttpSessionSecurityContextRepository 检查给定的HttpRequest 并尝试访问相应的HttpSession。如果存在,它将尝试从HttpSession 中读取SecurityContext。如果失败,存储库会生成一个空的SecurityContext

因此,我的解决方案是将HttpSession 与包含SecurityContext 的请求一起传递:

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Test;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;

import eu.ubicon.webapp.test.WebappTestEnvironment;

public class Test extends WebappTestEnvironment {

    public static class MockSecurityContext implements SecurityContext {

        private static final long serialVersionUID = -1386535243513362694L;

        private Authentication authentication;

        public MockSecurityContext(Authentication authentication) {
            this.authentication = authentication;
        }

        @Override
        public Authentication getAuthentication() {
            return this.authentication;
        }

        @Override
        public void setAuthentication(Authentication authentication) {
            this.authentication = authentication;
        }
    }

    @Test
    public void signedIn() throws Exception {

        UsernamePasswordAuthenticationToken principal = 
                this.getPrincipal("test1");

        MockHttpSession session = new MockHttpSession();
        session.setAttribute(
                HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, 
                new MockSecurityContext(principal));


        super.mockMvc
            .perform(
                    get("/api/v1/resource/test")
                    .session(session))
            .andExpect(status().isOk());
    }
}

【讨论】:

  • 我们尚未添加对 Spring Security 的官方支持。见jira.springsource.org/browse/SEC-2015github.com/SpringSource/spring-test-mvc/blob/master/src/test/…中指定了它的外观的轮廓@
  • 我不认为创建一个 Authentication 对象并添加一个具有相应属性的会话是那么糟糕。你认为这是一个有效的“变通办法”吗?另一方面,直接支持当然很棒。看起来很整洁。感谢您的链接!
  • 很好的解决方案。为我工作!受保护方法getPrincipal() 的命名只是一个小问题,我认为这有点误导。理想情况下,它应该被命名为getAuthentication()。同样,在您的 signedIn() 测试中,局部变量应命名为 authauthentication 而不是 principal
  • 什么是“getPrincipal("test1") ¿?你能解释一下它在哪里吗?提前谢谢
  • @user2992476 它可能返回一个 UsernamePasswordAuthenticationToken 类型的对象。或者,您创建 GrantedAuthority 并构造此对象。
【解决方案4】:

在 pom.xml 中添加:

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <version>4.0.0.RC2</version>
    </dependency>

并使用org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors 进行授权请求。 请参阅https://github.com/rwinch/spring-security-test-blog 的示例用法 (https://jira.spring.io/browse/SEC-2592)。

更新:

4.0.0.RC2 适用于 spring-security 3.x。 对于 spring-security 4 spring-security-test 成为 spring-security 的一部分(http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test,版本相同)。

设置已更改:http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test-mockmvc

public void setup() {
    mvc = MockMvcBuilders
            .webAppContextSetup(context)
            .apply(springSecurity())  
            .build();
}

基本身份验证示例:http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#testing-http-basic-authentication

【讨论】:

  • 这也解决了我在尝试通过登录安全过滤器登录时收到 404 的问题。谢谢!
  • 您好,在 GKislin 提到的测试中。我收到以下错误“身份验证失败 UserDetailsS​​ervice 返回 null,这是违反接口合同”。请有任何建议。 final AuthenticationRequest auth = new AuthenticationRequest(); auth.setUsername(userId); auth.setPassword(密码); mockMvc.perform(post("/api/auth/").content(json(auth)).contentType(MediaType.APPLICATION_JSON));
【解决方案5】:

这里有一个示例供那些想要使用 Base64 基本身份验证测试 Spring MockMvc 安全配置的人使用。

String basicDigestHeaderValue = "Basic " + new String(Base64.encodeBase64(("<username>:<password>").getBytes()));
this.mockMvc.perform(get("</get/url>").header("Authorization", basicDigestHeaderValue).accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk());

Maven 依赖

    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.3</version>
    </dependency>

【讨论】:

    【解决方案6】:

    简答:

    @Autowired
    private WebApplicationContext webApplicationContext;
    
    @Autowired
    private Filter springSecurityFilterChain;
    
    @Before
    public void setUp() throws Exception {
        final MockHttpServletRequestBuilder defaultRequestBuilder = get("/dummy-path");
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext)
                .defaultRequest(defaultRequestBuilder)
                .alwaysDo(result -> setSessionBackOnRequestBuilder(defaultRequestBuilder, result.getRequest()))
                .apply(springSecurity(springSecurityFilterChain))
                .build();
    }
    
    private MockHttpServletRequest setSessionBackOnRequestBuilder(final MockHttpServletRequestBuilder requestBuilder,
                                                                 final MockHttpServletRequest request) {
        requestBuilder.session((MockHttpSession) request.getSession());
        return request;
    }
    

    在 spring 安全测试中执行 formLogin 后,您的每个请求都将被自动调用为登录用户。

    长答案:

    检查这个解决方案(答案是春季 4):How to login a user with spring 3.2 new mvc testing

    【讨论】:

      【解决方案7】:

      避免在测试中使用 SecurityContextHolder 的选项:

      • 选项 1:使用模拟 - 我的意思是模拟 SecurityContextHolder 使用一些模拟库 - EasyMock 例如
      • 选项 2:在某些服务的代码中包装调用 SecurityContextHolder.get... - 例如在 SecurityServiceImpl 中使用方法 getCurrentPrincipal 实现 SecurityService 接口,然后在您的测试中,您可以简单地创建模拟实现这个接口返回所需的主体而不访问SecurityContextHolder

      【讨论】:

      • 嗯,也许我没有了解全部情况。我的问题是 SecurityContextPersistenceFilter 使用来自 HttpSessionSecurityContextRepository 的 SecurityContext 替换了 SecurityContext,而后者又从相应的 HttpSession 中读取 SecurityContext。因此使用会话的解决方案。关于对 SecurityContextHolder 的调用:我编辑了我的答案,以便不再使用对 SecurityContextHolder 的调用。但也没有引入任何包装或额外的模拟库。您认为这是一个更好的解决方案吗?
      • 抱歉,我不明白您在寻找什么,我无法提供比您想出的解决方案更好的答案 - 这似乎是一个不错的选择。
      • 好的,谢谢。我现在将接受我的建议作为解决方案。
      【解决方案8】:

      虽然答案很晚。但这对我有用,而且可能有用。

      在使用 Spring Security 和 mockMvc 时,您只需使用 @WithMockUser 注释,就像提到的其他注释一样。

      Spring security 还提供了另一个名为 @WithAnonymousUser 的注解,用于测试未经身份验证的请求。但是,您应该在这里小心。你会期待 401,但默认情况下我得到 403 Forbidden Error。在实际场景中,当你运行实际服务时,它会被重定向,最终你会得到正确的 401 响应码。匿名请求使用这个注解。

      您也可以考虑省略注释并保持未经授权。但这通常会引发正确的异常(如 AuthenticationException),但如果处理正确(如果您使用自定义处理程序),您将获得正确的状态代码。我曾经为此得到500。所以寻找调试器中引发的异常,检查是否处理正确并返回正确的状态码。

      【讨论】:

        【解决方案9】:

        在您的测试包上创建一个类TestUserDetailsImpl

        @Service
        @Primary
        @Profile("test")
        public class TestUserDetailsImpl implements UserDetailsService {
            public static final String API_USER = "apiuser@example.com";
        
            private User getAdminUser() {
                User user = new User();
                user.setUsername(API_USER);
        
                SimpleGrantedAuthority role = new SimpleGrantedAuthority("ROLE_API_USER");
                user.setAuthorities(Collections.singletonList(role));
        
                return user;
            }
        
            @Override
            public UserDetails loadUserByUsername(String username) 
                                                 throws UsernameNotFoundException {
                if (Objects.equals(username, ADMIN_USERNAME))
                    return getAdminUser();
                throw new UsernameNotFoundException(username);
            }
        }
        
        

        休息端点:

        @GetMapping("/invoice")
        @Secured("ROLE_API_USER")
        public Page<InvoiceDTO> getInvoices(){
           ...
        }
        

        测试端点:

        @Test
        @WithUserDetails("apiuser@example.com")
        public void testApi() throws Exception {
             ...
        }
        

        【讨论】:

          【解决方案10】:

          当使用MockMvcBuilders.webAppContextSetup(wac).addFilters(...)而不是springSecurityFilterChain(更具体地说是SecurityContextPersistenceFilter)将接管并删除@WithMockUser准备的SecurityContext(很傻);发生这种情况是因为SecurityContextPersistenceFilter 试图从找不到的HttpSession 中“恢复”SecurityContext。好吧,使用下面定义的这个简单的AutoStoreSecurityContextHttpFilter,它将负责将@WithMockUser 准备好的SecurityContext 放入HttpSession,以便以后SecurityContextPersistenceFilter 能够找到它。

          @ContextConfiguration(...) // the issue doesn't occur when using @SpringBootTest
          public class SomeTest {
              @Autowired
              private Filter springSecurityFilterChain;
              private MockMvc mockMvc;
          
              @BeforeEach
              void setup(WebApplicationContext wac) {
                  this.mockMvc = MockMvcBuilders.webAppContextSetup(wac)
                          .addFilters(new AutoStoreSecurityContextHttpFilter(), springSecurityFilterChain).build();
              }
          
              @WithMockUser
              @Test
              void allowAccessToAuthenticated() {
                  ...
              }
          }
          
          // don't use this Filter in production because it's only intended for tests, to solve the 
          // @WithMockUser & springSecurityFilterChain (more specifically SecurityContextPersistenceFilter) "misunderstandings"
          public class AutoStoreSecurityContextHttpFilter extends HttpFilter {
              protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
                  req.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
                  super.doFilter(req, res, chain);
              }
          }
          

          【讨论】:

            猜你喜欢
            • 2020-03-15
            • 2020-10-29
            • 2022-01-14
            • 2022-01-15
            • 2014-02-26
            • 2018-08-31
            • 2019-08-04
            • 1970-01-01
            • 2013-01-15
            相关资源
            最近更新 更多