Spring security详细上手教学(二)用户管理
这章节主要学习:
- 如何使用UserDetails接口描述用户
- 在鉴权流中使用UserDetailsService
- 自定义的UserDetailsService实现
- 自定义的UserDetailsManager实现
- 在鉴权中使用JdbcUserDetialsManager
在Spring security中抽象了许多关于用户的接口
- UserDetails,描述了用户
- GrantedAuthority,定义用户可以执行的操作
- UserDetailsManager,扩展了UserDetails,描述了创建用户,修改、删除用户密码等等
在用户管理中,我们使用了UserDetailsService和UserDetailsManager两个接口。
UserDetailsService 只负责将用户信息按照用户名检索出来。这个功能在用户身份认证的时候会被用到。UserDetailsManager继承UserDetailsService ,扩展了增删改用户信息的方法。
1. 实现身份验证
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;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return List.of(() -> "read");}@Overridepublic String getPassword() {return "john";}@Overridepublic String getUsername() {return "12345";}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}
2.4 多种用户信息存储方式
实际使用中,我们往往会将用户信息存储在数据库中,或者从外部系统调用用户信息。那么我们就需要将获取到的用户信息和UserDetails结合起来。
如下代码,我们使用jpa的注解将User类的两种职责结合起来
@Entity
public class User implements UserDetails {@Idprivate int id;private String username;private String password;private String authority;@Overridepublic String getUsername() {return this.username;}@Overridepublic String getPassword() {return this.password;}public String getAuthority() {return this.authority;}@Overridepublic 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;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return List.of(user::getAuthority);}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUsername();}@Overridepublic boolean isAccountNonExpired() {return false;}@Overridepublic boolean isAccountNonLocked() {return false;}@Overridepublic boolean isCredentialsNonExpired() {return false;}@Overridepublic 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;}@Overridepublic 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 {@Beanpublic 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;}@Beanpublic PasswordEncoder passwordEncoder() {return NoOpPasswordEncoder.getInstance();}
}
3.5 LdapUserDetailsManager
LDAP没接触过,暂时省略