You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@dubbo.apache.org by "houkunlin (via GitHub)" <gi...@apache.org> on 2023/04/12 14:03:36 UTC

[GitHub] [dubbo] houkunlin commented on issue #12073: 与 SpringSecurity 合并使用时,使用自定义的 UserDetails 对象,用户登录成功后无法发起请求

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-unsubscribe@dubbo.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org