Java 登录到Spring MVC应用程序时的自定义响应

Java 登录到Spring MVC应用程序时的自定义响应,java,spring,spring-boot,spring-mvc,Java,Spring,Spring Boot,Spring Mvc,登录到SpringMVC应用程序需要自定义响应 当前感知用户的合理默认响应似乎是302,其中“Location:”头指向应用程序的所需部分 如果web应用程序恰好是一个单页应用程序,因此不需要实际重定向到任何地方,该怎么办 到目前为止,我已经成功地使用了两个过滤器:一个在用户名密码身份验证过滤器之前,另一个在过滤器安全Interceptor类之后 在代码中,前者扩展AbstractAuthenticationProcessingFilter并处理凭证和存储会话属性,而后者则查找该属性,如果存在,

登录到SpringMVC应用程序需要自定义响应

当前感知用户的合理默认响应似乎是302,其中“Location:”头指向应用程序的所需部分

如果web应用程序恰好是一个单页应用程序,因此不需要实际重定向到任何地方,该怎么办

到目前为止,我已经成功地使用了两个过滤器:一个在用户名密码身份验证过滤器之前,另一个在过滤器安全Interceptor类之后

在代码中,前者扩展AbstractAuthenticationProcessingFilter并处理凭证和存储会话属性,而后者则查找该属性,如果存在,则最终将使用所需的响应数据进行响应(稍后还会从会话中删除该属性)

到目前为止,过滤器对的好的一面是,可以对登录请求编写自定义响应。此外,新会话已正确初始化,我们可以重用现有会话固定策略

所述两个过滤器方法的问题:

  • 重定向将发生回web应用程序的根(“/”),尽管可以重置缓冲区并写入自定义数据(一个简单的JSON响应)——覆盖重定向没有成功

  • 并行执行的任何请求都将获得该自定义数据(必须注意,在经过身份验证之前,不得向应用程序的任何其他路径发出任何其他请求

  • 当在筛选时完成时,用户权限还未知。在通过AuthenticationProvider传递凭据之前,不知道响应的状态代码

  • 额外问题:如果出于某种原因,好的旧用户名和密码过滤器也需要合作,该怎么办

我还不能将完整的代码粘贴到最小的应用程序中,但到目前为止,Spring Security conf在扩展WebSecurity ConfigureAdapter的配置类中有以下内容:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf()
            .disable()
            .cors()
            .disable()
            .httpBasic()
            .disable()
            .formLogin()
            .disable()
            .authorizeRequests()
            .antMatchers("/")
            .permitAll()
            .antMatchers("/auth/**")
            .permitAll()
            .antMatchers("/js/**")
            .permitAll()
            .antMatchers("/welcome")
            .hasAuthority(MyAuthenticationProvider.AUTH_WELCOME.getAuthority())
            .anyRequest()
            .authenticated()
            .and()
            .addFilterBefore(getMyAuthenticationFilter("/auth/login"), UsernamePasswordAuthenticationFilter.class)
            .addFilterAfter(getMyResponseFilter(), FilterSecurityInterceptor.class)
            .logout()
            .logoutUrl("/logout")
            .deleteCookies("JSESSIONID")
            .invalidateHttpSession(true)
            .logoutSuccessUrl("/");
    }

    private Filter getMyAuthenticationFilter(String loginUrl) {
        return new MyAuthenticationProcessingFilter(loginUrl);
    }

    private Filter getMyResponseFilter() {
        return new MyAuthenticationReplyFilter();
    }
和过滤器如下所示:

package org.so.example;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.so.example.auth.TokenHolder;
import org.so.example.auth.MyAuthenticationToken;

