Spring 安全登录不起作用,没有返回 JSESSIONID cookie 并且重定向失败

wer*_*erw 5 java spring spring-security reactjs axios

我正在尝试编写一个具有独立后端(使用 Spring Boot 和 Spring Security 进行登录)和前端(ReactJS)的应用程序。现在,我正在努力在成功登录后访问安全端点。

我想要实现的目标:对安全端点进行 GET,例如“/books/all”。如果用户未登录,则返回 401。如果前端收到 401,则对 '/login' 进行 POST。然后我想要成功登录并能够成功获取到“/books/all”。

什么不起作用:最后一部分。我正在向 '/login' 发送 POST 并收到 200 GET。然后我再次调用“/books/all”并收到 GET 401。此外,我不再收到让我担心的 JSESSIONID cookie。

我的问题:如何解决这种行为?我相信它已连接到 JSESSIONID(服务器不发送有关用户成功登录的信息?)。

在前端,我使用 axios。

  axios.get('http://localhost:8080/rest/book/anna/all')
        .then(response => {
            console.log('response rebuild');
            console.log(response);
            if (response.status === 401 && response.request.responseURL === 'http://localhost:8080/login') {
                axios.post('http://localhost:8080/login', 'username=c&password=d')
                    .then(response => {
                        console.log('response 2');
                        console.log(response);
                    })
                    .catch(error => {
                        console.log('error');
                        console.log(error);
                    })
            }
        })
        .catch(error => {
            console.log('error 2');
            console.log(error);
            axios.post('http://localhost:8080/login', 'username=c&password=d')
                .then(response => {
                    console.log('response 2');
                    console.log(response);
                    axios.get('http://localhost:8080/rest/book/anna/all')
                        .then(response => {
                            console.log('response 3');
                            console.log(response);
                        })
                        .catch(error => {
                            console.log('error 3');
                            console.log(error);
                        })
                })
                .catch(error => {
                    console.log('error');
                    console.log(error);
                })
        });
Run Code Online (Sandbox Code Playgroud)

请注意,我知道这段代码质量很低;检查登录后重定向是否有效只是暂时的。

安全配置文件

package com.shareabook.security;

import com.shareabook.repository.UsersRepository;
import com.shareabook.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;

@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@EnableJpaRepositories(basePackageClasses = UsersRepository.class)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService userDetailsService;
    @Autowired
    private RESTAuthenticationEntryPoint restAuthenticationEntryPoint;
    @Autowired
    private RESTAuthenticationSuccessHandler restAuthenticationSuccessHandler;
    @Autowired
    private RESTAuthenticationFailureHandler restAuthenticationFailureHandler;

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .cors()
                .and()
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("**/anna/**").authenticated()
                .anyRequest().permitAll();
        http.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint);
        http.formLogin().successHandler(restAuthenticationSuccessHandler);
        http.formLogin().failureHandler(restAuthenticationFailureHandler);
//                .and()
//                .formLogin().permitAll();

        http
                .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessUrl("/rest/author/all");
    }

    private PasswordEncoder getPasswordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return charSequence.toString();
            }

            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return true;
            }
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

RESTAuthenticationEntryPoint.java

@Component
public class RESTAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
            throws IOException, ServletException {

        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
    }
}
Run Code Online (Sandbox Code Playgroud)

RESTAuthenticationFailureHandler.java

@Component
public class RESTAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException,
            ServletException {

        super.onAuthenticationFailure(request, response, exception);
    }
}
Run Code Online (Sandbox Code Playgroud)

RESTAuthenticationSuccessfulHandler.java

@Component
public class RESTAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {

//        clearAuthenticationAttributes(request);
        HttpSession session = request.getSession();
        session.setAttribute("username", "c");
        response.setStatus(HttpServletResponse.SC_OK);
    }
}
Run Code Online (Sandbox Code Playgroud)

图书控制器.java

@RestController
@RequestMapping("/rest/book")
public class BookController {

    @CrossOrigin(origins = "http://localhost:8888")
    @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
    @RequestMapping(value = "/anna/all", method = RequestMethod.GET)
    public List<String> securedHello() {
        List<String> word = new ArrayList<>();
        word.add("all");
        System.out.print(word);
        return word;
    }
}
Run Code Online (Sandbox Code Playgroud)

小智 7

您已在应用程序上启用表单身份验证。当您将帖子发送到登录页面时,Spring 会对您的请求进行身份验证,并默认在用户会话上缓存身份验证。

稍后您可以将请求发送到绑定到同一会话的服务器,而无需额外的身份验证信息。但您需要根据您的请求提供会话信息。通常这是通过在您的下一个请求中提供JSESSIONIDcookie 来完成的。浏览器会自动为您执行此操作,但仅在页面重新加载后才执行。如果您停留在同一页面,则只有最初为该页面加载的 cookie 才会被发送回服务器。

对于 SPA 应用程序,我建议使用基于令牌的身份验证而不是表单。您必须首先登录并收到响应的令牌。接下来,您必须Authorization为每个请求提供标头,提供令牌作为身份验证信息(通常采用 form Bearer <token>