1

我是 Spring 新手,我尝试使用 Spring Boot 和 Spring Security 创建一个安全的休息应用程序。我现在正在寻找几周的解决方案......

我在我的 pom.xml 中使用 Spring Boots 嵌入式 Web 容器(Tomcat)和 spring-boot-starter-parent 1.2.6.RELEASE。

我的端点:

  • /login(验证)
  • /application/{id}(我想确保的一些服务)

我在 application.properties 中配置了我的 servlet 路径,如下所示:

server.servletPath: /embedded

所以我期待我的服务,例如//localhost/embedded/login

好的,现在问题是:如果我在没有安全性的情况下运行应用程序一切都很好,我可以调用 http//localhost/embedded/application 并获得答案。如果我现在像这样添加我的安全配置:

import javax.servlet.ServletContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebMvcSecurity
@EnableScheduling
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private TokenAuthenticationService tokenAuthenticationService;

    @Value("${server.servletPath}")
    private String servletPath;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
         http.authorizeRequests().antMatchers("/hello/**", "/login").permitAll()
            .antMatchers("/application/**").authenticated().and()
            .addFilterBefore(new TokenAuthenticationFilter(tokenAuthenticationService), UsernamePasswordAuthenticationFilter.class);  
            http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .httpBasic().disable();
    }

}

当运行应用程序时 //localhost/application/{id} 是安全的,而不是我所期望的 //localhost/embedded/application/{id} 。由于某种原因,此处忽略了 servlet 路径。我坚持“好的,所以我只需手动添加 servlet 路径”并使其看起来像这样:

...antMatchers(servletPath+"/application/**").authenticated()...

这适用于我的应用程序。但是,我也使用 MockMvc 来测试我的服务,并且由于某种原因,servlet 路径已正确添加到匹配器中。因此,如果我开始测试,安全过滤器被映射到//localhost/embedded/embedded/application/{id},而控制器本身仍然被映射到//localhost/embedded/application/{id}这非常烦人......我在这里查看了http://spring.io/blog/2013/07/03/spring -security-java-config-preview-web-security/并认为我可以通过使用AbstractSecurityWebApplicationInitializer而不是解决问题,SpringBootServletInitializer但它没有任何改变。顺便说一句,这是我的应用程序类:

com.sebn.gsd.springservertemplate.service.security.WebSecurityConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application extends SpringBootServletInitializer {

    public static void main(String[] args) {
        System.out.println("Run from main");
        SpringApplication.run(applicationClass, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(applicationClass, WebSecurityConfig.class);
    }

    private static Class<Application> applicationClass = Application.class;

}

我认为它application.properties不包含任何更有趣的信息。要完成这是我的 MockMvc 测试类:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sebn.gsd.springservertemplate.service.api.LoginData;
import com.sebn.gsd.springservertemplate.service.security.Session_model;
import com.sebn.gsd.springservertemplate.service.security.WebSecurityConfig;
import java.util.Arrays;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.hamcrest.Matchers.notNullValue;
import org.junit.Assert;
import org.junit.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.web.servlet.ResultActions;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {Application.class, WebSecurityConfig.class })
@WebAppConfiguration
@ActiveProfiles(profiles = "development")
public class SecurityTests {

    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext webApplicationContext;

    private HttpMessageConverter mappingJackson2HttpMessageConverter;
    private ObjectMapper o = new ObjectMapper();

    @Autowired
    private FilterChainProxy filterChainProxy;

    @Value("${server.servletPath}")
    private String servletPath;

    @Before
    public void setup() throws Exception {
        this.mockMvc = webAppContextSetup(webApplicationContext).addFilter(filterChainProxy).build();
    }

    @Test
    public void testLoginSecurity() throws Exception {
        int applicationId = 1;
        // Try to access secured api
        ResultActions actions = mockMvc.perform(get("/application/" + applicationId))
                .andDo(MockMvcResultHandlers.print())
                .andExpect(status().isForbidden());
        //login
        String username = "user";
        LoginData loginData = new LoginData();
        loginData.setPasswordBase64("23j4235jk26=");
        loginData.setUsername(username);
        actions = mockMvc.perform(post("/login").content(o.writeValueAsString(loginData)).contentType(MediaType.APPLICATION_JSON_VALUE))
                .andDo(MockMvcResultHandlers.print())
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.login", Matchers.equalTo(username)))
                .andExpect(jsonPath("$.token", notNullValue()))
                .andExpect(jsonPath("$.expirationDate", notNullValue()));
         Session_model session = getResponseContentAsJavaObject(actions.andReturn().getResponse(), Session_model.class);
         Assert.assertNotNull(session);
        // Try to access secured api again 
        actions = mockMvc.perform(get("/application/" + applicationId).header("X-AUTH-TOKEN", session.getToken()))
                .andDo(MockMvcResultHandlers.print())
                .andExpect(status().isOk());
    }

    private <T> T getResponseContentAsJavaObject(MockHttpServletResponse response, Class<T> returnType) throws Exception{
        return o.readValue(response.getContentAsString(), returnType);
    }

    @Autowired
    void setConverters(HttpMessageConverter<?>[] converters) {

        this.mappingJackson2HttpMessageConverter = Arrays.asList(converters).stream().filter(
                hmc -> hmc instanceof MappingJackson2HttpMessageConverter).findAny().get();

        Assert.assertNotNull("the JSON message converter must not be null",
                this.mappingJackson2HttpMessageConverter);
    }
}

也许我误解了什么。我希望你能告诉我。

4

1 回答 1

3

概括

简而言之,您需要映射 Spring Security 以使用包含 servlet 路径。此外,您需要在MockMvc请求中包含 servlet 路径。为此,您可以执行以下操作:

@Before
public void setup() throws Exception {
    this.mockMvc = webAppContextSetup(webApplicationContext)
           // ADD LINE BELOW!!!
           .defaultRequest(get("/").servletPath(servletPath))
           .addFilter(filterChainProxy)
           .build();
}

详细回复

基于上下文根的 Spring 安全匹配

Spring Security 的匹配器是相对于应用程序的上下文根的。它与 servlet 路径无关。这是故意的,因为它应该保护所有的 servlet(不仅仅是 Spring MVC)。如果它与 servlet 相关,请考虑以下事项:

servlet1-path/abc -> Only users with role ROLE_ADMIN can access

servlet2-path/abc -> Only users with role ROLE_USER can access

如果 Spring Security 相对于 servlet 路径,您将如何区分这两个映射?

在模拟 MVC 中工作

Spring Security 在 MockMvc 中工作的原因是,当您使用 MockMvc 时,不再考虑 servlet 路径。您的请求被发送到 Spring Security 和 Spring MVC,就好像 servlet 路径是“”一样。要解决此问题,您需要在请求中包含 servlet 路径。

@Before
public void setup() throws Exception {
    this.mockMvc = webAppContextSetup(webApplicationContext)
           // ADD LINE BELOW!!!
           .defaultRequest(get("/").servletPath(servletPath))
           .addFilter(filterChainProxy)
           .build();
}
于 2015-10-02T16:20:45.210 回答