在使用UserDetailsS​​ervice时,是否可以在身份验证成功后加载授予的权限和其他用户信息?

时间:2017-07-31 11:54:28

标签: spring-security

我已经能够启动并运行基于Spring Security的应用程序,直到现在它已满足我的所有要求。

我对如何在Spring Security中使用UserDetailsService存在疑问。我有一个自定义的'UserDetailsS​​ervice'实现,就像这样 -

public class CustomUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        Optional<UserDetailsDto> userDetailsByEmail = // Load userDetailsDto from database

        if (!userDetailsByEmail.isPresent()) {
            throw new UsernameNotFoundException("Username does not exists");
        }

        UserDetailsDto userDetailsDto = userDetailsByEmail.get();

        List<Role> roles = roleService.listByEmail(username);
        List<ModulePermission> modulePermissions = modulePermissionService.listByUserId(userDetailsDto.getId());
        UserType userType = userTypeService.getByUserId(userDetailsDto.getId());

        return new LoggedInUser(userDetailsDto, roles, modulePermissions, userType);
    }
}

LoggedInUser是Spring Security的org.springframework.security.core.userdetails.User类的扩展,就像这样 -

public class LoggedInUser extends User {

    private static final long serialVersionUID = -1L;

    private Long userId;
    private boolean firstLogin;
    private UserType userType;
    private List<ModulePermission> modulePermissions;

    private String firstName;
    private String lastName;
    private String contactNo;

    public LoggedInUser(UserDetailsDto userDetailsDto, List<Role> roles, List<ModulePermission> modulePermissions,
            UserType userType) {

        super(userDetailsDto.getEmail(), userDetailsDto.getPassword(), userDetailsDto.getEnabledStatus().getValue(),
                userDetailsDto.getAccountNonExpiredStatus().getValue(), true,
                userDetailsDto.getAccountNonLockedStatus().getValue(),
                roles.stream().map(role -> new SimpleGrantedAuthority(role.getId())).collect(Collectors.toList()));

        this.modulePermissions = modulePermissions;

        this.userType = userType;
        this.userId = userDetailsDto.getId();
        this.firstLogin = userDetailsDto.getIsFirstLoginStatus().getValue();
        this.firstName = userDetailsDto.getFirstName();
        this.lastName = userDetailsDto.getLastName();
        this.contactNo = userDetailsDto.getContactNo();
    }

    public List<ModulePermission> getModulePermissions() {
        return Collections.unmodifiableList(modulePermissions);
    }

    public UserType getUserType() {
        return userType;
    }

    public Long getUserId() {
        return userId;
    }

    public boolean isFirstLogin() {
        return firstLogin;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public String getContactNo() {
        return contactNo;
    }

    public void setFirstLogin(boolean firstLogin) {
        this.firstLogin = firstLogin;
    }
}

现在,要配置Spring Security以使用我的CustomUserDetailsService,我在安全配置中执行以下操作 -

@Bean
public UserDetailsService customUserDetailsService() {
    return new CustomUserDetailsService();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.antMatcher(SuperAdminConstant.UrlConstant.ANT_MATCHER_PATH)
            .userDetailsService(customUserDetailsService())
            .formLogin(// further configuration)
}

这没有任何问题。

但请注意,在CustomUserDetailsService中,即使在用户成功通过身份验证之前也会执行多个数据库查询(这是因为Spring Security已创建DaoAuthenticationProvider,一个UserDetails实现(在我的例子中,LoggedInUser),并在从UserDetailsService(在我的情况下,CustomUserDetailsService)中检索到该对象后对该对象执行各种检查

考虑用户输入了正确的用户名,但密码错误。在这种情况下,高级认证流程将是 -

  1. CustomUserDetailsService将被称为
  2. 执行第一次查询以验证用户名并加载用户详细信息(由于用户名不正确,因此不会引发UsernameNotFoundException
  3. 执行第二次查询以检索角色
  4. 执行
  5. 第三次查询以检索模块权限
  6. 执行第四次查询以检索用户类型
  7. DaoAuthenticationProvider检查密码,发现密码错误,然后抛出BadCredentialsException
  8. 可以看出,在认证过程完成之前执行了总共4个查询,其中只有1个在此阶段是必不可少的(第一个验证用户名的查询)。

    此问题的一个解决方案可以是取消UserDetailsService altogeather的使用,而是使用自定义AuthenticationProvider

    public class CustomAuthenticationProvider implements AuthenticationProvider {
    
        @Override
        Authentication authenticate(Authentication authentication)
                throws AuthenticationException {
    
            // Customize the authentication logic here, and retrieve 
            // user information only if everything is correct.
        }
    }
    

    但是通过这种方法也意味着我必须复制DaoAuthenticationProviderAbstractUserDetailsAuthenticationProvider提供的代码和功能,其中包括手动检查用户帐户状态标志(accountNonExpired,accountNonLocked等)并抛出异常。

    所以我想知道天气可以执行身份验证逻辑,只有在身份验证成功后才能检索用户信息,并且可以重新使用Spring Security提供的大多数身份验证逻辑。

    任何想法都将深受赞赏。

1 个答案:

答案 0 :(得分:0)

您可以编写AuthenticationSuccessHandler的实现:

@Component
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {

    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        LoggedInUser loggedInUser = (LoggedInUser)authentication.getPrincipal();

        List<Role> roles = roleService.listByEmail(username);
        List<ModulePermission> modulePermissions = modulePermissionService.listByUserId(userDetailsDto.getId());
        UserType userType = userTypeService.getByUserId(userDetailsDto.getId());

        // Set roles after authentication succeeds

        loggedInUser.setRoles(roles);
        loggedInUser.setModulePermissions(modulePermissions);
        loggedInUser.setUserType(userType);
    }
}

身份验证成功后,您可以从安全上下文中获取登录用户并设置其他属性。