gpt4 book ai didi

spring - 自定义 Spring Security 应用程序中的无限循环

转载 作者:行者123 更新时间:2023-12-04 16:49:14 35 4
gpt4 key购买 nike

我们尝试用开源应用程序中的 REST-API 替换现有的 Spring Security 基本登录,以实现使用 token 的自定义登录。我阅读了这篇关于该主题的博文:http://javattitude.com/2014/06/07/spring-security-custom-token-based-rest-authentication/

当请求没有名为“Cookie”的 header 时,我得到正确的 401 - 未经授权的响应(预期行为)。当请求具有有效 token 时,我得到一个无限循环,导致 java.lang.StackOverflowError:

    Exception in thread "http-bio-8080-exec-45" java.lang.StackOverflowError
at org.apache.tomcat.util.http.NamesEnumerator.<init>(MimeHeaders.java:402)
at org.apache.tomcat.util.http.MimeHeaders.names(MimeHeaders.java:228)
at org.apache.catalina.connector.Request.getHeaderNames(Request.java:2108)
at org.apache.catalina.connector.RequestFacade.getHeaderNames(RequestFacade.java:726)
at javax.servlet.http.HttpServletRequestWrapper.getHeaderNames(HttpServletRequestWrapper.java:103)
at javax.servlet.http.HttpServletRequestWrapper.getHeaderNames(HttpServletRequestWrapper.java:103)
at javax.servlet.http.HttpServletRequestWrapper.getHeaderNames(HttpServletRequestWrapper.java:103)
at javax.servlet.http.HttpServletRequestWrapper.getHeaderNames(HttpServletRequestWrapper.java:103)
at javax.servlet.http.HttpServletRequestWrapper.getHeaderNames(HttpServletRequestWrapper.java:103)
at javax.servlet.http.HttpServletRequestWrapper.getHeaderNames(HttpServletRequestWrapper.java:103)
at org.activiti.rest.security.CustomTokenAuthenticationFilter.attemptAuthentication(CustomTokenAuthenticationFilter.java:43)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:211)
at org.activiti.rest.security.CustomTokenAuthenticationFilter.doFilter(CustomTokenAuthenticationFilter.java:86)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:65)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:166)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:749)
at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:487)
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:412)
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:339)
at org.springframework.security.web.firewall.RequestWrapper$FirewalledRequestAwareRequestDispatcher.forward(RequestWrapper.java:132)
at org.activiti.rest.security.TokenSimpleUrlAuthenticationSuccessHandler.onAuthenticationSuccess(TokenSimpleUrlAuthenticationSuccessHandler.java:30)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.successfulAuthentication(AbstractAuthenticationProcessingFilter.java:331)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.successfulAuthentication(AbstractAuthenticationProcessingFilter.java:298)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:235)
at org.activiti.rest.security.CustomTokenAuthenticationFilter.doFilter(CustomTokenAuthenticationFilter.java:86)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:65)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:166)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:749)
at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:487)
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:412)
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:339)
at org.springframework.security.web.firewall.RequestWrapper$FirewalledRequestAwareRequestDispatcher.forward(RequestWrapper.java:132)
at org.activiti.rest.security.TokenSimpleUrlAuthenticationSuccessHandler.onAuthenticationSuccess(TokenSimpleUrlAuthenticationSuccessHandler.java:30)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.successfulAuthentication(AbstractAuthenticationProcessingFilter.java:331)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.successfulAuthentication(AbstractAuthenticationProcessingFilter.java:298)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:235)
at org.activiti.rest.security.CustomTokenAuthenticationFilter.doFilter(CustomTokenAuthenticationFilter.java:86)

我的 Spring 安全配置如下所示:

@Configuration
@EnableWebSecurity
@EnableWebMvcSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Bean
public AuthenticationProvider authenticationProvider() {
return new BasicAuthenticationProvider();
}

