当前位置: 首页 > 知识库问答 >
问题:

如何使用Spring Security性与基于数据库的用户测试JWT身份验证?

东方嘉木
2023-03-14

我有一个带有JWT Authentication的Spring Boot 2.1.6 webapp。呼叫流程如下:

  1. 用户输入用户名和密码并向 /authenticate发送POST请求
  2. 过滤器正在监视这个URL(setFilterProcessesUrl),当请求到来时,它会对密码进行哈希处理并根据存储在DB中的哈希进行检查
  3. 如果匹配,并且用户未被锁定,它会创建一个具有用户名和授予角色的JWT,并返回它作为响应
  4. 用户必须在所有进一步的请求中包含此JWT

此外,在WebSecurity配置适配器中禁用了CSRF。

解决方案本身运行良好,但我也必须创建单元测试。我最终得到了以下测试用例:

@RunWith(SpringRunner.class)
@WebMvcTest
@ContextConfiguration(classes = { ConfigReaderMock.class })
public class ControllerSecurityTest {

    private static final String VALID_USERNAME = "username";
    private static final String VALID_PASSWORD = "password";

    @Autowired
    private MockMvc mockMvc;

    private String createAuthenticationBody(String username, String passwordHash) {
        return "username=" + URLEncoder.encode(username, StandardCharsets.UTF_8) + "&password="
                + URLEncoder.encode(passwordHash, StandardCharsets.UTF_8);
    }

    @Test
    public void testValidLogin() throws Exception {
        MvcResult result = mockMvc
                .perform(MockMvcRequestBuilders.post("/authenticate")
                        .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                        .content(createAuthenticationBody(VALID_USERNAME, VALID_PASSWORD)).accept(MediaType.ALL))
                .andExpect(status().isOk()).andReturn();

        String authHeader = result.getResponse().getHeader(SecurityConstants.TOKEN_HEADER);

        mockMvc.perform(MockMvcRequestBuilders.get("/main?" + SecurityConstants.TOKEN_QUERY_PARAM + "="
                + URLEncoder.encode(authHeader, StandardCharsets.UTF_8))).andExpect(status().isOk());
    }
}

我期望的是,服务器接受提供的用户名和密码,并返回JWT,我可以在后续请求中使用它来访问下一页(同样在前端实现)。相反,我从身份验证过滤器中获得HTTP 403:

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /authenticate
       Parameters = {username=[username], password=[password]}
          Headers = [Content-Type:"application/x-www-form-urlencoded", Accept:"*/*"]
             Body = <no character encoding set>
    Session Attrs = {org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN=org.springframework.security.web.csrf.DefaultCsrfToken@4ac0fdc7}

Handler:
             Type = null

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 403
    Error message = Forbidden
          Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = null
             Body =
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

我注意到它出于某种原因在会话属性中发送了一个CSRF令牌。进一步检查日志,我可以看到belo消息:

2019-07-29 08:09:17,438 DEBUG o.s.b.f.s.DefaultSingletonBeanRegistry [main] Creating shared instance of singleton bean 'org.springframework.boot.autoconfigure.security.servlet.WebSecurityEnablerConfiguration'
2019-07-29 08:09:17,443 DEBUG o.s.s.c.a.a.c.AuthenticationConfiguration$EnableGlobalAuthenticationAutowiredConfigurer [main] Eagerly initializing {org.springframework.boot.autoconfigure.security.servlet.WebSecurityEnablerConfiguration=org.springframework.boot.autoconfigure.security.servlet.WebSecurityEnablerConfiguration$$EnhancerBySpringCGLIB$$236da03c@4e68aede}
2019-07-29 08:09:17,444 DEBUG o.s.b.f.s.DefaultSingletonBeanRegistry [main] Creating shared instance of singleton bean 'inMemoryUserDetailsManager'
2019-07-29 08:09:17,445 DEBUG o.s.b.f.s.DefaultSingletonBeanRegistry [main] Creating shared instance of singleton bean 'org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration'
2019-07-29 08:09:17,454 DEBUG o.s.b.f.s.DefaultSingletonBeanRegistry [main] Creating shared instance of singleton bean 'spring.security-org.springframework.boot.autoconfigure.security.SecurityProperties'
2019-07-29 08:09:17,457 DEBUG o.s.b.f.s.ConstructorResolver [main] Autowiring by type from bean name 'inMemoryUserDetailsManager' via factory method to bean named 'spring.security-org.springframework.boot.autoconfigure.security.SecurityProperties'
2019-07-29 08:09:17,462 INFO o.s.b.a.s.s.UserDetailsServiceAutoConfiguration [main] 

Using generated security password: 963b2bac-d953-4793-a8cd-b3f81586823e

...

