java 带有会话/Redis 序列化错误的 Spring Boot 和错误的 Active Directory LDAP 凭据
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/32751094/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring Boot with Session/Redis Serialization Error with Bad Active Directory Ldap Credentials
提问by Michael Kowalski
Hi I am new to Spring and Java, I am trying to implement a Gateway authentication server as described in this tutorial https://spring.io/guides/tutorials/spring-security-and-angular-js/
嗨,我是 Spring 和 Java 的新手,我正在尝试实现本教程中所述的网关身份验证服务器https://spring.io/guides/tutorials/spring-security-and-angular-js/
I got everything working and then tried to implement the authentication against our company Ldap server. It works if I use a valid username and password. When I use invalid credentials the application errors.
我让一切正常,然后尝试对我们公司的 Ldap 服务器实施身份验证。如果我使用有效的用户名和密码,它会起作用。当我使用无效凭据时,应用程序会出错。
I am not at work so I don't have the exact error, but it is returning an ldap error (com.sun.jndi.ldap.LdapCtx) and Redis is trying to serialize it.
我不在工作,所以我没有确切的错误,但它返回一个 ldap 错误 (com.sun.jndi.ldap.LdapCtx) 并且 Redis 正在尝试对其进行序列化。
Is there something I am missing in my configuration. From what I have read I think I should be looking for a way to wrap/extend the class and implement Serializable, but I am unsure of the least invasive way to do this with Spring Boot.
我的配置中是否缺少某些内容。从我读到的内容来看,我认为我应该寻找一种包装/扩展类并实现 Serializable 的方法,但我不确定使用 Spring Boot 执行此操作的侵入性最小的方法。
Any help is greatly appreciated.
任何帮助是极大的赞赏。
Thanks,
谢谢,
Mike Kowalski
迈克·科瓦尔斯基
PS I have been working mostly in dynamic languages and frameworks until now (Javascript/Node, Php/Laravel)
PS 到目前为止,我主要从事动态语言和框架的工作(Javascript/Node、Php/Laravel)
Here are what I think are the relevant parts of the Security configuration:
以下是我认为安全配置的相关部分:
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.defaultSuccessUrl("/")
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/logout")
.permitAll();
http
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.csrf().csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder
.authenticationProvider(activeDirectoryLdapAuthenticationProvider())
.userDetailsService(userDetailsService());
}
@Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(
Arrays.asList(activeDirectoryLdapAuthenticationProvider())
);
}
@Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(
"XXX.XXX", "ldaps://XXX.XXX:636");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException
{
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
Here is part of the error that using invalid credentials:
这是使用无效凭据的错误的一部分:
2015-09-24 15:07:30.564 DEBUG 6552 --- [nio-8080-exec-3] o.s.security.web.FilterChainProxy : /login at position 3 of 13 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2015-09-24 15:07:30.564 DEBUG 6552 --- [nio-8080-exec-3] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@44258b05
2015-09-24 15:07:30.564 DEBUG 6552 --- [nio-8080-exec-3] o.s.security.web.FilterChainProxy : /login at position 4 of 13 in additional filter chain; firing Filter: 'CsrfFilter'
2015-09-24 15:07:30.564 DEBUG 6552 --- [nio-8080-exec-3] o.s.security.web.FilterChainProxy : /login at position 5 of 13 in additional filter chain; firing Filter: ''
2015-09-24 15:07:30.564 DEBUG 6552 --- [nio-8080-exec-3] o.s.security.web.FilterChainProxy : /login at position 6 of 13 in additional filter chain; firing Filter: 'LogoutFilter'
2015-09-24 15:07:30.564 DEBUG 6552 --- [nio-8080-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/login'; against '/logout'
2015-09-24 15:07:30.580 DEBUG 6552 --- [nio-8080-exec-3] o.s.security.web.FilterChainProxy : /login at position 7 of 13 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
2015-09-24 15:07:30.580 DEBUG 6552 --- [nio-8080-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/login'; against '/login'
2015-09-24 15:07:30.580 DEBUG 6552 --- [nio-8080-exec-3] w.a.UsernamePasswordAuthenticationFilter : Request is to process authentication
2015-09-24 15:07:30.580 DEBUG 6552 --- [nio-8080-exec-3] o.s.s.authentication.ProviderManager : Authentication attempt using org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider
2015-09-24 15:07:30.580 DEBUG 6552 --- [nio-8080-exec-3] ctiveDirectoryLdapAuthenticationProvider : Processing authentication request for user: admin
2015-09-24 15:07:31.113 DEBUG 6552 --- [nio-8080-exec-3] ctiveDirectoryLdapAuthenticationProvider : Authentication for [email protected] failed:javax.naming.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C090334, comment: AcceptSecurityContext error, data 525, vece]
2015-09-24 15:07:31.113 INFO 6552 --- [nio-8080-exec-3] ctiveDirectoryLdapAuthenticationProvider : Active Directory authentication failed: User was not found in directory
2015-09-24 15:07:31.114 DEBUG 6552 --- [nio-8080-exec-3] w.a.UsernamePasswordAuthenticationFilter : Authentication request failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials
2015-09-24 15:07:31.114 DEBUG 6552 --- [nio-8080-exec-3] w.a.UsernamePasswordAuthenticationFilter : Updated SecurityContextHolder to contain null Authentication
2015-09-24 15:07:31.114 DEBUG 6552 --- [nio-8080-exec-3] w.a.UsernamePasswordAuthenticationFilter : Delegating to authentication failure handler org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler@28626d9a
2015-09-24 15:07:31.114 DEBUG 6552 --- [nio-8080-exec-3] .a.SimpleUrlAuthenticationFailureHandler : Redirecting to /login?error
2015-09-24 15:07:31.115 DEBUG 6552 --- [nio-8080-exec-3] o.s.s.web.DefaultRedirectStrategy : Redirecting to '/login?error'
2015-09-24 15:07:31.115 DEBUG 6552 --- [nio-8080-exec-3] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2015-09-24 15:07:31.139 DEBUG 6552 --- [nio-8080-exec-3] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2015-09-24 15:07:31.148 ERROR 6552 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.io.NotSerializableException: com.sun.jndi.ldap.LdapCtx
at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.serialize(JdkSerializationRedisSerializer.java:52)
at org.springframework.data.redis.core.AbstractOperations.rawHashValue(AbstractOperations.java:146)
at org.springframework.data.redis.core.DefaultHashOperations.putAll(DefaultHashOperations.java:128)
at org.springframework.data.redis.core.DefaultBoundHashOperations.putAll(DefaultBoundHashOperations.java:85)
at org.springframework.session.data.redis.RedisOperationsSessionRepository$RedisSession.saveDelta(RedisOperationsSessionRepository.java:409)
at org.springframework.session.data.redis.RedisOperationsSessionRepository$RedisSession.accessimport com.gateway.utils.LdapFailAwareRedisObjectSerializer;
@Configuration
public class RedisConfiguration {
@Primary
@Bean
public RedisTemplate<String,ExpiringSession> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, ExpiringSession> template = new RedisTemplate<String, ExpiringSession>();
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new LdapFailAwareRedisObjectSerializer());
template.setConnectionFactory(connectionFactory);
return template;
}
}
0(RedisOperationsSessionRepository.java:331)
at org.springframework.session.data.redis.RedisOperationsSessionRepository.save(RedisOperationsSessionRepository.java:211)
at org.springframework.session.data.redis.RedisOperationsSessionRepository.save(RedisOperationsSessionRepository.java:141)
at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.commitSession(SessionRepositoryFilter.java:193)
at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.access0(SessionRepositoryFilter.java:169)
at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:127)
at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:65)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:85)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:68)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:668)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1521)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1478)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.io.NotSerializableException: com.sun.jndi.ldap.LdapCtx
at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:67)
at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:34)
at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.serialize(JdkSerializationRedisSerializer.java:50)
... 40 common frames omitted
Caused by: java.io.NotSerializableException: com.sun.jndi.ldap.LdapCtx
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:441)
at java.lang.Throwable.writeObject(Throwable.java:985)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:988)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at org.springframework.core.serializer.DefaultSerializer.serialize(DefaultSerializer.java:44)
at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:62)
... 42 common frames omitted
采纳答案by Michael Kowalski
I figured out a solution to this problem. I am open to any suggestions to improve the answer.
我想出了解决这个问题的办法。我愿意接受任何改进答案的建议。
The solution is not complete as I need to look specifically for the
com.sun.jndi.ldap.LdapCtxtype when serialization fails so I can deal with that specific case and throw the SerializationExceptionin all others. But I thought the general idea might be useful to anyone who is blocked on this.
该解决方案并不完整,因为我需要com.sun.jndi.ldap.LdapCtx在序列化失败时专门查找
类型,以便我可以处理该特定情况并将其抛入SerializationException所有其他情况。但我认为一般的想法可能对任何被阻止的人有用。
Now when invalid credentials are used (eg Bad Username or Incorrect Password) the application returns to the log in page rather than blowing up :)
现在,当使用无效凭据(例如错误的用户名或错误的密码)时,应用程序将返回登录页面而不是炸毁 :)
I added some RedisConfigurationto replace the RedisTemplateSpring Session is using.
我添加了一些RedisConfiguration来替换RedisTemplate正在使用的Spring Session。
public class LdapFailAwareRedisObjectSerializer implements RedisSerializer<Object> {
private Converter<Object, byte[]> serializer = new SerializingConverter();
private Converter<byte[], Object> deserializer = new DeserializingConverter();
static final byte[] EMPTY_ARRAY = new byte[0];
public Object deserialize(byte[] bytes) {
if (isEmpty(bytes)) {
return null;
}
try {
return deserializer.convert(bytes);
} catch (Exception ex) {
throw new SerializationException("Cannot deserialize", ex);
}
}
public byte[] serialize(Object object) {
if (object == null) {
return EMPTY_ARRAY;
}
try {
return serializer.convert(object);
} catch (Exception ex) {
return EMPTY_ARRAY;
//TODO add logic here to only return EMPTY_ARRAY for known conditions
// else throw the SerializationException
// throw new SerializationException("Cannot serialize", ex);
}
}
private boolean isEmpty(byte[] data) {
return (data == null || data.length == 0);
}
}
Here is my implementation of RedisSerializer<Object>(LdapFailAwareRedisObjectSerializerwhich is got from here)
这是我的RedisSerializer<Object>(LdapFailAwareRedisObjectSerializer从这里得到的)的实现
/**
* @author Meron Abraha 12/18/17
*/
public class CustomRedisSerializer implements RedisSerializer<Object> {
private Converter<Object, byte[]> serializer = new SerializingConverter();
private Converter<byte[], Object> deserializer = new DeserializingConverter();
static final byte[] EMPTY_ARRAY = new byte[0];
public Object deserialize(byte[] bytes) {
if (isEmpty(bytes)) {
return null;
}
try {
return deserializer.convert(bytes);
} catch (Exception ex) {
throw new SerializationException("Cannot deserialize", ex);
}
}
public byte[] serialize(Object object) {
if (object == null) {
return EMPTY_ARRAY;
}
try {
return serializer.convert(object);
} catch (Exception ex) {
return EMPTY_ARRAY;
}
}
private boolean isEmpty(byte[] data) {
return (data == null || data.length == 0);
}
}
回答by user9115069
This just worked fine for me after using classes of org.springframework.core.serializer.support.DeserializingConverter and org.springframework.core.serializer.support.SerializingConverter
在使用 org.springframework.core.serializer.support.DeserializingConverter 和 org.springframework.core.serializer.support.SerializingConverter 类后,这对我来说很好用
##代码##回答by John Adeshola
The Java object to be cached must implement the serializable interface, because spring will serialize the object and store it in redis.
需要缓存的Java对象必须实现serializable接口,因为spring会将对象序列化存储在redis中。
e.g. public class Store implement Serializable
例如 public class Store implement Serializable
The short story here is ensure you implement the serializable interface on your class.
这里的小故事是确保你在你的类上实现了可序列化的接口。
I hope this help. Good luck.
我希望这有帮助。祝你好运。

