具体异常
java.lang.IllegalArgumentException: The class with com.xxxx.CustomUser and name of com.xxxx.CustomUser is not in the allowlist. If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. If the serialization is only done by a trusted source, you can also enable default typing. See https://github.com/spring-projects/spring-security/issues/4370 for details
at org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService$OAuth2AuthorizationRowMapper.parseMap(JdbcOAuth2AuthorizationService.java:462) ~[spring-security-oauth2-authorization-server-0.3.0.jar:0.3.0]
at org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService$OAuth2AuthorizationRowMapper.mapRow(JdbcOAuth2AuthorizationService.java:347) ~[spring-security-oauth2-authorization-server-0.3.0.jar:0.3.0]
at org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService$OAuth2AuthorizationRowMapper.mapRow(JdbcOAuth2AuthorizationService.java:318) ~[spring-security-oauth2-authorization-server-0.3.0.jar:0.3.0]
at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:94) ~[spring-jdbc-5.3.15.jar:5.3.15]
at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:61) ~[spring-jdbc-5.3.15.jar:5.3.15]
at org.springframework.jdbc.core.JdbcTemplate$1.doInPreparedStatement(JdbcTemplate.java:723) ~[spring-jdbc-5.3.15.jar:5.3.15]
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:651) ~[spring-jdbc-5.3.15.jar:5.3.15]
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:713) ~[spring-jdbc-5.3.15.jar:5.3.15]
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:744) ~[spring-jdbc-5.3.15.jar:5.3.15]
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:799) ~[spring-jdbc-5.3.15.jar:5.3.15]
at org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService.findBy(JdbcOAuth2AuthorizationService.java:266) ~[spring-security-oauth2-authorization-server-0.3.0.jar:0.3.0]
at org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService.findByToken(JdbcOAuth2AuthorizationService.java:251) ~[spring-security-oauth2-authorization-server-0.3.0.jar:0.3.0]
at org.springframework.security.oauth2.server.authorization.authentication.CodeVerifierAuthenticator.authenticate(CodeVerifierAuthenticator.java:77) ~[spring-security-oauth2-authorization-server-0.3.0.jar:0.3.0]
at org.springframework.security.oauth2.server.authorization.authentication.CodeVerifierAuthenticator.authenticateIfAvailable(CodeVerifierAuthenticator.java:66) ~[spring-security-oauth2-authorization-server-0.3.0.jar:0.3.0]
at org.springframework.security.oauth2.server.authorization.authentication.ClientSecretAuthenticationProvider.authenticate(ClientSecretAuthenticationProvider.java:111) ~[spring-security-oauth2-authorization-server-0.3.0.jar:0.3.0]
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182) ~[spring-security-core-5.6.1.jar:5.6.1]
at org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter.doFilterInternal(OAuth2ClientAuthenticationFilter.java:120) ~[spring-security-oauth2-authorization-server-0.3.0.jar:0.3.0]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.15.jar:5.3.15]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter.doFilterInternal(OAuth2AuthorizationServerMetadataEndpointFilter.java:77) ~[spring-security-oauth2-authorization-server-0.3.0.jar:0.3.0]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.15.jar:5.3.15]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter.doFilterInternal(NimbusJwkSetEndpointFilter.java:85) ~[spring-security-oauth2-authorization-server-0.3.0.jar:0.3.0]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.15.jar:5.3.15]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter.doFilterInternal(OidcProviderConfigurationEndpointFilter.java:79) ~[spring-security-oauth2-authorization-server-0.3.0.jar:0.3.0]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.15.jar:5.3.15]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter.doFilterInternal(OAuth2AuthorizationEndpointFilter.java:140) ~[spring-security-oauth2-authorization-server-0.3.0.jar:0.3.0]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.15.jar:5.3.15]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:117) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.15.jar:5.3.15]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.15.jar:5.3.15]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.security.oauth2.server.authorization.web.ProviderContextFilter.doFilterInternal(ProviderContextFilter.java:63) ~[spring-security-oauth2-authorization-server-0.3.0.jar:0.3.0]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.15.jar:5.3.15]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.15.jar:5.3.15]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.security.web.debug.DebugFilter.invokeWithWrappedRequest(DebugFilter.java:90) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.security.web.debug.DebugFilter.doFilter(DebugFilter.java:78) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.security.web.debug.DebugFilter.doFilter(DebugFilter.java:67) ~[spring-security-web-5.6.1.jar:5.6.1]
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354) ~[spring-web-5.3.15.jar:5.3.15]
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267) ~[spring-web-5.3.15.jar:5.3.15]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.15.jar:5.3.15]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.15.jar:5.3.15]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.15.jar:5.3.15]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.15.jar:5.3.15]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.15.jar:5.3.15]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.15.jar:5.3.15]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:895) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1732) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
抛出异常的原因
背景是我使用了Spring Authorization Server 0.3.0的OAuth2的authorization_code授权类型,需要对资源所有者进行身份验证。但我对org.springframework.security.core.userdetails.User类进行扩展com.xxxx.CustomUser,导致在资源所有者登录成功后,在根据authorization_code获取token时的执行期间查询数据库表oauth2_authorization信息后再转化为具体对象抛出异常。
具体查看异常位置,在JdbcOAuth2AuthorizationService的内部类OAuth2AuthorizationRowMapper的mapRow方法中
public OAuth2Authorization mapRow(ResultSet rs, int rowNum) throws SQLException {
// ......
Map<String, Object> attributes = parseMap(getLobValue(rs, "attributes"));
// ......
}
分析发现getLobValue(rs, “attributes”)方法获取的是对应表oauth2_authorization的attributes字段,oauth2_authorization表结构如下:
CREATE TABLE `oauth2_authorization` (
`id` varchar(100) NOT NULL,
`registered_client_id` varchar(100) NOT NULL,
`principal_name` varchar(200) NOT NULL,
`authorization_grant_type` varchar(100) NOT NULL,
`attributes` blob,
`state` varchar(500) DEFAULT NULL,
`authorization_code_value` blob,
`authorization_code_issued_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`authorization_code_expires_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`authorization_code_metadata` blob,
`access_token_value` blob,
`access_token_issued_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`access_token_expires_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`access_token_metadata` blob,
`access_token_type` varchar(100) DEFAULT NULL,
`access_token_scopes` varchar(1000) DEFAULT NULL,
`oidc_id_token_value` blob,
`oidc_id_token_issued_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`oidc_id_token_expires_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`oidc_id_token_metadata` blob,
`refresh_token_value` blob,
`refresh_token_issued_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`refresh_token_expires_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`refresh_token_metadata` blob,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
attributes存储的是一个json数据,类似如下:
{
"@class": "java.util.Collections$UnmodifiableMap",
"java.security.Principal": {
"@class": "org.springframework.security.authentication.UsernamePasswordAuthenticationToken",
"authorities": [
"java.util.Collections$UnmodifiableRandomAccessList",
[
{
"@class": "org.springframework.security.core.authority.SimpleGrantedAuthority",
"authority": "ROLE_common"
}
]
],
"details": {
"@class": "org.springframework.security.web.authentication.WebAuthenticationDetails",
"remoteAddress": "127.0.0.1",
"sessionId": "C79A07F205A263A7423C8645D6A37BBC"
},
"authenticated": true,
"principal": {
"@class": "com.xxxx.CustomUser",
"password": null,
"username": "admin",
"authorities": [
"java.util.Collections$UnmodifiableSet",
[
{
"@class": "org.springframework.security.core.authority.SimpleGrantedAuthority",
"authority": "ROLE_common"
}
]
],
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true,
"permissions": [
"java.util.ArrayList",
[
{
"@class": "com.bulv.security.authorization.config.login.PermissionGrantedAuthority",
"targetDomain": "system/user/index",
"authority": "system:user:list"
}
]
]
},
"credentials": null
},
"org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest": {
"@class": "org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest",
"authorizationUri": "http://127.0.0.1:9200/oauth2/authorize",
"authorizationGrantType": {
"value": "authorization_code"
},
"responseType": {
"value": "code"
},
"clientId": "bulv-app",
"redirectUri": "http://127.0.0.1/oauth2/code",
"scopes": [
"java.util.Collections$UnmodifiableSet",
[
"user_info",
"openid",
"client.create"
]
],
"state": "wWxnPj3KQeJYjFZIWZ1e_KvHpZOvlyp8DK9tQczrg6E=",
"additionalParameters": {
"@class": "java.util.Collections$UnmodifiableMap",
"nonce": "-CyFrwZsxGOzu67cdN80FV1V893gRcprFy7DKDfTkg4"
},
"authorizationRequestUri": "http://127.0.0.1:9200/oauth2/authorize?response_type=code&client_id=bulv-app&scope=user_info%20openid%20client.create&state=wWxnPj3KQeJYjFZIWZ1e_KvHpZOvlyp8DK9tQczrg6E%3D&redirect_uri=http://127.0.0.1/oauth2/code&nonce=-CyFrwZsxGOzu67cdN80FV1V893gRcprFy7DKDfTkg4",
"attributes": {
"@class": "java.util.Collections$UnmodifiableMap"
}
},
"org.springframework.security.oauth2.server.authorization.OAuth2Authorization.AUTHORIZED_SCOPE": [
"java.util.Collections$UnmodifiableSet",
[
"user_info",
"openid",
"client.create"
]
]
}
异常翻译过来告诉我CustomUser类不在允许列表中。如果您认为此类可以安全地反序列化,请使用Jackson注释或提供 Mixin提供显式映射。如果序列化仅由受信任的来源完成,您还可以启用默认类型。详见 https://github.com/spring-projects/spring-security/issues/4370。说实话没看懂什么意思。但我大概明白和Jackson 有关系。后来在查看源码我发现在创建JdbcOAuth2AuthorizationService对象时属性authorizationParametersMapper实例化时,对authorizationParametersMapper的属性objectMapper默认设置了一些属性,而这些属性中正有被我覆盖的默认的org.springframework.security.core.userdetails.User类
public static class OAuth2AuthorizationParametersMapper implements Function<OAuth2Authorization, List<SqlParameterValue>> {
private ObjectMapper objectMapper = new ObjectMapper();
public OAuth2AuthorizationParametersMapper() {
ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
this.objectMapper.registerModules(securityModules);
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
}
// ...省略...
}
public final class SecurityJackson2Modules {
// ...省略...
/**
* @param loader the ClassLoader to use
* @return List of available security modules in classpath.
*/
public static List<Module> getModules(ClassLoader loader) {
List<Module> modules = new ArrayList<>();
for (String className : securityJackson2ModuleClasses) {
addToModulesList(loader, modules, className);
}
if (ClassUtils.isPresent("javax.servlet.http.Cookie", loader)) {
addToModulesList(loader, modules, webServletJackson2ModuleClass);
}
if (ClassUtils.isPresent("org.springframework.security.oauth2.client.OAuth2AuthorizedClient", loader)) {
addToModulesList(loader, modules, oauth2ClientJackson2ModuleClass);
}
if (ClassUtils.isPresent(javaTimeJackson2ModuleClass, loader)) {
addToModulesList(loader, modules, javaTimeJackson2ModuleClass);
}
return modules;
}
// ...省略...
}
public final class SecurityJackson2Modules {
private static final Log logger = LogFactory.getLog(SecurityJackson2Modules.class);
private static final List<String> securityJackson2ModuleClasses = Arrays.asList(
"org.springframework.security.jackson2.CoreJackson2Module",
"org.springframework.security.cas.jackson2.CasJackson2Module",
"org.springframework.security.web.jackson2.WebJackson2Module",
"org.springframework.security.web.server.jackson2.WebServerJackson2Module");
private static final String webServletJackson2ModuleClass = "org.springframework.security.web.jackson2.WebServletJackson2Module";
private static final String oauth2ClientJackson2ModuleClass = "org.springframework.security.oauth2.client.jackson2.OAuth2ClientJackson2Module";
private static final String javaTimeJackson2ModuleClass = "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule";
// ...省略...
}
public class CoreJackson2Module extends SimpleModule{
public CoreJackson2Module() {
super(CoreJackson2Module.class.getName(), new Version(1, 0, 0, null, null, null));
}
@Override
public void setupModule(SetupContext context) {
SecurityJackson2Modules.enableDefaultTyping(context.getOwner());
context.setMixInAnnotations(AnonymousAuthenticationToken.class, AnonymousAuthenticationTokenMixin.class);
context.setMixInAnnotations(RememberMeAuthenticationToken.class, RememberMeAuthenticationTokenMixin.class);
context.setMixInAnnotations(SimpleGrantedAuthority.class, SimpleGrantedAuthorityMixin.class);
context.setMixInAnnotations(Collections.<Object>unmodifiableSet(Collections.emptySet()).getClass(),
UnmodifiableSetMixin.class);
context.setMixInAnnotations(Collections.<Object>unmodifiableList(Collections.emptyList()).getClass(),
UnmodifiableListMixin.class);
context.setMixInAnnotations(User.class, UserMixin.class);
context.setMixInAnnotations(UsernamePasswordAuthenticationToken.class,
UsernamePasswordAuthenticationTokenMixin.class);
context.setMixInAnnotations(BadCredentialsException.class, BadCredentialsExceptionMixin.class);
}
}
顺着List securityModules = SecurityJackson2Modules.getModules(classLoader);这行代码我们一层一层向内跟进发现在CoreJackson2Module中存在context.setMixInAnnotations(User.class, UserMixin.class);这么一行代码,也就是被自定义CustomUser类替换的User。现在我们大体应该明白了异常的意思了,它是让我们为CustomUser设置一个类可以安全地反序列化它,使用Jackson注释或提供Mixin提供显式映射,就像UserMixin.class类似。
解决问题
我们需要对JdbcOAuth2AuthorizationService的属性authorizationParametersMapper在原来的基础上把CustomUser对应反序列化加入authorizationParametersMapper的objectMapper属性中,代码如下,CustomUserMixin可参考UserMixin相关内容我这里照着负责了一份,实际上要根据自己的实际要求来修改,我这里图方便,这样就可以解决异常问题了。
@Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository) {
JdbcOAuth2AuthorizationService service = new JdbcOAuth2AuthorizationService(jdbcTemplate,
registeredClientRepository);
JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper authorizationRowMapper = new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(
registeredClientRepository);
authorizationRowMapper.setLobHandler(new DefaultLobHandler());
ObjectMapper objectMapper = new ObjectMapper();
ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
objectMapper.registerModules(securityModules);
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
objectMapper.addMixIn(CustomUser.class, CustomUserMixin.class);
authorizationRowMapper.setObjectMapper(objectMapper);
service.setAuthorizationRowMapper(authorizationRowMapper);
return service;
}
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonDeserialize(using = CustomUserDeserializer.class)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(ignoreUnknown = true)
public interface CustomUserMixin {
}
public class CustomUserDeserializer extends JsonDeserializer<User> {
private static final TypeReference<Set<SimpleGrantedAuthority>> SIMPLE_GRANTED_AUTHORITY_SET = new TypeReference<Set<SimpleGrantedAuthority>>() {
};
/**
* This method will create {@link User} object. It will ensure successful object
* creation even if password key is null in serialized json, because credentials
* may be removed from the {@link User} by invoking
* {@link User#eraseCredentials()}. In that case there won't be any password key
* in serialized json.
*
* @param jp the JsonParser
* @param ctxt the DeserializationContext
* @return the user
* @throws IOException if a exception during IO occurs
* @throws JsonProcessingException if an error during JSON processing occurs
*/
@Override
public User deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
JsonNode jsonNode = mapper.readTree(jp);
Set<? extends GrantedAuthority> authorities = mapper.convertValue(jsonNode.get("authorities"),
SIMPLE_GRANTED_AUTHORITY_SET);
JsonNode passwordNode = readJsonNode(jsonNode, "password");
String username = readJsonNode(jsonNode, "username").asText();
String password = passwordNode.asText("");
boolean enabled = readJsonNode(jsonNode, "enabled").asBoolean();
boolean accountNonExpired = readJsonNode(jsonNode, "accountNonExpired").asBoolean();
boolean credentialsNonExpired = readJsonNode(jsonNode, "credentialsNonExpired").asBoolean();
boolean accountNonLocked = readJsonNode(jsonNode, "accountNonLocked").asBoolean();
User result = new User(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked,
authorities);
if (passwordNode.asText(null) == null) {
result.eraseCredentials();
}
return result;
}
private JsonNode readJsonNode(JsonNode jsonNode, String field) {
return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
}
}
遇到这个问题简单粗略的记录下,希望对遇见类似问题的小伙伴能有一点点的帮助。