Java安全框架(一)Spring Security
1.未登入,`SecurityContext`為null,建立一個新的`ThreadLocal`的`SecurityContext`填充`SecurityContextHolder`.
2.已登入,從`SecurityContextRepository`獲取的`SecurityContext`物件.
兩個請求完成後都清空`SecurityContextHolder`,並更新`SecurityContextRepository` | | `HeaderWriterFilter` | 新增頭資訊到響應物件 | | `CsrfFilter` | 防止csrf攻擊(跨站請求偽造)的過濾器 | | `LogoutFilter` | 登出處理 | | `UsernamePasswordAuthenticationFilter` | 獲取表單使用者名稱和密碼,處理基於表單的登入請求 | | `DefaultLoginPageGeneratingFilter` | 配置登入頁面 | | `BasicAuthenticationFilter` | 檢測和處理http basic認證,將結果放進`SecurityContextHolder` | | `RequestCacheAwareFilter` | 處理請求request的快取 | | `SecurityContextHolderAwareRequestFilter` | 包裝請求request,便於訪問`SecurityContextHolder` | | `AnonymousAuthenticationFilter` | 匿名身份過濾器,不存在使用者資訊時呼叫該過濾器 | | `SessionManagementFilter` | 檢測有使用者登入認證時做相應的session管理 | | `ExceptionTranslationFilter` | 處理`AccessDeniedException`訪問異常和`AuthenticationException`認證異常 | | `FilterSecurityInterceptor` | 檢測使用者是否具有訪問資源路徑的許可權 | ### 1.3 資料庫管理
UserDetails
null
)
*
* @throws UsernameNotFoundException if the user could not be found or the user has no
* GrantedAuthority
*/
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
```
`UserDetailService`該介面只有一個方法,通過方法名可以看出方法是通過使用者名稱來獲取使用者資訊的,但返回結果是`UserDetails`物件,`UserDetails`也是一個介面,介面中任何一個方法返回false使用者的憑證就會被視為無效。
```java
package org.springframework.security.core.userdetails;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import java.io.Serializable;
import java.util.Collection;
/**
* Provides core user information.
*
* * Implementations are not used directly by Spring Security for security purposes. They * simply store user information which is later encapsulated into {@link Authentication} * objects. This allows non-security related user information (such as email addresses, * telephone numbers etc) to be stored in a convenient location. *
* Concrete implementations must take particular care to ensure the non-null contract
* detailed for each method is enforced. See
* {@link org.springframework.security.core.userdetails.User} for a reference
* implementation (which you might like to extend or use in your code).
*
* @see UserDetailsService
* @see UserCache
*
* @author Ben Alex
*/
public interface UserDetails extends Serializable {
// ~ Methods
// ========================================================================================================
/**
* Returns the authorities granted to the user. Cannot return null
.
*
* @return the authorities, sorted by natural key (never null
)
*/
Collection getAuthorities(); //許可權集合
/**
* Returns the password used to authenticate the user.
*
* @return the password
*/
String getPassword(); //密碼
/**
* Returns the username used to authenticate the user. Cannot return null
.
*
* @return the username (never null
)
*/
String getUsername(); //使用者名稱
/**
* Indicates whether the user's account has expired. An expired account cannot be
* authenticated.
*
* @return true
if the user's account is valid (ie non-expired),
* false
if no longer valid (ie expired)
*/
boolean isAccountNonExpired(); //賬戶是否過期
/**
* Indicates whether the user is locked or unlocked. A locked user cannot be
* authenticated.
*
* @return true
if the user is not locked, false
otherwise
*/
boolean isAccountNonLocked(); //賬戶是否被鎖定
/**
* Indicates whether the user's credentials (password) has expired. Expired
* credentials prevent authentication.
*
* @return true
if the user's credentials are valid (ie non-expired),
* false
if no longer valid (ie expired)
*/
boolean isCredentialsNonExpired(); //證書是否過期
/**
* Indicates whether the user is enabled or disabled. A disabled user cannot be
* authenticated.
*
* @return true
if the user is enabled, false
otherwise
*/
boolean isEnabled(); //賬戶是否有效
}
```
這裡需要注意的是`Authentication`與`UserDetails`物件的區分,`Authentication`物件才是Spring Security使用的進行安全訪問控制使用者資訊的安全物件,實際上`Authentication`物件有未認證和已認證兩種狀態,在作為引數傳入認證管理器的時候,它是一個為認證的物件,它從客戶端獲取使用者的身份認證資訊,如使用者名稱、密碼,可以是從一個登入頁面,也可以是從cookie中獲取,並由系統自動生成一個`Authentication`物件,而這裡的`UserDetails`代表的是一個使用者安全資訊的源,這個源可以是從資料庫、LDAP伺服器、CA中心返回,Spring Security要做的就是將未認證的`Authentication`物件與`UserDetails`物件進行匹配,成功後將`UserDetails`物件中的許可權資訊拷貝到`Authentication`中,組成一個完整的`Authentication`物件,與其他元件進行共享。
```java
package org.springframework.security.core;
import java.io.Serializable;
import java.security.Principal;
import java.util.Collection;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.context.SecurityContextHolder;
public interface Authentication extends Principal, Serializable {
/**許可權集合*/
Collection getAuthorities();
/**獲取憑證*/
Object getCredentials();
/**獲取認證一些額外資訊*/
Object getDetails();
/**過去認證的實體*/
Object getPrincipal();
/**是否認證通過*/
boolean isAuthenticated();
/**
* See {@link #isAuthenticated()} for a full description.
*
* Implementations should always allow this method to be called with a
* false
parameter, as this is used by various classes to specify the
* authentication token should not be trusted. If an implementation wishes to reject
* an invocation with a true
parameter (which would indicate the
* authentication token is trusted - a potential security risk) the implementation
* should throw an {@link IllegalArgumentException}.
*
* @param isAuthenticated true
if the token should be trusted (which may
* result in an exception) or false
if the token should not be trusted
*
* @throws IllegalArgumentException if an attempt to make the authentication token
* trusted (by passing true
as the argument) is rejected due to the
* implementation being immutable or implementing its own alternative approach to
* {@link #isAuthenticated()}
*/
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
```
瞭解了Spring Security的上面三個物件,當我們需要資料庫管理使用者時,我們需要手動實現`UserDetailsService`物件中的`loadUserByUsername`方法,這就需要我們同時準備以下幾張資料表,分別是使用者表(user)、角色表(role)、許可權表(permission)、使用者和角色關係表(user_role)、許可權和角色關係表(permission_role),`UserDetails`中的使用者狀態通過使用者表裡的屬性去填充,`UserDetails`中的許可權集合則是通過角色表、許可權表、使用者和角色關係表、許可權和角色關係表構成的RBAC模型來提供,這樣就可以把使用者認證、使用者許可權集合放在資料庫中進行管理了。
### 1.4 許可權快取
Spring Security的許可權快取和資料庫管理有關,都是在使用者認證上做文章,所以都與`UserDetails`有關,與資料庫管理不同的是,Spring Security提供了一個可以快取`UserDetailsService`的實現類,這個類的名字是`CachingUserDetailsService`
```java
package org.springframework.security.authentication;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.cache.NullUserCache;
import org.springframework.util.Assert;
/**
*
* @author Luke Taylor
* @since 2.0
*/
public class CachingUserDetailsService implements UserDetailsService {
private UserCache userCache = new NullUserCache();
private final UserDetailsService delegate;
public CachingUserDetailsService(UserDetailsService delegate) {
this.delegate = delegate;
}
public UserCache getUserCache() {
return userCache;
}
public void setUserCache(UserCache userCache) {
this.userCache = userCache;
}
public UserDetails loadUserByUsername(String username) {
UserDetails user = userCache.getUserFromCache(username);
//快取中不存在UserDetails時,通過UserDetailsService載入
if (user == null) {
user = delegate.loadUserByUsername(username);
}
Assert.notNull(user, () -> "UserDetailsService " + delegate
+ " returned null for username " + username + ". "
+ "This is an interface contract violation");
//將UserDetials存入快取,並將UserDetails返回
userCache.putUserInCache(user);
return user;
}
}
```
`CachingUserDetailsService`類的構造接收一個用於真正載入`UserDetails`的`UserDetailsService`實現類,當需要載入`UserDetails`時,會首先從快取中獲取,如果快取中沒有`UserDetails`存在,則使用持有的`UserDetailsService`實現類進行載入,然後將載入後的結果存在快取中,`UserDetails`與快取的互動是通過`UserCache`介面來實現的,`CachingUserDetailsService`預設擁有一個`UserCache`的`NullUserCache()`實現。Spring Security提供的快取都是基於記憶體的快取,並且快取的`UserDetails`物件,在實際應用中一般會用到更多的快取,比如Redis,同時也會對許可權相關的資訊等更多的資料進行快取。
### 2.5 自定義決策
Spring Security在使用者身份認證通過後,會呼叫一個角色管理器判斷是否可以繼續訪問,[Spring Security核心處理流程(圖1-5)](#1.3 資料庫管理)中的`AccessDecisionManager`就是Spring Security的角色管理器,它對應的抽象類為`AbstractAccessDecisionManager`,要自定義決策管理器的話一般是繼承這個抽象類,而不是去實現介面。
```java
package org.springframework.security.access.vote;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.util.Assert;
/**
* Abstract implementation of {@link AccessDecisionManager}.
*
*
* Handles configuration of a bean context defined list of {@link AccessDecisionVoter}s * and the access control behaviour if all voters abstain from voting (defaults to deny * access). */ public abstract class AbstractAccessDecisionManager implements AccessDecisionManager, InitializingBean, MessageSourceAware { protected final Log logger = LogFactory.getLog(getClass()); private List
* If one or more voters cannot support the presented class, false
is
* returned.
*
* @param clazz the type of secured object being presented
* @return true if this type is supported
*/
public boolean supports(Class clazz) {
for (AccessDecisionVoter voter : this.decisionVoters) {
if (!voter.supports(clazz)) {
return false;
}
}
return true;
}
}
```
裡面的核心方法是`supports`方法,方法中用到一個`decisionVoters`的集合,集合中的型別是`AccessDecisionVoter`,這是Spring Security引入的一個投票器,有無許可權訪問的最終決定權就是由投票器來決定的。
```java
package org.springframework.security.access;
import java.util.Collection;
import org.springframework.security.core.Authentication;
public interface AccessDecisionVoter {
int ACCESS_GRANTED = 1;
int ACCESS_ABSTAIN = 0;
int ACCESS_DENIED = -1;
boolean supports(ConfigAttribute attribute);
boolean supports(Class clazz);
int vote(Authentication authentication, S object,
Collection