Необходим пользовательский ответ для входа в приложение 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);
}
}
}
}```