Spring 返回 401 而不是 200 状态

Posted

技术标签:

【中文标题】Spring 返回 401 而不是 200 状态【英文标题】:Spring returns 401 instead of 200 status 【发布时间】:2019-03-23 01:57:48 【问题描述】:

作为学习 Spring 的一部分,我编写了一个应用程序,但是当我测试身份验证时,我收到 401 状态而不是 200。我正在寻找错误的原因,在我看来 Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(email, password)); 行返回 null .但是,我不知道如何解决这个问题。

@Component
public class AuthenticationServiceUsernamePassword 
    private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationServiceUsernamePassword.class);
    @Autowired
    @Qualifier("customAuthenticationManager")
    private AuthenticationManager authenticationManager;
    @Autowired
    private TokenManager tokenManager;

    public SignedJWT authenticate(final String email, final String password)
        try 
            Authentication authentication = authenticationManager
                .authenticate(new UsernamePasswordAuthenticationToken(email, password));        
            SecurityContextHolder.getContext()
                .setAuthentication(authentication);

            if (authentication.getPrincipal() != null) 
                return tokenManager.createNewToken((PrincipalUser) authentication.getPrincipal());
            
         catch (AuthenticationException authException) 
            LOGGER.debug("Authentication failed for user:\"" + email + ".\" Reason " + authException.getClass());
        

        return null;
    

控制器

@Controller
public class AuthController 
    @Value("$jwt.result")
    private String defaultTokenResponse;
    @Autowired
    private AuthenticationServiceUsernamePassword authUserPassword;

    @RequestMapping(value = "/authentication", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<String> authenticate(String email, String password, HttpServletRequest request,
                                           HttpServletResponse response)
        if (email != null && password != null)
            try 
                SignedJWT token = authUserPassword.authenticate(email, password);

                if (token != null)
                    return new ResponseEntity<String>(String.format(defaultTokenResponse, token.serialize()),
                        HttpStatus.OK);
                 else 
                    return new ResponseEntity<String>(HttpStatus.UNAUTHORIZED);
                
             catch (BadCredentialsException badCredentials) 
                return new ResponseEntity<String>(HttpStatus.UNAUTHORIZED);
            
         else 
            return new ResponseEntity<String>(HttpStatus.UNAUTHORIZED);
        
    

测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
@WebAppConfiguration
public class ConnectControllerTest 
    protected MockMvc mockMvc;
    @Autowired
    private WebApplicationContext context;
    @Autowired
    private Filter springSecurityFilterChain;

    @Before
    public void setup() 
        mockMvc = MockMvcBuilders.webAppContextSetup(context)
            .addFilters(springSecurityFilterChain)
            .defaultRequest(get("/"))
            .build();
    

    @Test
    public void shouldTestAuthentication() throws Exception 
        String result = mockMvc.perform(post("/authentication")
            .param("email", "user@test.pl").param("password", "password"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.token").exists())
            .andReturn().getResponse().getContentAsString();
    

如果有人对此处的其余代码感兴趣,请点击链接:repository

【问题讨论】:

你得到一个空指针异常吗?还是您的 catch 子句中的日志消息已写入您的日志? @SvenHakvoort 以上都不是。我刚收到java.lang.AssertionError: Status expected:&lt;200&gt; but was:&lt;401&gt; 【参考方案1】:

您已向单元测试用例添加参数。不是有效载荷。如果添加有效负载,则必须使用 @RequestBody。在这里你必须使用@RequestParam。在控制器中使用以下代码:

public ResponseEntity<String> authenticate(@RequestParam String email, @RequestParam String password, HttpServletRequest request,HttpServletResponse response)
 ..do stuff

这将工作..!

【讨论】:

【参考方案2】:

好的。第一要务

EmailPassword 传递正确

问题来了

public SignedJWT authenticate(final String email, final String password)
        try 
            System.out.println("test => "+email+" : "+password);
            Authentication authentication = authenticationManager
                    .authenticate(new UsernamePasswordAuthenticationToken(email, password));
            SecurityContextHolder.getContext().setAuthentication(authentication);

            if (authentication.getPrincipal() != null) 
                return tokenManager.createNewToken((PrincipalUser) authentication.getPrincipal());
            
         catch (AuthenticationException authException) 
            authException.printStackTrace();
            LOGGER.debug("Authentication failed for user:\"" + email + ".\" Reason " + authException.getClass());
        
        System.out.println("return nulll");
        return null;
    

如果你运行你的测试用例,它会抛出以下错误

org.springframework.security.authentication.BadCredentialsException: Bad credentials
    at org.springframework.security.authentication.dao.DaoAuthenticationProvider.additionalAuthenticationChecks(DaoAuthenticationProvider.java:98)
    at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:166)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:199)
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.authenticate(WebSecurityConfigurerAdapter.java:504)
    at com.github.springjwt.security.jwt.service.AuthenticationServiceUsernamePassword.authenticate(AuthenticationServiceUsernamePassword.java:30)
    at com.github.springjwt.web.api.controller.AuthController.authenticate(AuthController.java:31)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImp

这意味着您的测试用例的 usernamepasswordUserRepository 类用户详细信息不匹配

在您的UserRepository 班级中 您需要设置正确的散列密码及其设置为 null 的盐值。

当您调用authenticate.authenticate 时,它会在内部获取密码和哈希并将其与传递的值匹配。

如果值不匹配,则会引发错误凭据错误

PS:我是在本地运行你的代码后得出这个结论

【讨论】:

如果我将当前的UserRepository 更改为public interface UserRepository extends JpaRepository&lt;User UUID&gt; 会怎样?在这种情况下我还需要设置散列密码吗? 是的,你可以。底线是密码传递,哈希密码应该在使用盐逻辑编码后匹配。否则你会得到 404。 我更改了我的 UserRepository 并使用匹配的数据创建了我的测试用户,但仍然得到 401。我可能做错了什么,但我不知道是什么。我所做的所有更改都在存储库中可见。【参考方案3】:

您的代码大部分是正确的,但在您的控制器定义中出现了错误:

public ResponseEntity<String> authenticate(String email, String password, HttpServletRequest request,
                                           HttpServletResponse response)

默认情况下,Spring 不知道如何检索电子邮件和密码变量。您需要使用 @RequestBody 注释对这些进行注释,例如:

public ResponseEntity<String> authenticate(@RequestBody String email, @RequestBody String password, HttpServletRequest request,
                                           HttpServletResponse response)

但是,如果您的整个控制器将用作 API,您还可以使用 @RestController 注释您的控制器,这告诉 spring 对每个参数使用 @RequestBody,并且每个方法都应该使用 @ResponseBody 注释,这将告诉 spring应该将返回值转换为 JSON(这对 API 来说很方便)。

参考资料:

Spring’s RequestBody and ResponseBody Annotations

【讨论】:

我用@RestController 注释了我的控制器,但它并没有解决问题。依次使用@RequestBody 注解将状态更改为400 使用@RequestParam 而不是@RequestBody。 @VishwPatel,这是不正确的。 @RequestParam 用于 GET 变量,而不用于 POST 参数。

以上是关于Spring 返回 401 而不是 200 状态的主要内容,如果未能解决你的问题,请参考以下文章

NSURLConnection 返回错误而不是响应 401

使用错误凭据的 Django 登录返回 200 而不是 401

为啥 Spring Boot Actuator 返回 404 而不是 401?

对于未经身份验证的请求,Keycloak spring boot 返回 403 而不是 401

Spring Boot POST 请求返回 http 状态 405“方法不允许”而不是 HTTP 状态 404

Apache shiro,返回状态 401 而不是重定向到 url