@Autowired
AuthenticationProvider basicAuthenticationProvider;

@Bean
public CustomTokenAuthenticationFilter customTokenAuthenticationFilter(){
System.out.println("+++ create new CustomTokenAuthenticationFilter for path=/**");
return new CustomTokenAuthenticationFilter("/**");
};

@Autowired
CustomTokenAuthenticationFilter customTokenAuthenticationFilter;

@Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("init of http security START");
http
.authenticationProvider(authenticationProvider())
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()//.authenticationProvider(basicAuthenticationProvider);
.addFilterBefore(customTokenAuthenticationFilter, BasicAuthenticationFilter.class)
.httpBasic();
//.and().addFilter(filter);
System.out.println("init of http security DONE");
}
}

我已经尝试将 URL 映射从 /** 更改为 /activiti-rest/** 但随后,基本身份验证再次启动。

这是我的自定义 token 身份验证过滤器:

public class CustomTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

private static final Logger logger = LoggerFactory.getLogger(CustomTokenAuthenticationFilter.class);
public CustomTokenAuthenticationFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(defaultFilterProcessesUrl));
setAuthenticationManager(new NoOpAuthenticationManager());
setAuthenticationSuccessHandler(new TokenSimpleUrlAuthenticationSuccessHandler());
}


public final String HEADER_SECURITY_TOKEN = "Cookie";//"LdapToken";


/**
* Attempt to authenticate request - basically just pass over to another method to authenticate request headers
*/
@Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
Enumeration<String> headerNames = request.getHeaderNames();
int i = 0;
while (headerNames.hasMoreElements()){
String key = (String) headerNames.nextElement();
String value = request.getHeader(key);
System.out.println("+++ key["+i+"]" +key);
System.out.println("+++ val["+i+"]" +value);
i++;
}
String token = request.getHeader(HEADER_SECURITY_TOKEN);
logger.info("token found:"+token);
System.out.println("+++ token found:"+token);
AbstractAuthenticationToken userAuthenticationToken = authUserByToken(token);
if(userAuthenticationToken == null) throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token"));
System.out.println("+++ userAuthenticationToken:"+userAuthenticationToken.toString());
return userAuthenticationToken;
}


/**
* authenticate the user based on token
* @return
*/
private AbstractAuthenticationToken authUserByToken(String token) {
if(token==null) {
System.out.println("+++ i shouldn't be null +++");
return null;
}
AbstractAuthenticationToken authToken = new JWTAuthenticationToken(token);
try {
return authToken;
} catch (Exception e) {
System.out.println(e);
logger.error("Authenticate user by token error: ", e);
}
return authToken;
}


@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
System.out.println("++++++++++++++++++++++++++++++ doFilter ");
super.doFilter(req, res, chain);
}
}

还有我的自定义成功处理程序。我认为这会导致无限循环,但我不知道为什么:

public class TokenSimpleUrlAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

@Override
protected String determineTargetUrl(HttpServletRequest request,
HttpServletResponse response) {
System.out.println("+++ yuhuuu determineTargetUrl+++");
String context = request.getContextPath();
String fullURL = request.getRequestURI();
String url = fullURL.substring(fullURL.indexOf(context)+context.length());
return url;
}

@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
System.out.println("+++ yuhuuu onAuthenticationSuccess+++");
String url = determineTargetUrl(request,response);
request.getRequestDispatcher(url).forward(request, response);
}
}

所有其他类(NoOpAuthenticationManager 和 RestAuthenticationEntryPoint)与这篇博文中的完全一样。

如果有人能给我提示是什么导致了这个无限循环,那就太好了。正如我所说,它仅在请求具有有效 token 时发生。

谢谢和最好的问候本

最佳答案

您的编码方法是有效的。但是,我可以为您提供一种略有不同但可行的方法。在我开始解释解决方案之前,这里是代码:

WebSecurityConfig.java

