Spring security详细上手教学(二)用户管理

Spring security详细上手教学(二)用户管理

这章节主要学习:

  • 如何使用UserDetails接口描述用户
  • 在鉴权流中使用UserDetailsService
  • 自定义的UserDetailsService实现
  • 自定义的UserDetailsManager实现
  • 在鉴权中使用JdbcUserDetialsManager

在Spring security中抽象了许多关于用户的接口

  • UserDetails,描述了用户
  • GrantedAuthority,定义用户可以执行的操作
  • UserDetailsManager,扩展了UserDetails,描述了创建用户,修改、删除用户密码等等

image-20250425160101340

在用户管理中,我们使用了UserDetailsService和UserDetailsManager两个接口。
UserDetailsService 只负责将用户信息按照用户名检索出来。这个功能在用户身份认证的时候会被用到。UserDetailsManager继承UserDetailsService ,扩展了增删改用户信息的方法。

1. 实现身份验证

image-20250425100909330

2. 描述用户

2.1 UserDetails接口

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

如果我们不需要设置用户过期等逻辑,那么我们可以直接让isAccountNonExpired返回true即可,同理其他三个接口。
getAuthorities接口返回用户访问资源的权限

isXxxx这四个接口,isXxxNon这种方式好像让人比较困惑,其实这些接口按照身份验证失败返回false,相反返回true这样的逻辑来设定的。

2.2 GrantedAuthority

public interface GrantedAuthority extends Serializable {
	String getAuthority();
}

需要实现getAuthority方法来返回权限的名称String。这个接口只有一个方法,所以可以用lambda表达式来实现。
我们还可以使用SimpleGrantedAuthority类来创建。

GrantedAuthority g1 = () -> "READ";
SimpleGrantedAuthority g2 = new SimpleGrantedAuthority("READ");

2.3 UserDetails的最小实现

public class DummyUser implements UserDetails {
	private final String username;
	private final String password;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(() -> "read");
    }

    @Override
    public String getPassword() {
        return "john";
    }

    @Override
    public String getUsername() {
        return "12345";
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

2.4 多种用户信息存储方式

实际使用中,我们往往会将用户信息存储在数据库中,或者从外部系统调用用户信息。那么我们就需要将获取到的用户信息和UserDetails结合起来。

如下代码,我们使用jpa的注解将User类的两种职责结合起来

@Entity
public class User implements UserDetails {
    @Id
    private int id;
    private String username;
    private String password;
    private String authority;
    
    @Override
    public String getUsername() {
    	return this.username;
    }
    
    @Override
    public String getPassword() {
    	return this.password;
    }
    
    public String getAuthority() {
    	return this.authority;
    }
    
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    	return List.of(() -> authority);
    }
    // Omitted code
}

我们还可以利用设计模式中的适配器模式进行解耦,使得我们的类既能适配jps的模型也可以适配UserDetails

public class SecurityUserAdaptor implements UserDetails {
    private final User user;

    public SecurityUserAdaptor(User user) {
        this.user = user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(user::getAuthority);
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return false;
    }

    @Override
    public boolean isAccountNonLocked() {
        return false;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return false;
    }

    @Override
    public boolean isEnabled() {
        return false;
    }
}

3. 如何管理用户

3.1 理解UserDetailsService约定

public interface UserDetailsService {
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

身份验证的事项调用loadUserByUsername获取用户信息。

3.2 实现UserDetailsService

public class InmemoryUserDetailsService implements UserDetailsService {
    private final List<UserDetails> users;
    
    public InmemoryUserDetailsService(List<UserDetails> users) {
        this.users = users;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return users.stream()
                .filter(user -> user.getUsername().equals(username))
                .findFirst()
                .orElseThrow(() -> new UsernameNotFoundException("User not found"));
    }
}

这里自定义了一个用户list将用户信息保存在内存中。
loadUserByUsername方法从List中根据用户名筛选出来用户信息

3.3 UserDetailsManager扩展UserDetailsService接口

public interface UserDetailsManager extends UserDetailsService {
    void createUser(UserDetails user);
    void updateUser(UserDetails user);
    void deleteUser(String username);
    void changePassword(String oldPassword, String newPassword);
    boolean userExists(String username);
}

3.4 JdbcUserDetailsManager

需要先引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
</dependency>

JdbcUserDetailsManager类管理存储在SQL数据库中的用户信息,通过JDBC链接数据库。

@Configuration
public class ProjectConfig {
    @Bean
    public UserDetailsService userDetailsManager(DataSource dataSource) {
        String usersByUsernameQuery = "select username, password, enabled [CA] from users where username = ?";
        String authsByUserQuery = "select username, authority [CA] from spring.authorities where username = ?";

        var userDetailsManager = new JdbcUserDetailsManager(dataSource);
        userDetailsManager.setUsersByUsernameQuery(usersByUsernameQuery);
        userDetailsManager.setAuthoritiesByUsernameQuery(authsByUserQuery);
        return userDetailsManager;
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

3.5 LdapUserDetailsManager

LDAP没接触过,暂时省略

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值