只要和用戶打交道的系統基本都須要進行權限管理,否則哪一天操做不當給你刪庫了怎麼辦。開源的權限管理框架有SpringSecurity、Shiro,權限管理模型有RBAC、ACL等,是選擇開源框架好仍是基於權限管理模型造輪子好,必須都調研一下選一個適合公司業務的實現方式,首先先調研學習一波SpringSecuritycss
SpringSecurity
中的一些核心類SpringSecurity
基於角色的權限校驗SpringSecurity
的不足由於對SpringSecurity
只是調研性的學習,因此這裏並不會對源碼進行介紹。權限管理就是受權、鑑權,要想鑑權必須首先登錄拿到用戶信息,可是這裏也不會去講登錄、登出以及分佈式session
管理,只會介紹一下鑑權過程當中的核心類,瞭解一下核心類能夠快速整合SpringSecurity
框架。java
UsernamePasswordAuthenticationFilter
=> 用戶登錄驗證,但這個類裏並不會正真去進行登錄校驗,而是經過ProviderManagerProviderManager
=> 這個類裏有一個List<AuthenticationProvider>
,提供了不一樣的校驗方式,只要其中一個經過便可。通常咱們什麼都不配的狀況下是根據用戶名和密碼,這時候AuthenticationProvider
實現類爲AbstractUserDetailsAuthenticationProvider
AbstractUserDetailsAuthenticationProvider
=> 其登錄校驗是經過子類的additionalAuthenticationChecks
方法完成的DaoAuthenticationProvider
=> AbstractUserDetailsAuthenticationProvider
的惟一子類,若是咱們設定的密碼(能夠基於內存也能夠基於數據庫)和傳過來的密碼比對不上登錄校驗失敗UserDetailsService
=> 經過這個接口惟一的方法loadUserByUsername
返回咱們設定的用戶名和密碼等用戶信息(被封裝爲UserDetails
的實現類)小結:登錄驗證就是拿到客戶端的用戶名密碼和咱們設定的用戶名密碼(即UserDetails
的實現類)進行比對,拿到咱們設定的用戶名密碼是經過UserDetailsService.loadUserByUsername(String userName)
實現的[劃重點,一會要考],框架實現的UserDetailsService
通常無法知足項目要求,就須要本身手動實現了,同時若是框架自帶的UserDetails
的實現類無法知足要求咱們也能夠本身實現UserDetailsgit
FilterSecurityInterceptor
=> 基於角色的權限校驗攔截器,調用父類AbstractSecurityInterceptor
的beforeInvocation
方法進行鑑權AbstractSecurityInterceptor
=> 調用AccessDecisionManager
實現類的decide
方法進行鑑權,因此如何想自定義鑑權方式能夠寫一個類而後實現AccessDecisionManager
AffirmativeBased
=> 默認使用的AccessDecisionManager
實現類,調用AccessDecisionVoter
實現類的vote方法進行鑑權,返回1權限校驗經過,其實跟蹤到最後其實仍是比對字符串不能說是投票吧,方法名容易讓人誤解WebExpressionVoter
=> 默認使用的AccessDecisionVoter
實現類,調用Authentication的authentication
方法進行鑑權SecurityExpressionOperations
=> 獲取Authentication
對象接口SecurityExpressionRoot
=> SecurityExpressionOperations
實現類小結:通常不自定鑑權方式的話這些咱們均可以不須要管,雖然層層調用了不少層,其實實質就是判斷當前的用戶所包含的權限列表中是否包含訪問指定url所須要的權限github
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>1.4.1.RELEASE</version>
</dependency>
複製代碼
public class UserDTO implements UserDetails {
/**
* 用戶名
* */
private String username;
/**
* 密碼
* */
private String password;
/**
* 角色列表
* */
private List<String> roleList;
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public List<String> getRoleList() {
return roleList;
}
public void setRoleList(List<String> roleList) {
this.roleList = roleList;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorityList = new ArrayList<>();
for (String role : roleList) {
authorityList.add(new SimpleGrantedAuthority(role));
}
return authorityList;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
複製代碼
實現類須要實現接口,這裏咱們將查詢到的roleList
字符串封裝到SimpleGrantedAuthority
中,SimpleGrantedAuthority
是GrantedAuthority
的一個實現類,若是默認實現無法知足需求可本身從新實現。UserDetail
是SpringSecurity
和應用之間的橋樑,無論你數據庫怎麼建,只要你最後將用戶信息和權限的關係封裝爲UserDetails
,SpringSecurity
就能夠按它本身的機制進行權限校驗web
public class UserDetailsServiceImpl implements UserDetailsService {
@Resource
private UsersService usersService;
/**
* 根據用戶名獲取用戶信息
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Users users = new Users();
users.setUsername(username);
List<Users> usersList = usersService.selectList(users);
return buildUserDTO(usersList);
}
/**
* 封裝UserDTO對象
*
* @param usersList
* @return
* */
private UserDTO buildUserDTO(List<Users> usersList) {
UserDTO userDTO = new UserDTO();
userDTO.setUsername(usersList.get(0).getUsername());
userDTO.setPassword(usersList.get(0).getPassword());
List<String> roleList = new ArrayList<>();
for (Users users : usersList) {
roleList.add(String.format("ROLE_%s", users.getRole()));
}
userDTO.setRoleList(roleList);
return userDTO;
}
}
複製代碼
該類做用就是將用戶信息和權限信息從數據庫查找到封裝爲一個UserDetails
返回spring
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 開啓方法級安全驗證
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated() //任何請求,登陸後能夠訪問
.and().formLogin().permitAll(); //登陸頁面用戶任意訪問
// 關閉CSRF跨域
http.csrf().disable();
}
@Override
public void configure(WebSecurity web) throws Exception {
// 設置攔截忽略文件夾,能夠對靜態資源放行
web.ignoring().antMatchers("/css/**", "/js/**", "/templates/**");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//1.設置自定義userDetailService
//2.校驗時指定密碼解碼方式
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
}
複製代碼
這個類裏配置了登錄頁面以及一些簡單權限設置,好比任何請求登錄後可訪問、登陸頁面全部人能夠訪問。由於經過@EnableGlobalMethodSecurity
註解開啓了方法級別驗證,在這個方法裏沒有配置方法級別的權限。同時經過指定本身實現的UserDetailsService
以及密碼解碼方式,若是不指定密碼解碼方式將會報錯在最新的SpringSecurity
版本中數據庫
@RestController
@RequestMapping("/api/user")
public class UsersController {
@GetMapping("/guest")
@PreAuthorize("hasAnyRole('guest')")
public Object guest() {
return "hello guest";
}
@PreAuthorize("hasAnyRole('admin')")
@GetMapping("/admin")
public Object admin() {
return "hello admin";
}
}
複製代碼
經過@PreAuthorize
來進行權限控制,在hasAnyRole
中寫入訪問該api具備的權限(角色),除了可使用@PreAuthorize
註解,還可使用@Secured
、@PostAuthorize
註解api
對項目代碼有入侵跨域
不夠通用,全部須要權限校驗的系統都須要整合SpringSecurity
框架,不一樣應用系統數據庫設計不一樣,UserDetail
通常需本身實現【只是舉個例子,實際開發過程當中重寫的類可能更多】緩存
角色應該是動態的,但經過SpringSecurity
配置的角色是靜態的,在數據庫新添角色時必須對代碼進行修改,不然沒法使用
並非一個RBAC
的設計模型而是一個ACL
模型,角色權限的劃分並非特別清楚,權限也能夠是角色,若是想基於RBAC
的權限校驗就必須本身從新寫權限校驗方法
權限管理粒度不夠細,好比無法支持到方法級別的權限校驗,想支持更細粒度的權限必須本身寫權限校驗
提供的三種緩存用戶信息的方式,分別爲NullUserCache
、EhCacheBasedUserCache
、SpringCacheBasedUserCache
。第一種永遠return null
,至關於沒使用緩存,後二者是內存緩存,在分佈式部署中會出現緩存命中率低、緩存不一致的狀況,須要本身實現緩存
僅是本身的一些見解,若有紕漏歡迎指正
最後附:項目源碼