@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().
antMatchers("/restapi").hasRole("USER")
.and().addFilterBefore(new SsoTokenAuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class).httpBasic()
.and().authorizeRequests().antMatchers("/**").permitAll().anyRequest().authenticated();
}

@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
// The order is important! During runtime Spring Security tries to find Provider-Implementations that
// match the UsernamePasswordAuthenticationToken (which will be created later..). We must make sure
// that daoAuthenticationProvider matches first. Why? Hard to explain, I figured it out with the debugger.
auth.authenticationProvider(daoAuthenticationProvider());
auth.authenticationProvider(tokenAuthenticationProvider());

}

@Bean
public AuthenticationProvider tokenAuthenticationProvider() {
return new SsoTokenAuthenticationProvider();
}

@Bean
public AuthenticationProvider daoAuthenticationProvider() {
// DaoAuthenticationProvider requires a userDetailsService object to be attached.
// So we build one. This replaces the AuthenticationConfiguration, which is commented out below

// Build the userDetailsService
User userThatMustMatch = new User("michael", "password", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_RESTUSER"));
Collection<UserDetails> users = new ArrayList<>();
users.add(userThatMustMatch);
InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager(users);

// Create the DaoAuthenticationProvider that will handle all HTTP BASIC AUTH requests
DaoAuthenticationProvider daoAuthProvider = new DaoAuthenticationProvider();
daoAuthProvider.setUserDetailsService(userDetailsService);
return daoAuthProvider;
}

SsoTokenAuthenticationFilter.java

public class SsoTokenAuthenticationFilter extends GenericFilterBean {

public final String HEADER_SECURITY_COOKIE = "LdapToken";

private AuthenticationManager authenticationManager;
private AuthenticationDetailsSource<HttpServletRequest,?> ssoTokenAuthenticationDetailsSource = new SsoTokenWebAuthenticationDetailsSource();

public SsoTokenAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;

// check if SSO token is available. If not, pass down to next filter in chain
try {
Cookie[] cookies = httpRequest.getCookies();
if (cookies == null){
chain.doFilter(request, response);
return;
}
Cookie ssoCookie = null;
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals("ssoToken"))
ssoCookie = cookies[i];
}
if (ssoCookie == null){
chain.doFilter(request, response);
return;
}

// SSO token found, now authenticate and afterwards pass down to next filter in chain
authenticateWithSsoToken(httpRequest);
logger.debug("now the AuthenticationFilter passes down to next filter in chain");
chain.doFilter(request, response);
} catch (InternalAuthenticationServiceException internalAuthenticationServiceException) {
SecurityContextHolder.clearContext();
logger.error("Internal authentication service exception", internalAuthenticationServiceException);
httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} catch (AuthenticationException authenticationException) {
SecurityContextHolder.clearContext();
logger.debug("No or invalid SSO token");
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage());
}
}

private void authenticateWithSsoToken(HttpServletRequest request) throws IOException {
System.out.println("+++ authenticateWithSSOToken +++");
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(null, null, AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_RESTUSER"));
authRequest.setDetails(ssoTokenAuthenticationDetailsSource.buildDetails(request));

// Delegate authentication to SsoTokenAuthenticationProvider, he will call the SsoTokenAuthenticationProvider <-- because of the configuration in WebSecurityConfig.java
Authentication authResult = authenticationManager.authenticate(authRequest);
}}

SsoTokenAuthenticationProvider.java