2019-07-29 08:09:17,783 DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository [main] No HttpSession currently exists
2019-07-29 08:09:17,784 DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository [main] No SecurityContext was available from the HttpSession: null. A new one will be created.
2019-07-29 08:09:17,794 DEBUG o.s.s.w.c.CsrfFilter [main] Invalid CSRF token found for http://localhost/authenticate
2019-07-29 08:09:17,795 DEBUG o.s.s.w.h.w.HstsHeaderWriter [main] Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@1c15a6aa
2019-07-29 08:09:17,796 DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository$SaveToSessionResponseWrapper [main] SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2019-07-29 08:09:17,799 DEBUG o.s.s.w.c.SecurityContextPersistenceFilter [main] SecurityContextHolder now cleared, as request processing completed

所以看起来Spring Security正在创建它自己的安全配置,而不是使用我创建的类,扩展WebSecurityConfigrerAdapter。问题是,为什么?我如何强制它使用我的安全配置,因为我在数据库登录时依赖它?

更新:添加了WebSecurity配置适配器

@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AICAuthenticationService authenticationService;

    @Autowired
    private AICUserDetailsService aicUserDetailsService;

    @Autowired
    private AICLogoutSuccessHandler aicLogoutSuccessHandler;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .cors()
                .and()
                .authorizeRequests()
                        .antMatchers("/resources/**", "/login", "/").permitAll()
                        .anyRequest().authenticated()
                .and()
                        .addFilter(new JwtAuthenticationFilter(authenticationManager()))
                        .addFilter(new JwtAuthorizationFilter(authenticationManager()))
                .sessionManagement()
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .logout()
                        .logoutUrl("/logout")
                        .logoutSuccessHandler(aicLogoutSuccessHandler)
                        .invalidateHttpSession(true)
                        .deleteCookies("JSESSIONID", "error");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(aicUserDetailsService);
    }

    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return authenticationService;
    }

    @Bean
    public AuthenticationManager custromAuthenticationManager() throws Exception {
        return authenticationManager();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(aicUserDetailsService);
    }

共有1个答案

陶弘业
2023-03-14

我能够使用TestRestTemplate完成它,如下所示:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class ControllerSecurityTest {

    private static final String VALID_USERNAME = "username";
    private static final String VALID_PASSWORD = "password";

    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void testValidLogin() throws Exception {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        headers.setAccept(Arrays.asList(MediaType.ALL));

        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("username", VALID_USERNAME);
        map.add("password", VALID_PASSWORD);

        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);

        ResponseEntity<String> tokenResponse = restTemplate
                .postForEntity("http://localhost:" + port + "/authenticate", request, String.class);

        assertEquals(200, tokenResponse.getStatusCodeValue());

        String authHeader = tokenResponse.getHeaders().getFirst(SecurityConstants.TOKEN_HEADER);

        assertNotNull(authHeader);

        ResponseEntity<String> mainResponse = restTemplate.getForEntity("http://localhost:" + port + "/main?"
                + SecurityConstants.TOKEN_QUERY_PARAM + "=" + URLEncoder.encode(authHeader, StandardCharsets.UTF_8),
                String.class);

        assertEquals(200, mainResponse.getStatusCodeValue());
    }
}
 类似资料:
  • 我读了一些关于“JWT vs Cookie”的帖子,但它们只会让我更加困惑…… > 我想澄清一下,当人们谈论“基于令牌的身份验证与cookie”时,这里的cookie仅指会话cookie?我的理解是,cookie就像一个介质,它可以用来实现基于令牌的身份验证(在客户端存储可以识别登录用户的东西)或者基于会话的身份验证(在客户端存储与服务器端会话信息匹配的常量) 为什么我们需要JSON web令牌?

  • 在身份验证等情况下,与会话相比,使用JWTs有什么优势? 它是作为独立方法使用还是在会话中使用?

  • 我正在使用SpringBoot开发具有微服务架构的Rest Backend。为了保护endpoint,我使用了JWT令牌机制。我正在使用Zuul API网关。 如果请求需要权限(来自JWT的角色),它将被转发到正确的微服务。Zuul api网关的“WebSecurityConfigrerAdapter”如下。 这样,我必须在这个类中编写每个请求授权部分。因此,我希望使用方法级安全性,即“Enabl

  • 现在,使用这些标记,我在api文件中创建了一个函数,但它将<code>0 函数代码: 那么,如何使用WordPress hook<code>wp_get_current_user()获取登录用户数据呢? 其次,我如何使< code > jwt-auth/v1/token API动态获取用户名和密码? P. S我在htacceess文件中添加了ReWriteCond和ReWriteRur,并且在我的

  • 问题内容: 是否存在node.js的现有用户身份验证库?特别是,我正在寻找可以对用户进行密码身份验证的东西(使用自定义后端身份验证数据库),并将该用户与会话相关联。 在编写身份验证库之前,我认为我会看看人们是否知道现有的库。通过Google搜索找不到任何明显的内容。 -Shreyas 问题答案: 看起来连接中间件的connect-auth插件正是我所需要的:http : //wiki.github

  • LDAP的基础是什么?如果在配置期间没有给出任何基数。 我必须从基于web的应用程序验证/验证用户,并且我有java代码。 但是我需要为此建立基础(变量),我已经让另一个团队告诉我基础,他们说我们没有在LDAP上定制任何东西。LDAP的默认基数是什么?