houkunlin commented on issue #12073: URL: https://github.com/apache/dubbo/issues/12073#issuecomment-1505336399
@jojocodeX 我这两天把我代码提取一下,尽量提供一个复现的完整代码出来。 目前与你贴出的代码有一点区别,我的 LoginUserDetails 是直接实现了接口,而不是继承 `public class LoginUserDetails implements UserDetails, CredentialsContainer`。下面先放一下我的几个类的代码,完整的简化复现版本,需要多花点时间来搞。 1. SpringBootSecurity第一次请求 `org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername` 方法发起 Dubbo 调用时,能够正常返回信息,此时多次调用 Dubbo 获取数据都正常 2. 当登录成功后,在 `org.springframework.security.web.authentication.AuthenticationSuccessHandler` 再次发起一次 Dubbo 调用就会报错 ```java @JsonDeserialize(using = LoginUserDetailsDeserializer.class) @Getter @EqualsAndHashCode @Accessors(fluent = true) public class LoginUserDetails implements UserDetails, CredentialsContainer { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; /** * 机构ID */ private final Long orgId; /** * 部门ID */ private final Long deptId; /** * 用户ID */ private final Long userId; /** * 用户名,实际存储的是用户ID */ private final String username; /** * 权限代码列表 */ private final Set<GrantedAuthority> authorities; /** * 账号未过期 */ private final boolean accountNonExpired; /** * 账号未锁定 */ private final boolean accountNonLocked; /** * 密码未过期 */ private final boolean credentialsNonExpired; /** * 是否启用 */ private final boolean enabled; /** * 用户密码 */ private String password; public LoginUserDetails(final LoginUserVo user, final String credential, final Set<String> permissions) { this.username = ObjectUtils.getDisplayString(user.getId()); this.password = credential; this.enabled = user.isEnabled(); this.accountNonExpired = !user.isExpired(); this.credentialsNonExpired = true; this.accountNonLocked = true; this.authorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toSet()); this.orgId = user.getOrgId(); this.deptId = user.getDeptId(); this.userId = user.getId(); } public LoginUserDetails(Long orgId, Long deptId, Long userId, String username, Set<GrantedAuthority> authorities, boolean accountNonExpired, boolean accountNonLocked, boolean credentialsNonExpired, boolean enabled, String password) { this.orgId = orgId; this.deptId = deptId; this.userId = userId; this.username = username; this.authorities = authorities; this.accountNonExpired = accountNonExpired; this.accountNonLocked = accountNonLocked; this.credentialsNonExpired = credentialsNonExpired; this.enabled = enabled; this.password = password; } @Override public void eraseCredentials() { this.password = null; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return accountNonExpired; } @Override public boolean isAccountNonLocked() { return accountNonLocked; } @Override public boolean isCredentialsNonExpired() { return credentialsNonExpired; } @Override public boolean isEnabled() { return enabled; } } public class LoginUserDetailsDeserializer extends JsonDeserializer<LoginUserDetails> { private static final Logger logger = LoggerFactory.getLogger(LoginUserDetailsDeserializer.class); private static final TypeReference<Set<SimpleGrantedAuthority>> SIMPLE_GRANTED_AUTHORITY_SET = new TypeReference<Set<SimpleGrantedAuthority>>() { }; /** * This method will create {@link LoginUserDetails} object. It will ensure successful object * creation even if password key is null in serialized json, because credentials may * be removed from the {@link LoginUserDetails} by invoking {@link LoginUserDetails#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 LoginUserDetails deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { ObjectMapper mapper = (ObjectMapper) jp.getCodec(); JsonNode jsonNode = mapper.readTree(jp); Long orgId = readJsonNode(jsonNode, "orgId").asLong(); Long deptId = readJsonNode(jsonNode, "deptId").asLong(); Long userId = readJsonNode(jsonNode, "userId").asLong(); String username = readJsonNode(jsonNode, "username").asText(); Set<? extends GrantedAuthority> authorities = mapper.convertValue(jsonNode.get("authorities"), SIMPLE_GRANTED_AUTHORITY_SET); boolean enabled = readJsonNode(jsonNode, "enabled").asBoolean(); boolean accountNonExpired = readJsonNode(jsonNode, "accountNonExpired").asBoolean(); boolean credentialsNonExpired = readJsonNode(jsonNode, "credentialsNonExpired").asBoolean(); boolean accountNonLocked = readJsonNode(jsonNode, "accountNonLocked").asBoolean(); JsonNode passwordNode = readJsonNode(jsonNode, "password"); String password = passwordNode.asText(""); logger.info("反序列化LoginUserDetails:{}", userId); LoginUserDetails result = new LoginUserDetails( orgId, deptId, userId, username, (Set<GrantedAuthority>) authorities, accountNonExpired, accountNonLocked, credentialsNonExpired, enabled, password); 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(); } } @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) @JsonDeserialize(using = LoginUserDetailsDeserializer.class) @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE) @JsonIgnoreProperties(ignoreUnknown = true) public abstract class LoginUserDetailsMixin { } public class LoginUserObjectMapperCodecCustomer implements ObjectMapperCodecCustomer { @Override public void customize(ObjectMapperCodec objectMapperCodec) { objectMapperCodec.addModule(new LoginUserModule()); // objectMapperCodec.configureMapper(new Consumer<ObjectMapper>() { // @Override // public void accept(ObjectMapper objectMapper) { // objectMapper.addMixIn(LoginUserDetails.class, LoginUserDetailsMixin.class); // } // }); } public static class LoginUserModule extends SimpleModule { public LoginUserModule() { super(LoginUserModule.class.getName(), new Version(1, 0, 0, null, null, null)); } @Override public void setupModule(SetupContext context) { // SecurityJackson2Modules.enableDefaultTyping(context.getOwner()); context.setMixInAnnotations(LoginUserDetails.class, LoginUserDetailsMixin.class); // context.setMixInAnnotations(User.class, UserMixin.class); } } } // 文件:src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.spring.security.jackson.ObjectMapperCodecCustomer loginUserObjectMapperCodecCustomer=com.houkunlin.login.LoginUserObjectMapperCodecCustomer ``` -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: notifications-unsubscr...@dubbo.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@dubbo.apache.org For additional commands, e-mail: notifications-h...@dubbo.apache.org