public class SsoTokenAuthenticationProvider implements AuthenticationProvider {

public SsoTokenAuthenticationProvider() {

}

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
SsoTokenWebAuthenticationDetails ssoTokenWebAuthenticationDetails = null;
WebAuthenticationDetails webWebAuthenticationDetails = (WebAuthenticationDetails)authentication.getDetails();

if (! (webWebAuthenticationDetails instanceof SsoTokenWebAuthenticationDetails)){
// ++++++++++++++++++++++++
// BASIC authentication....
// ++++++++++++++++++++++++
UsernamePasswordAuthenticationToken emptyToken = new UsernamePasswordAuthenticationToken(null, null);
emptyToken.setDetails(null);
return emptyToken; //return null works, too.
}

// ++++++++++++++++++++++++
// LDAP authentication....
// ++++++++++++++++++++++++
ssoTokenWebAuthenticationDetails = (SsoTokenWebAuthenticationDetails)webWebAuthenticationDetails;
Cookie ssoTokenCookie = ssoTokenWebAuthenticationDetails.getSsoTokenCookie();

// check if SSO cookie is available
if (ssoTokenCookie == null){
return new UsernamePasswordAuthenticationToken(null, null); //do basic auth.
}
String username = ssoTokenCookie.getValue();

// Do your SSO token authentication here
if (! username.equals("michael"))
return new UsernamePasswordAuthenticationToken(null, null); //do basic auth.

// Create new Authentication object. Name and password can be null (but you can set the values of course).
// Be careful with your role names!
// In WebSecurityConfig the role "USER" is automatically prefixed with String "ROLE_", so it is "ROLE_USER" in the end.
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(null, null, AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_RESTUSER"));
authRequest.setDetails(ssoTokenWebAuthenticationDetails);

// Don't let spring decide.. you already have made the right decisions. Tell spring you have an authenticated user.
// vielleicht ist dieses obere Kommentar auch bullshit... ich lese das morgen noch mal nach...
SecurityContextHolder.getContext().setAuthentication(authentication);
return authentication;
}

@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}

SsoTokenWebAuthenticationDetailsS​​ource.java

public class SsoTokenWebAuthenticationDetailsSource extends
WebAuthenticationDetailsSource {

@Override
public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
return new SsoTokenWebAuthenticationDetails(context);
}

}

SsoTokenWebAuthenticationDetails.java

public class SsoTokenWebAuthenticationDetails extends WebAuthenticationDetails {
private static final long serialVersionUID = 1234567890L;

private Cookie ssoTokenCookie;

public SsoTokenWebAuthenticationDetails(HttpServletRequest request) {
super(request);
// Fetch cookie from request
Cookie[] cookies = request.getCookies();

Cookie ssoTokenCookie = null;
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals("SSOToken"))
ssoTokenCookie= cookies[i];
}
this.setSsoTokenCookie(ssoTokenCookie);
}

public Cookie getSsoTokenCookie() {
return ssoTokenCookie;
}

public void setSsoTokenCookie(Cookie ssoTokenCookie) {
this.ssoTokenCookie = ssoTokenCookie;
}
}

我在 View 中描述解决方案的话:

  1. Config 类保护任何具有角色ROLE_USER/restapi Controller 。可以使用 httpBasic 身份验证完成身份验证,但在此之前您可以尝试基本身份验证。您必须尝试通过 ssoTokenCookie(如果可用)对用户进行身份验证。因此,您将 SsoTokenAuthenticationFilter 设置为基本身份验证之前的过滤器。已应用。
  2. 在过滤器内部,您检查 ssoTokenCookie 是否在请求中可用。
    • 如果是,您将身份验证委托(delegate)给标准的 spring AuthenticationManagerAuthenticationManager 知道您自己的 SsoTokenAuthenticationProvider 实现并将身份验证委托(delegate)给它。在这里,重要的是要有可用的 cookie 信息。这可以通过使用自定义的 WebAuthenticationDetails 来完成。
    • 如果否,您将工作传递给链中的下一个过滤器。毫不奇怪,标准的 BasicAuthenticationFilter 将被调用。因为您告诉 Spring 在 WebSecurityConfig.java 中使用标准的 daoAuthenticationProvider,当在基本身份验证中输入正确的凭据时,Spring 可以对用户进行身份验证。对话

关于spring - 自定义 Spring Security 应用程序中的无限循环,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28368254/

35 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com