【问题标题】:Spring returns 401 instead of 200 statusSpring 返回 401 而不是 200 状态
【发布时间】: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;

标签: java spring-boot jwt


【解决方案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 参数。
    猜你喜欢
    • 2021-04-04
    • 2021-07-28
    • 1970-01-01
    • 2016-06-22
    • 2018-05-28
    • 1970-01-01
    • 2016-02-10
    • 1970-01-01
    • 2015-03-13
    相关资源
    最近更新 更多