在 Spring 中为单页应用程序实现注销



我正在使用Java Web Token(JWT(在我的Web应用程序中进行身份验证。我想创建一个 /logout Rest 端点,该端点删除客户端上的 JSession cookie,使会话无效并执行任何其他必要的操作。

前端是用 React 编写的 SPA。

我在扩展WebSecurityConfigurerAdapterWebSecurityConfig 类中有以下 configure 方法:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf().disable()
        .authorizeRequests()
            .antMatchers("/login").permitAll()
        .and()
        .authorizeRequests()
            .antMatchers("/signup").permitAll()
        .and()
        .authorizeRequests()
            .anyRequest().authenticated()
        .and()
        .addFilterBefore(
            new JWTAuthenticationFilter(userDetailsServiceBean()),
            UsernamePasswordAuthenticationFilter.class);
}

我有以下代码,但它返回404 Not found错误,路径设置为 /login .我想得到一个HTTP response 200和一些清理。我该怎么办?

.logout().logoutUrl("/logout").logoutSuccessHandler(logoutHandler).logoutSuccessUrl("/login").invalidateHttpSession(true)

经过一些研究,我发现如果我想使用无状态的Rest API设计,我不应该使用任何cookie,包括JSESSIONID。

因此,我将代码更改为:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        .csrf().disable()
        .authorizeRequests()
            .anyRequest().authenticated()
        .and()
        .addFilterBefore(
            new JWTAuthenticationFilter(userDetailsServiceBean()),
            UsernamePasswordAuthenticationFilter.class);
}

你能先尝试这样的登录请求吗?首先在 WebSecurityConfig 中添加 JWTLoginFilter

粘贴示例项目中的代码:

http.csrf().disable() // disable csrf for our requests.
    .authorizeRequests()
    .antMatchers("/").permitAll()
    .antMatchers(HttpMethod.POST,"/login").permitAll()
    .anyRequest().authenticated()
    .and()
    // We filter the api/login requests
    .addFilterBefore(new JWTLoginFilter("/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class)
    // And filter other requests to check the presence of JWT in header
    .addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
    .addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class);

如果您的前端和后端位于同一台服务器上,则不需要 CORSFilter。

还可以在下面找到JWTLoginFilter类

   public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter 
    {
    private TokenAuthenticationService tokenAuthenticationService;
    public JWTLoginFilter(String url, AuthenticationManager authenticationManager) {
     super(new AntPathRequestMatcher(url));
     setAuthenticationManager(authenticationManager);
     tokenAuthenticationService = new TokenAuthenticationService();
}
@Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
throws AuthenticationException, IOException, ServletException {
    AccountCredentials credentials = new ObjectMapper().readValue(httpServletRequest.getInputStream(), AccountCredentials.class);
    UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(credentials.getUsername(), credentials.getPassword());
    return getAuthenticationManager().authenticate(token);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication)
throws IOException, ServletException {
    String name = authentication.getName();
    tokenAuthenticationService.addAuthentication(response, name);
  }
}

AccountCredential类是简单的POJO类,包含两个字段用户名和密码,我用它来接收请求。

另请注意,我们使用的用户名密码身份验证过滤器在登录请求"用户名"和"密码"中需要两个字段。喜欢{"username":"user1","password":"secret1"}