public class MyAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
    private static final Logger LOG = LoggerFactory.getLogger(MyAuthenticationProcessingFilter.class);

    protected static final String MY_RESPONSE_DATA = "MY_RESPONSE_DATA";

    public MyAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
        LOG.info("Created MyAuthenticationProcessingFilter {}", defaultFilterProcessesUrl);
    }

    public MyAuthenticationProcessingFilter(RequestMatcher requestMatcher) {
        super(requestMatcher);
        LOG.info("Created MyAuthenticationProcessingFilter {}", requestMatcher);
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
        throws IOException, ServletException {
        LOG.info("doFilter");
        super.doFilter(req, res, chain);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
        throws AuthenticationException, IOException, ServletException {
        if (!"POST".equals(req.getMethod())) {
            throw new InsufficientAuthenticationException("POST only please");
        }

        // TODO authenticate the request and compose data

        // Once here, need to reply with My specific response
        // At moment there is another filter for that (farther in chain for always returning a success with detected user name).
        // however the user has not been authorized to do anything yet.
        // No idea what http status code to respond with.

        HttpServletRequest request = (HttpServletRequest) req;
        request.getSession().setAttribute(MY_RESPONSE_DATA, sub);

        return new MyAuthenticationToken(sub, parsedToken);
    }
}

如何提供登录?通过表单或HTTP Basic或OAuth?例如,如果您有一个登录页面,那么您将发送一个HTTP Get用于接收此页面,并发送一个HTTP POST用于发送凭据。但是在POST请求的响应中,您可以指向您想要的任何页面。让我困惑的是,可能有很多初始Get请求。登录按钮c您可以使用现代javascript在不更改URL的情况下打开对话框。例如,可能需要登录才能启用评论的新闻页面。单个帖子将在一个点上完成,并且根本不需要重定向。仅http状态和新cookie就够了吗?为什么要在浏览器上重新加载其余数据:?看起来Spring MVC真的很有用不适用于单页应用程序。嘿,我不确定这是否有效,但也许你可以尝试使用登录帖子作为AJAX请求。这样你就不会有重定向问题,你可以只动态更改登录用户想要的内容。非常感谢!目前使用AJAX提交登录数据时情况似乎更好-改为请求!但要注意安全性。也许你必须自己添加加密。别忘了启用CSRF-Protection。如何提供登录?通过表单或HTTP Basic或OAuth?如果你有一个登录页面,那么你发送一个HTTP Get以接收此页面,发送一个HTTP POST以发送凭据。但是在POST请求的响应中你可以指向任何你想要的页面。让我困惑的是,可能会有很多初始GET请求。登录按钮可以使用现代javascript打开对话框,而无需任何URL更改。例如,可能需要登录才能启用评论的新闻页面。单篇文章将在某一点完成,并且根本不需要重定向。Woul单凭http状态和新cookie还不够吗?为什么要在浏览器上重新加载其余数据:?Spring MVC似乎不适合单页应用程序。嘿,我不确定这是否有效,但也许你可以尝试使用登录帖子作为AJAX请求。这样你就不存在重定向问题,你可以只动态更改内容nt您想要一个登录的用户。非常感谢!现在使用AJAX请求提交登录数据似乎更好了!但是要注意安全性。也许您必须自己添加加密。不要忘记启用CSRF保护。
package org.so.example;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.filter.GenericFilterBean;

/**
 * This filter is meant to be last in chain to provide special kind of authentication response
 */
public class MyAuthenticationReplyFilter extends GenericFilterBean {
    private static final Logger LOG = LoggerFactory.getLogger(MyAuthenticationReplyFilter.class);

    public MyAuthenticationReplyFilter() {
        LOG.info("Constructing special reply filter");
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
        throws IOException, ServletException {
        LOG.info("doFilter");
        if (res.isCommitted()) {
            LOG.warn("How come response is already committed at filter time?");
            chain.doFilter(req, res);
        } else {
            HttpServletRequest request = (HttpServletRequest) req;
            String userName = (String) request
                .getSession()
                .getAttribute(MyAuthenticationProcessingFilter.MY_RESPONSE_DATA);
            if (userName != null) {
                HttpServletResponse response = (HttpServletResponse) res;
                response.setContentType("application/json; charset=UTF-8");
                response.setStatus(200);
                response.resetBuffer();
                //TODO sanitize data set by authentication processing filter
                response.getWriter().write("{\"great\" : \"success\", \"sub\": \"" + userName + "\"}");
                LOG.info("Responded with http 200 and some json instead of redirection");
                request.getSession().removeAttribute(MyAuthenticationProcessingFilter.MY_RESPONSE_DATA);
            } else {
                chain.doFilter(req, res);
            }
        }
    }
}```