У меня проблемы с моим бэкэндом Spring и внешним интерфейсом AngularJS.Как информация, я довольно новичок в Spring Security и также учусь с этим проектом.
Я не использую SpringBoot.Оба работают раздельно и должны работать на отдельных машинах.ATM Мой внешний интерфейс работает локально через сервер gulp на https://localhost:3000
, внутренний сервер работает в Tomcat на https://localhost:8443/context
.Я настроил CORSFilter
в Java.
Пока все хорошо.Если я запускаю веб-интерфейс, в сервер поступают вызовы для получения ресурсов, и я захожу на страницу входа.Если я выберу логин, звонок будет https://localhost:8443/context/login
, как и положено. Но : после обработки имени входа в серверной части сервер выполняет перенаправление на https://localhost:8443/context
вместо https://localhost:3000
, что, конечно, создает404 и приводит к неудачной регистрации (по внешнему интерфейсу).Я просто не могу найти, где происходит это странное перенаправление.
SpringSecurityConfig
:
private static final String C440_LOGIN = "/login";
private static final String c440_START_PAGE = "/index.html";
private static final String FAVICON_ICO = "/favicon.ico";
@Override
protected void configure(HttpSecurity http) throws Exception {
// HttpSecurity workHttp = http.addFilterBefore(new CORSFilter(), SessionManagementFilter.class); does not work!
HttpSecurity workHttp = http.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class);
workHttp.addFilterBefore(new CookieFilter(), ChannelProcessingFilter.class);
workHttp.addFilterBefore(getUsernamePasswordPortalAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
// set authorizations
workHttp = authorizeRequests(http);
// login handling
workHttp = formLogin(workHttp);
// exception handling
workHttp = exceptionHandling(workHttp);
// logout handling
workHttp = logout(workHttp);
// cookie handling
workHttp = rememberMe(workHttp);
// disable caching because if IE11 webfonds bug
// /6159599/font-face-eot-ne-zagruzhaetsya-po-https
http.headers().cacheControl().disable();
csrf(workHttp);
}
/**
* Configures request authorization.
*
* @param http The security configuration.
* @return The configured security configuration.
* @throws Exception is throws if the configuration fails.
*/
protected HttpSecurity authorizeRequests(HttpSecurity http) throws Exception {
return http
.authorizeRequests()
// secured pages
.antMatchers("/", getCustomerdWebRessourceSecuredPath()).authenticated()
// common resources
.antMatchers("/app/**").permitAll()
.antMatchers("/profiles/**").permitAll()
.antMatchers("/captcha/**").permitAll()
.antMatchers("/", getCustomerRessourcePath()).permitAll()
.antMatchers("/", getCustomerWebRessourcePath()).permitAll()
.antMatchers("/", c440_START_PAGE).permitAll()
.antMatchers("/", FAVICON_ICO).permitAll()
.antMatchers(C440_LOGIN).permitAll()
// frontend services
.antMatchers("/services/userService/**").permitAll()
.antMatchers("/services/applicationService/**").permitAll()
.antMatchers("/services/textContentService/**").permitAll()
.antMatchers("/services/textContentBlockService/**").permitAll()
.antMatchers("/services/menuItemService/**").permitAll()
.antMatchers("/services/calculatorService/**").permitAll()
.anyRequest().authenticated()
.and();
}
private String getCustomerRessourcePath() {
return "/resources/app-" + portalFrontendBase + "/**";
}
private String getCustomerWebRessourcePath() {
return "/app-" + portalFrontendBase + "/**";
}
private String getCustomerdWebRessourceSecuredPath() {
return "/app-" + portalFrontendBase + "/secure/**";
}
/**
* Configures form login.
*
* @param http The security configuration.
* @return The configured security configuration.
* @throws Exception is throws if the configuration fails.
*/
protected HttpSecurity exceptionHandling(HttpSecurity http) throws Exception {
return http
.exceptionHandling()
.authenticationEntryPoint((request, response, authException) -> {
if (authException != null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
/**
* IMPORTANT: do not redirect the requests. The front-end will be responsible to do this.
* Otherwise the unauthorized status cannot be caught in the front-end correctly.
*/
return;
}
})
.and();
}
/**
* Configures form login.
*
* @param http The security configuration.
* @return The configured security configuration.
* @throws Exception is throws if the configuration fails.
*/
protected HttpSecurity formLogin(HttpSecurity http) throws Exception {
return http
.formLogin()
.loginPage(c440_START_PAGE)
.successHandler(getAuthenticationSuccessHandler())
.failureHandler(getAuthenticationFailureHandler())
.loginProcessingUrl(C440_LOGIN)
.permitAll()
.and();
}
/**
* Configures logout.
*
* @param http The security configuration.
* @return The configured security configuration.
* @throws Exception is throws if the configuration fails.
*/
protected HttpSecurity logout(HttpSecurity http) throws Exception {
return http
.logout()
.logoutUrl(portalLogoutURL)
.addLogoutHandler(getLogoutHandler())
.logoutSuccessHandler(getLogoutSuccessHandler())
.invalidateHttpSession(true)
.and();
}
@Bean
public UsernamePasswordPortalAuthenticationFilter getUsernamePasswordPortalAuthenticationFilter() throws Exception {
UsernamePasswordPortalAuthenticationFilter customFilter = new UsernamePasswordPortalAuthenticationFilter();
customFilter.setAuthenticationManager(authenticationManagerBean());
return customFilter;
}
UsernamePasswordPortalAuthenticationFilter.java
:
@PropertySource(value = {"classpath:application.properties"})
public class UsernamePasswordPortalAuthenticationFilter extends
UsernamePasswordAuthenticationFilter {
private Logger log = Logger.getLogger(this.getClass());
@Value("${captchaActive}")
private boolean captchaActive;
@Override
public AuthenticationManager getAuthenticationManager() {
return super.getAuthenticationManager();
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
UsernamePasswordPortalAuthenticationToken authRequest = getAuthenticationTokenFromRequest(request);
return getAuthenticationManager().authenticate(authRequest);
}
/**
* Reads the UsernamePasswordPortalAuthenticationToken from the data of the request.
*
* @param request The request to read the data from.
* @return The authentication token.
* @throws AuthenticationException is thrown if the data cannot be read.
*/
public UsernamePasswordPortalAuthenticationToken getAuthenticationTokenFromRequest(final HttpServletRequest request) throws AuthenticationException {
StringBuffer buf = new StringBuffer();
String line = null;
try {
BufferedReader reader = request.getReader();
while ((line = reader.readLine()) != null) {
buf.append(line);
}
UsernamePasswordPortalAuthenticationToken loginDataWithCaptcha =
new ObjectMapper().readValue(buf.toString(), UsernamePasswordPortalAuthenticationToken.class);
if (this.captchaActive) {
String answer = (String) request.getSession().getAttribute("COLLPHIRCAPTCHA");
List<CaptchaCookieDto> captchaCookieDtos;
captchaCookieDtos = (List<CaptchaCookieDto>) request.getAttribute("captchaCookies");
CaptchaCookieDto captchaCookieDto = captchaCookieDtos.stream().filter(captchaCookie -> captchaCookie.getUsername().equals(
loginDataWithCaptcha.getUsername())).findAny().orElse(null);
if (captchaCookieDto != null && captchaCookieDto.getCounter() >= 2) {
if (answer.equals(loginDataWithCaptcha.getConfirmCaptcha())) {
return new ObjectMapper().readValue(loginDataWithCaptcha.loginDataToStringWithoutCaptcha(),
UsernamePasswordPortalAuthenticationToken.class);
} else {
throw new BadCredentialsException("invalid data");
}
} else {
return new ObjectMapper().readValue(loginDataWithCaptcha.loginDataToStringWithoutCaptcha(),
UsernamePasswordPortalAuthenticationToken.class);
}
} else {
return new ObjectMapper().readValue(loginDataWithCaptcha.loginDataToStringWithoutCaptcha(), UsernamePasswordPortalAuthenticationToken.class);
}
} catch (Exception e) {
throw new BadCredentialsException("invalid data");
}
}
}
Я попытался изменить порядок двух своих пользовательских фильтров (CORSFilter
и CookieFilter
) или поставить CORSFilter
что-то еще (addFilterBefore
SessionManagementFilter
не работает, если я это сделаюто, что login-call не будет работать из-за отсутствия CORS-заголовка) и почти всего остального ...
Я также попытался использовать идею из authsuccesshandler
из https://www.baeldung.com/spring_redirect_after_login, гдеЯ просто получаю заголовок origin
запросов (который должен быть URL-адресом https://localhost:3000
) для перенаправления на него:
@Component
public class MyTestAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private Logger LOG = Logger.getLogger(this.getClass());
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
public MyTestAuthenticationSuccessHandler() {
super();
setUseReferer(true);
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException {
LOG.info("onAuthenticationSuccess");
SecurityContextHolder.getContext().setAuthentication(auth);
handle(request, response, auth);
}
protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException {
String targetUrl = determineTargetUrl(request);
if (response.isCommitted()) {
LOG.info("Response has already been committed. Unable to redirect to " + targetUrl);
return;
}
redirectStrategy.sendRedirect(request, response, targetUrl);
}
protected String determineTargetUrl(HttpServletRequest request) {
return request.getHeader("Origin");
}
}
, но все равно он не работает.
Кроме того, если я попытаюсь отладить бэкэнд и установить точки останова внутри authsuccesshandler
и authfailurehandler
, это все равно не останавливается на достигнутом.Разве это не должно останавливаться на достигнутом?
.formLogin()
.loginPage(c440_START_PAGE)
.successHandler(getAuthenticationSuccessHandler())
.failureHandler(getAuthenticationFailureHandler())
.loginProcessingUrl(C440_LOGIN)
.permitAll()
.and();
Я действительно не понимаю, где происходит это перенаправление и почему он не будет использовать мой новый authsuccesshandler
.
ОБНОВЛЕНИЕ07.03.19: Похоже, что successhandler
вообще не вызывается , даже если я разверну и интерфейс, и серверную часть по одному и тому же URL-адресу, что и связанный файл WAR, который заставляет вход в систему работатьснова.Странно то, что даже если я уберу материал .formLogin()
из метода configure внутри SecurityConfig
, логин все еще работает.Так что я думаю, что похоже, что вся магия происходит в AuthenticationProvider
, который вызывается в нашем обычае UsernamePasswordAuthenticationFilter
:
UsernamePasswordAuthenticationFilter
[...]
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
UsernamePasswordPortalAuthenticationToken authRequest = getAuthenticationTokenFromRequest(request);
return getAuthenticationManager().authenticate(authRequest);
}
[...]
AuthenticationProvider
:
[...]
@Override
public CollphirAuthentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication == null) {
throw new IllegalArgumentException("authentication");
}
if (UsernamePasswordPortalAuthenticationToken.class.isAssignableFrom(authentication.getClass())) {
UsernamePasswordPortalAuthenticationToken clientAuthentication = (UsernamePasswordPortalAuthenticationToken) authentication;
CollphirUser user = getUserService().loginUser(
clientAuthentication.getName(), clientAuthentication.getCredentials().toString(), clientAuthentication.getPortal(), clientAuthentication.getArbeitgeber());
CollphirAuthentication auth = null;
if (user == null || user.getBenutzerkennung() == null || user.getCOLRolle() == null) {
LOG.info("authentication failed");
Notification[] notifications = user.getNotifications();
String msg = null;
if (notifications != null && notifications[0] != null && notifications[0].getText() != null) {
msg = notifications[0].getText();
}
throw new BadCredentialsException(msg);
}
Referenz arbeitgeberReference = getArbeitgeberReference(user, clientAuthentication.getPortal(), clientAuthentication.getArbeitgeber());
auth = new CollphirAuthentication(user, arbeitgeberReference);
auth.setArbeitgeber(getArbeitgeber( arbeitgeberReference));
LOG.debug("is authenticated: " + auth.isAuthenticated());
return auth;
}
throw new BadCredentialsException("type");
}
[...]
Итак, я думаю: где-то внутри UsernamePasswordPortalAuthenticationFilter
илиAuthenticationProvider
перенаправление выполняется.Если я подумаю об этом, перенаправление вообще не имеет смысла во внешнем интерфейсе AngularJS, где вызов сервера выполняется через REST, верно?Разве сервер не должен просто отправлять обратно код состояния или что-то, что контроллер AngularJS может оценить, чтобы изменить состояние или отобразить сообщение об ошибке?
Похоже, весь процесс входа в систему в этом приложении действительно странный.Я не могу себе представить, что обычно не используют .formLogin()
и .successHandler()
?Дело в том, что у меня нет лучшего примера для интерфейса AngularJS и Spring Security для сравнения ...