使用 Spring 的 AbstractAuthenticationProcessingFilter 成功认证后解决 404
Posted
技术标签:
【中文标题】使用 Spring 的 AbstractAuthenticationProcessingFilter 成功认证后解决 404【英文标题】:Resolved 404 after successfulAuthentication using AbstractAuthenticationProcessingFilter of Spring 【发布时间】:2021-07-30 02:12:21 【问题描述】:我有一个问题:我正在尝试基于两个 HEADER 实现身份验证,一个 HMAC-SHA256 使用商家的共享密钥和一个商家 ID 签名。 我想要的所有以 /api/payment/** 开头的路径都以这种方式检查。
当我在 POST /fcabpo/api/payment/hmac-validation 中调用我的服务时 似乎验证正确,因为调用了扩展 AbstractAuthenticationProcessingFilter 的我的类的方法successAuthentication gas bene,但似乎调用被转发到GET到上下文根/fcabpo,所以我收到了404。
我编写了扩展 AuthenticationProvider 的类 HMACAuthenticationProvider
@Component
@Slf4j
public class HMACAuthenticationProvider implements AuthenticationProvider
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException
HMACAuthenticationToken hmacAuthenticationToken = (HMACAuthenticationToken)authentication;
String merchandId = hmacAuthenticationToken.getPrincipalMerchantId();
HMACSha256Credential hmacSha256Credential = hmacAuthenticationToken.getHmacSha256Credential();
// TODO check the hmac
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority("MERCHANT"));
Authentication auth = new UsernamePasswordAuthenticationToken(merchandId, hmacSha256Credential.getHmacSha256(), grantedAuthorities);
return auth;
@Override
public boolean supports(Class<?> authentication)
return HMACAuthenticationToken.class.equals(authentication);
我还写了一个扩展 AbstractAuthenticationProcessingFilter 的 HMACRestSecurityFilter
@Slf4j
public class HMACRestSecurityFilter extends AbstractAuthenticationProcessingFilter
public static final String HEADER_KEY_MERCHANT_ID = "merchantId";
public static final String HMAC_SHA256 = "HMAC-SHA256";
public static final String EPOCH_TIME = "EPOCH-TIME";
public HMACRestSecurityFilter(RequestMatcher requiresAuthenticationRequestMatcher)
super(requiresAuthenticationRequestMatcher);
public HMACRestSecurityFilter(String defaultFilterProcessesUrl)
super(defaultFilterProcessesUrl);
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException
MultiReadHttpServletRequest multiReadHttpServletRequest = ServletUtil.wrapRequest(request);
String merchantId = multiReadHttpServletRequest.getHeader(HEADER_KEY_MERCHANT_ID);
String hmacSha256 = multiReadHttpServletRequest.getHeader(HMAC_SHA256);
String epochTimeStr = multiReadHttpServletRequest.getHeader(EPOCH_TIME);
if (StringUtils.isBlank(merchantId) || StringUtils.isBlank(hmacSha256) || StringUtils.isBlank(epochTimeStr))
unsuccessfulAuthentication(multiReadHttpServletRequest, response, new BadHMACAuthRequestException("Authentication attempt failed! Request missing mandatory headers."));
long epochTime = 0;
try
epochTime = Long.parseLong(epochTimeStr);
catch (NumberFormatException e)
unsuccessfulAuthentication(multiReadHttpServletRequest, response, new BadHMACAuthRequestException("Authentication attempt failed! Request missing mandatory headers."));
HMACSha256Credential hmacSha256Credential = new HMACSha256Credential();
byte[] content = multiReadHttpServletRequest.getInputStreamCopy();
String contentString = new String(content, multiReadHttpServletRequest.getCharacterEncoding());
hmacSha256Credential.setBody(contentString);
hmacSha256Credential.setEpochTime(epochTime);
hmacSha256Credential.setHmacSha256(hmacSha256);
hmacSha256Credential.setHttpMethod(multiReadHttpServletRequest.getMethod());
hmacSha256Credential.setUri(multiReadHttpServletRequest.getRequestURI());
HMACAuthenticationToken hmacAuthenticationToken = new HMACAuthenticationToken(merchantId, hmacSha256Credential);
return hmacAuthenticationToken;
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
MultiReadHttpServletRequest multiReadHttpServletRequest = ServletUtil.wrapRequest(request);
super.doFilter(multiReadHttpServletRequest, response, chain);
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException
super.successfulAuthentication(request, response, chain, authResult);
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException
super.unsuccessfulAuthentication(request, response, failed);
和扩展 AbstractAuthenticationToken 的 HMACAuthenticationToken 以及扩展 WebSecurityConfigurerAdapter 以配置路径 e 过滤器的类
@Configuration
public class ActuatorSecurity extends WebSecurityConfigurerAdapter
@Override
protected void configure(HttpSecurity http) throws Exception
http.httpBasic().and().authorizeRequests().antMatchers("/api/payment/**").hasAnyAuthority("MERCHANT").and().addFilterBefore(new HMACRestSecurityFilter("/api/payment/**"),
UsernamePasswordAuthenticationFilter.class);
http.authorizeRequests().anyRequest().permitAll().and().csrf().disable();
我不知道这可能是什么问题,也找不到任何有用的东西可以在网上解决。
【问题讨论】:
请启用spring security调试日志,然后发布你的完整日志、启动日志和来自请求的日志,这样我们就可以看到启动时加载了什么,以及它如何记录你的传入请求。 【参考方案1】:解决方案是调用 chain.doFilter(request, response);在成功认证中
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException
super.successfulAuthentication(request, response, chain, authResult);
chain.doFilter(request, response);
并注册一个新的 RedirectStrategy
public class NoRedirectStrategy implements RedirectStrategy
@Override
public void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException
配置类为:
package it.reply.pay.fcabpo.rs.util.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import it.reply.pay.fcabpo.rs.util.security.signature.SignatureAuthenticationProvider;
import it.reply.pay.fcabpo.rs.util.security.signature.SignatureResponseFilter;
import it.reply.pay.fcabpo.rs.util.security.signature.SignatureRestSecurityFilter;
import it.reply.pay.fcabpo.util.constants.ProjectConstants;
import it.reply.pay.fcabpo.util.enums.RoleEnum;
@Configuration
public class ProjectSecurityConfiguration extends WebSecurityConfigurerAdapter
public static final String PAYMENT_PATH_WILD_CARDS = ProjectConstants.Path.Payment.PAYMENT + "/**";
@Autowired
private SignatureAuthenticationProvider authenticationProvider;
// Se viene aggiunta questa configurazione per AuthenticationManagerBuilder l'autenticatore viene chiamato 2 volte erroneamente in caso di errore
// .authenticationProvider(authenticationProvider) prima di addFilterBefore
// verificare se è una configurazione necessaria quando ci sono più autenticatori
// @Override
// protected void configure(AuthenticationManagerBuilder auth)
// auth.authenticationProvider(authenticationProvider);
//
@Override
protected void configure(HttpSecurity http) throws Exception
// al posto di .hasAnyAuthority(RoleEnum.ROLE_MERCHANT.name()) si puo' anche usare .authenticated() e poi sopra il metodo rest indicare quale sia il ruolo da usare @Secured("ROLE_USER")
RequestMatcher paymReqMatcher = new AntPathRequestMatcher(PAYMENT_PATH_WILD_CARDS);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().exceptionHandling().defaultAuthenticationEntryPointFor(forbiddenEntryPoint(),
paymReqMatcher).and().addFilterBefore(signatureRestSecurityFilter(), AnonymousAuthenticationFilter.class).authorizeRequests().antMatchers(PAYMENT_PATH_WILD_CARDS).hasAnyAuthority(
RoleEnum.ROLE_MERCHANT.name()).and().csrf().disable().formLogin().disable().httpBasic().disable().logout().disable();
http.authorizeRequests().anyRequest().permitAll().and().csrf().disable();
@Bean
FilterRegistrationBean<SignatureRestSecurityFilter> disableAutoRegistration(SignatureRestSecurityFilter filter)
FilterRegistrationBean<SignatureRestSecurityFilter> registration = new FilterRegistrationBean<SignatureRestSecurityFilter>(filter);
// set as false to avoid multiple filter calls
registration.setEnabled(false);
return registration;
@Bean
AuthenticationEntryPoint forbiddenEntryPoint()
return new HttpStatusEntryPoint(HttpStatus.FORBIDDEN);
@Bean
public FilterRegistrationBean<SignatureResponseFilter> loggingFilter()
FilterRegistrationBean<SignatureResponseFilter> registrationBean = new FilterRegistrationBean<SignatureResponseFilter>();
registrationBean.setFilter(new SignatureResponseFilter());
registrationBean.setOrder(1);
registrationBean.addUrlPatterns(ProjectConstants.Path.Payment.PAYMENT + "/*");
return registrationBean;
@Bean
SignatureRestSecurityFilter signatureRestSecurityFilter() throws Exception
SignatureRestSecurityFilter filter = new SignatureRestSecurityFilter(PAYMENT_PATH_WILD_CARDS);
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(successHandler());
return filter;
@Bean
SimpleUrlAuthenticationSuccessHandler successHandler()
SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();
successHandler.setRedirectStrategy(new NoRedirectStrategy());
return successHandler;
【讨论】:
以上是关于使用 Spring 的 AbstractAuthenticationProcessingFilter 成功认证后解决 404的主要内容,如果未能解决你的问题,请参考以下文章
8 -- 深入使用Spring -- 7...1 启动Spring 容器
Spring Framework,Spring Security - 可以在没有 Spring Framework 的情况下使用 Spring Security?
[Spring实战系列](15)使用Spring基于Java的配置