使用用户名或电子邮件的 Spring Security

Posted

技术标签:

【中文标题】使用用户名或电子邮件的 Spring Security【英文标题】:Spring Security using both username or email 【发布时间】:2012-12-16 20:41:03 【问题描述】:

我在我的 Spring MVC 应用程序中使用 Spring Security。

JdbcUserDetailsManager 使用以下验证查询进行初始化:

select username, password, enabled from user where username = ?

当局正在这里加载:

select u.username, a.authority from user u join authority a on u.userId = a.userId where username = ?

我想让它让用户可以使用用户名和电子邮件登录。有没有办法修改这两个查询来实现?还是有更好的解决方案?

【问题讨论】:

【参考方案1】:

您可以分别在users-by-username-queryauthorities-by-username-query 属性中的<jdbc-user-service> 标签中定义自定义查询。

<jdbc-user-service data-source-ref="" users-by-username-query="" authorities-by-username-query=""/>

更新

您可以创建实现org.springframework.security.core.userdetails.UserDetailsService 的类并将您的应用程序配置为将其用作身份验证源。在您的自定义 UserDetails 服务中,您可以执行从数据库获取用户所需的查询。

【讨论】:

这正是我已经在做的事情,我已经将它们都包含在我的问题中。问题是我必须自定义它们以包含用户名和电子邮件。 您还没有提到您知道如何配置它们。你在问“有没有办法修改这两个查询来实现这一点?”。【参考方案2】:

不幸的是,仅仅通过更改查询没有简单的方法来做到这一点。问题是 spring security 期望 users-by-username-query 和 authority-by-username-query 有一个参数(用户名),所以如果您的查询包含两个参数,例如

username = ? or email = ?

查询将失败。

您可以做的是实现您自己的 UserDetailsS​​ervice,它将执行查询(或查询)以通过用户名或电子邮件搜索用户,然后将此实现用作 Spring Security 中的身份验证提供程序像

这样的配置
  <authentication-manager>
    <authentication-provider user-service-ref='myUserDetailsService'/>
  </authentication-manager>

  <beans:bean id="myUserDetailsService" class="xxx.yyy.UserDetailsServiceImpl">
  </beans:bean>

【讨论】:

我查看了该类,但在确定应该覆盖哪些方法时遇到了一些麻烦。有什么指点吗? 我想我现在看到了:loadUserByUsername(String username)【参考方案3】:

如果我理解正确,那么问题是您想在两个不同的数据库列中查找用户输入的用户名。

当然,您可以通过自定义 UserDetailsS​​ervice 来做到这一点。

public class CustomJdbcDaoImpl extends JdbcDaoImpl 

    @Override
    protected List<GrantedAuthority> loadUserAuthorities(String username) 
    return getJdbcTemplate().query(getAuthoritiesByUsernameQuery(), new String[] username, username, new RowMapper<GrantedAuthority>() 
            public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException 
              .......
            
        );
    

    @Override
    protected List<UserDetails> loadUsersByUsername(String username) 
        return getJdbcTemplate().query(getUsersByUsernameQuery(), new String[] username, username, new RowMapper<UserDetails>() 
            public UserDetails mapRow(ResultSet rs, int rowNum) throws SQLException 
                 .......
            
        );

这个类的 bean 配置看起来像这样。

<beans:bean id="customUserDetailsService" class="com.xxx.CustomJdbcDaoImpl">
    <beans:property name="dataSource" ref="dataSource"/>
    <beans:property name="usersByUsernameQuery">
        <beans:value> YOUR_QUERY_HERE</beans:value>
    </beans:property>
    <beans:property name="authoritiesByUsernameQuery">
        <beans:value> YOUR_QUERY_HERE</beans:value>
    </beans:property>
</beans:bean>

您的查询将与此类似

select username, password, enabled from user where (username = ? or email = ?)
select u.username, a.authority from user u join authority a on u.userId = a.userId where (username = ? or email = ?)

【讨论】:

【参考方案4】:

我遇到了同样的问题,在尝试了很多不同的查询和过程之后......我发现这很有效:

public void configAuthentication(AuthenticationManagerBuilder auth)
        throws Exception 
    // Codificación del hash
    PasswordEncoder pe = new BCryptPasswordEncoder();

    String userByMailQuery = "SELECT mail, password, enabled FROM user_ WHERE mail = ?;";
    String userByUsernameQuery = "SELECT mail, password, enabled FROM user_ WHERE username=?";
    String roleByMailQuery = "SELECT mail, authority FROM role WHERE mail =?;";

    auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder(pe)
            .usersByUsernameQuery(userByMailQuery)
            .authoritiesByUsernameQuery(roleByMailQuery);

    auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder(pe)
            .usersByUsernameQuery(userByUsernameQuery)
            .authoritiesByUsernameQuery(roleByMailQuery);


它只是用两个查询重复配置。

【讨论】:

【参考方案5】:

这是我发现的一种解决方法。基本上我将用户名和电子邮件地址与中间的分隔符连接起来(例如'jsmith~johnsmith@gmail.com'),并检查参数是否匹配分隔符的左侧或是否匹配右侧分隔符:

select username, password, enabled 
  from users 
 where ? in (substring_index(concat(username, '~',email),'~', 1), 
             substring_index(concat(username, '~',email),'~', -1))

如果您担心用户名或电子邮件中可能存在分隔符(例如 ~),请改用非标准分隔符(例如 X'9C')

【讨论】:

【参考方案6】:

您可以像下面的代码一样使用您的 UserDetailesService.and 配置。

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter 

    @Autowired
    private MyUserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception 
        auth.userDetailsService(userDetailsService);
    

关键是您不需要返回具有相同用户名的用户,您可以获得用户电子邮件并返回具有用户名的用户。代码将类似于下面的代码。

@Service
public class MyUserDetailsService implements UserDetailsService 

    @Override
    public UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException 
        var user = /** here search user via jpa or jdbc by username or email **/;
        if(user == null ) 
            throw new UsernameNotFoundExeption();
        else return new UserDetail(user); // You can implement your user from UserDerail interface or create one;
    


tip* UserDetail 是一个接口,您可以创建一个或使用 Spring Default。

【讨论】:

【参考方案7】:

你可以像这样配置 UserDetailesService 类。

public class UserDetailsServiceImpl implements UserDetailsService

    @Autowired
    private UserRepository userRepository;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 
        User user = this.userRepository.getUserByEmailOrUserName(username); //for fetch user
        if(user==null) 
            throw new UsernameNotFoundException("User doesn't exists");
        
        UserDetailsImpl customUserDetails = new UserDetailsImpl(user);
        return customUserDetails;
    


您的查询将与此类似

select * from user where email = ?或用户名 = ?

用于获取用户数据的 UserRepository 类

@Repository
public interface UserRepository extends JpaRepository<User, Integer>

    @Query("from user where email = :u or username = :u")
    public User getUserByEmailOrUserName(@Param("u") String username);
    

您也可以在登录时添加电话号码。

【讨论】:

以上是关于使用用户名或电子邮件的 Spring Security的主要内容,如果未能解决你的问题,请参考以下文章

Spring boot,如何隐藏或防止加载实体的某些属性

在 Grails 的 Spring Security 插件中使用电子邮件和用户名作为登录名

Spring REST Api——访问存储库中的用户详细信息

Spring Security 的用户名别名

Spring Boot通过Controller从Authentication获取用户名

使用用户名作为电子邮件,Spring Security UI 插件