Пользовательский ответ при входе в приложение Spring MVC - PullRequest
1 голос
/ 21 апреля 2020

Необходим пользовательский ответ для входа в приложение Spring MVC.

Текущий разумный ответ по умолчанию для аутентификации пользователя выглядит как 302 с заголовком «Location:», указывающим на нужную часть приложения.

Что, если веб-приложение окажется одностраничным приложением, чтобы его не нужно было фактически перенаправлять куда-либо?

До сих пор мне удавалось поиграть с двумя фильтрами: Один до UsernamePasswordAuthenticationFilter и другой после класса FilterSecurityInterceptor в цепочке.

В первом коде расширяется AbstractAuthenticationProcessingFilter и обрабатывает учетные данные и сохраняет атрибут сеанса, в то время как последний ищет этот атрибут и, если он присутствует, в конечном итоге отвечает желаемыми данными ответа (позже также удаляет атрибут из сеанса).

Хорошая сторона пары фильтров заключается в том, что можно написать пользовательский ответ на требование входа стандартное восточное время. Также новый сеанс правильно инициализирован, и мы можем повторно использовать существующую стратегию фиксации сеанса.

Проблемы с описанным подходом двух фильтров:

  • Перенаправление произойдет обратно на root сети приложение ("/"), хотя возможно сбросить буфер и записать пользовательские данные (простой ответ JSON) - нет успеха в переопределении перенаправления;

  • Любой запрос параллельное выполнение получит эти пользовательские данные (необходимо позаботиться о том, чтобы никакой другой запрос к любому другому пути приложения не выполнялся до аутентификации;

  • При выполнении во время фильтрации права пользователя Пока неизвестно. Не знаю, с каким кодом состояния отвечать до тех пор, пока учетные данные не будут переданы через AuthenticationProvider.

  • Бонусный вопрос: Что если по какой-то причине понадобится старый добрый фильтр имени пользователя и пароля? сотрудничать?

Я не могу вставить полный код в минимальное приложение, но пока Spring Security conf имеет светящиеся экскременты в классе конфигурации, расширяющем WebSecurityConfigurerAdapter :

    @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);
    }
}
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);
            }
        }
    }
}```



Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...