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沒接觸過,暫時省略