文章主要分三部分
1、Spring Security的架構及核心元件:(1)認證;(2)許可權攔截;(3)資料庫管理;(4)許可權快取;(5)自定義決策;
2、環境搭建與使用,使用當前熱門的Spring Boot來搭建環境,結合專案中實際的例子來做幾個Case;
3、Spring Security的優缺點總結,結合第二部分中幾個Case的實現來總結Spring Security的優點和缺點。
1、Spring Security介紹
整體介紹,Spring Security為基於J2EE開發的企業應用軟體提供了全面的安全服務,特別是使用Spring開發的企業軟體專案,如果你熟悉Spring,尤其是Spring的依賴注入原理,這將幫助你更快掌握Spring Security,目前使用Spring Security有很多原因,通常因為在J2EE的Servlet規範和EJB規範中找不到典型應用場景的解決方案,提到這些規範,特別要指出的是它們不能在WAR或EAR級別進行移植,這樣如果你需要更換伺服器環境,就要在新的目標環境中進行大量的工作,對你的應用進行重新配置安全,使用Spring Security就解決了這些問題,也為你提供了很多很有用的可定製的安全特性。
Spring Security包含三個主要的元件:SecurityContext
、AuthenticationManager
、AccessDecisionManager
.
1.1 認證
Spring Security提供了很多過濾器,它們攔截Servlet請求,並將這些請求轉交給認證處理過濾器和訪問決策過濾器進行處理,並強制安全性認證使用者身份和使用者許可權以達到保護WEB資源的目的,Spring Security安全機制包括兩個主要的操作,認證和驗證,驗證也可以稱為許可權控制,這是Spring Security兩個主要的方向,認證是為使用者建立一個他所宣告的主體的過程,這個主體一般是指使用者裝置或可以在系統中執行行動的其他系統,驗證指使用者能否在應用中執行某個操作,在到達授權判斷之前身份的主體已經由身份認證過程建立了。下面列出幾種常用認證模式,這裡不對它們作詳細介紹,需要詳細瞭解的老鐵們可以自行查查對應的資料。
Basic
:HTTP1.0
提出,一種基於challenge/response的認證模式,針對特定的realm需要提供使用者名稱和密碼認證後才可訪問,其中密碼使用明文傳輸。缺點:①無狀態導致每次通訊都要帶上認證資訊,即使是已經認證過的資源;②傳輸安全性不足,認證資訊用Base64
編碼,基本就是明文傳輸,很容易對報文擷取並盜用認證資訊。Digest
:HTTP1.1
提出,它主要是為了解決Basic模式安全問題,用於替代原來的Basic認證模式,Digest認證也是採用challenge/response認證模式,基本的認證流程比較類似。Digest模式避免了密碼在網路上明文傳輸,提高了安全性,但它仍然存在缺點,例如認證報文被攻擊者攔截到攻擊者可以獲取到資源。X.509
:證照認證,X.509
是一種非常通用的證照格式,證照包含版本號、序列號(唯一)、簽名、頒發者、有效期、主體、主體公鑰。LDAP
:輕量級目錄訪問協議(Lightweight Directory Access Protocol)。Form
:基於表單的認證模式。
1.2 許可權攔截
Spring Security提供了很多過濾器,其中SecurityContextPersistenceFilter
、UsernamePasswordAuthenticationFilter
、FilterSecurityInterceptor
分別對應SecurityContext
、AuthenticationManager
、AccessDecisionManager
的處理。
下面分別介紹各個過濾器的功能。
過濾器 | 描述 |
---|---|
WebAsyncManagerIntegrationFilter |
設定SecurityContext 到非同步執行緒中,用於獲取使用者上下文資訊 |
SecurityContextPersistenceFilter |
整個請求過程中SecurityContext 的建立和清理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 資料庫管理
上圖展示的Spring Security核心處理流程。當一個使用者登入時,會先進行身份認證,如果身份認證未通過會要求使用者重新認證,當使用者身份證通過後就會呼叫角色管理器判斷他是否可以訪問,這裡,如果要實現資料庫管理使用者及許可權,就需要自定義使用者登入功能,Spring Security已經提供好了一個介面UserDetailsService
。
package org.springframework.security.core.userdetails;
public interface UserDetailsService {
/**
* Locates the user based on the username. In the actual implementation, the search
* may possibly be case sensitive, or case insensitive depending on how the
* implementation instance is configured. In this case, the <code>UserDetails</code>
* object that comes back may have a username that is of a different case than what
* was actually requested..
*
* @param username the username identifying the user whose data is required.
*
* @return a fully populated user record (never <code>null</code>)
*
* @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使用者的憑證就會被視為無效。
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.
*
* <p>
* 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.
* <p>
* 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 <code>null</code>.
*
* @return the authorities, sorted by natural key (never <code>null</code>)
*/
Collection<? extends GrantedAuthority> getAuthorities(); //許可權集合
/**
* Returns the password used to authenticate the user.
*
* @return the password
*/
String getPassword(); //密碼
/**
* Returns the username used to authenticate the user. Cannot return <code>null</code>.
*
* @return the username (never <code>null</code>)
*/
String getUsername(); //使用者名稱
/**
* Indicates whether the user's account has expired. An expired account cannot be
* authenticated.
*
* @return <code>true</code> if the user's account is valid (ie non-expired),
* <code>false</code> if no longer valid (ie expired)
*/
boolean isAccountNonExpired(); //賬戶是否過期
/**
* Indicates whether the user is locked or unlocked. A locked user cannot be
* authenticated.
*
* @return <code>true</code> if the user is not locked, <code>false</code> otherwise
*/
boolean isAccountNonLocked(); //賬戶是否被鎖定
/**
* Indicates whether the user's credentials (password) has expired. Expired
* credentials prevent authentication.
*
* @return <code>true</code> if the user's credentials are valid (ie non-expired),
* <code>false</code> if no longer valid (ie expired)
*/
boolean isCredentialsNonExpired(); //證照是否過期
/**
* Indicates whether the user is enabled or disabled. A disabled user cannot be
* authenticated.
*
* @return <code>true</code> if the user is enabled, <code>false</code> otherwise
*/
boolean isEnabled(); //賬戶是否有效
}
這裡需要注意的是Authentication
與UserDetails
物件的區分,Authentication
物件才是Spring Security使用的進行安全訪問控制使用者資訊的安全物件,實際上Authentication
物件有未認證和已認證兩種狀態,在作為引數傳入認證管理器的時候,它是一個為認證的物件,它從客戶端獲取使用者的身份認證資訊,如使用者名稱、密碼,可以是從一個登入頁面,也可以是從cookie中獲取,並由系統自動生成一個Authentication
物件,而這裡的UserDetails
代表的是一個使用者安全資訊的源,這個源可以是從資料庫、LDAP伺服器、CA中心返回,Spring Security要做的就是將未認證的Authentication
物件與UserDetails
物件進行匹配,成功後將UserDetails
物件中的許可權資訊拷貝到Authentication
中,組成一個完整的Authentication
物件,與其他元件進行共享。
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<? extends GrantedAuthority> getAuthorities();
/**獲取憑證*/
Object getCredentials();
/**獲取認證一些額外資訊*/
Object getDetails();
/**過去認證的實體*/
Object getPrincipal();
/**是否認證通過*/
boolean isAuthenticated();
/**
* See {@link #isAuthenticated()} for a full description.
* <p>
* Implementations should <b>always</b> allow this method to be called with a
* <code>false</code> 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 <code>true</code> parameter (which would indicate the
* authentication token is trusted - a potential security risk) the implementation
* should throw an {@link IllegalArgumentException}.
*
* @param isAuthenticated <code>true</code> if the token should be trusted (which may
* result in an exception) or <code>false</code> if the token should not be trusted
*
* @throws IllegalArgumentException if an attempt to make the authentication token
* trusted (by passing <code>true</code> 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
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
,要自定義決策管理器的話一般是繼承這個抽象類,而不是去實現介面。
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}.
*
* <p>
* 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<AccessDecisionVoter<?>> decisionVoters;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private boolean allowIfAllAbstainDecisions = false;
protected AbstractAccessDecisionManager(
List<AccessDecisionVoter<?>> decisionVoters) {
Assert.notEmpty(decisionVoters, "A list of AccessDecisionVoters is required");
this.decisionVoters = decisionVoters;
}
public void afterPropertiesSet() {
Assert.notEmpty(this.decisionVoters, "A list of AccessDecisionVoters is required");
Assert.notNull(this.messages, "A message source must be set");
}
protected final void checkAllowIfAllAbstainDecisions() {
if (!this.isAllowIfAllAbstainDecisions()) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
}
public List<AccessDecisionVoter<?>> getDecisionVoters() {
return this.decisionVoters;
}
public boolean isAllowIfAllAbstainDecisions() {
return allowIfAllAbstainDecisions;
}
public void setAllowIfAllAbstainDecisions(boolean allowIfAllAbstainDecisions) {
this.allowIfAllAbstainDecisions = allowIfAllAbstainDecisions;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
public boolean supports(ConfigAttribute attribute) {
for (AccessDecisionVoter voter : this.decisionVoters) {
if (voter.supports(attribute)) {
return true;
}
}
return false;
}
/**
* Iterates through all <code>AccessDecisionVoter</code>s and ensures each can support
* the presented class.
* <p>
* If one or more voters cannot support the presented class, <code>false</code> 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引入的一個投票器,有無許可權訪問的最終決定權就是由投票器來決定的。
package org.springframework.security.access;
import java.util.Collection;
import org.springframework.security.core.Authentication;
public interface AccessDecisionVoter<S> {
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<ConfigAttribute> attributes);
}
這裡有很多投票器,最常見的為RoleVoter
投票器,RoleVoter
定義了許可權的字首"ROLE_",投票器的核心是靠vote
這個選舉方法來實現的,方法中的引數authentication
是使用者及許可權資訊,attributes
是訪問資源需要的許可權,程式碼裡迴圈判斷使用者是否有訪問資源需要的許可權,如果有就返回ACCESS_GRANTED
,即有許可權。
package org.springframework.security.access.vote;
import java.util.Collection;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
public class RoleVoter implements AccessDecisionVoter<Object> {
private String rolePrefix = "ROLE_";
public String getRolePrefix() {
return rolePrefix;
}
public void setRolePrefix(String rolePrefix) {
this.rolePrefix = rolePrefix;
}
public boolean supports(ConfigAttribute attribute) {
if ((attribute.getAttribute() != null)
&& attribute.getAttribute().startsWith(getRolePrefix())) {
return true;
}
else {
return false;
}
}
public boolean supports(Class<?> clazz) {
return true;
}
/**
* authentication是使用者及許可權資訊
* attributes是訪問資源需要的許可權
*/
public int vote(Authentication authentication, Object object,
Collection<ConfigAttribute> attributes) {
if (authentication == null) {
return ACCESS_DENIED;
}
int result = ACCESS_ABSTAIN;
Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication);
for (ConfigAttribute attribute : attributes) {
if (this.supports(attribute)) {
result = ACCESS_DENIED;
// Attempt to find a matching granted authority
for (GrantedAuthority authority : authorities) {
if (attribute.getAttribute().equals(authority.getAuthority())) {
return ACCESS_GRANTED;
}
}
}
}
return result;
}
Collection<? extends GrantedAuthority> extractAuthorities(
Authentication authentication) {
return authentication.getAuthorities();
}
}
Spring Seucrity提供了三種投票決策,分別是AffirmativeBased
:一票通過即可訪問;ConsensusBased
:一半以上通過才允許訪問;UnanimousBased
:全部通過才允許訪問。自定義決策只需要繼承AbstractAccessDecisionManager
抽象類,可以自定義自己的投票器,比如需要同時滿足多個條件才能訪問等,不需要使用Spring Security自帶的投票器。
2、環境搭建及使用
2.1 快速搭建Spring Boot + Spring Security環境
開啟Spring Boot官網https://start.spring.io/,選擇Java語言,在Dependencies中新增Spring Web和Spring Security,最後點選GENERATE下載。
解壓下載的檔案,用idea開啟,可以看到這是一個可以直接啟動的demo,因為我們是web專案,所以這裡新增一個介面看一下。
@SpringBootApplication
@RestController
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@RequestMapping("/")
public String home() {
return "hello spring boot";
}
}
啟動後我們在位址列輸入locahost:8080會自動跳轉到/login路徑,說明Spring Security就已經直接參與進來了。
然後我們建立一個繼承WebSecurityConfigurerAdapter
的配置類,定義許可權訪問策略,同時再新增一個路徑為“/hello”的介面,根據程式碼註釋我們可以看出,訪問專案主路徑可以不需要驗證,訪問其餘路徑則需要驗證。啟動專案,訪問localhost:8080可以直接通過,但訪問localhost:8080\hello則會自動跳轉到localhost:8080/login路徑要求登入。這樣說明Spring Security的安全策略已經生效了,Spring Boot與Spring Security的環境搭建也完成了。
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 攔截策略
* 定義哪些路徑需要被攔截,哪些路徑不需要攔截
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/").permitAll() //專案主路徑可以放行
.anyRequest().authenticated() //其餘所有請求需要驗證
.and().logout().permitAll() //允許登出可以訪問
.and().formLogin(); //允許表單登入
http.csrf().disable(); //關閉csrf認證
}
@Override
public void configure(WebSecurity web) throws Exception {
/**
* 忽略靜態資源的攔截
*/
web.ignoring().antMatchers("/js/**", "/css/**");
}
}
2.2 常用Case實現
2.2.1 只要能登入即可
只要登入就可以訪問專案所有資源路徑,也不用寫單獨的登入頁面,這裡就會用到Spring Security提供的基於記憶體的驗證。在SpringSecurityConfig
類中繼續重寫configure(AuthenticationManagerBuilder auth)
這個方法。Spring security 5.0之後新增了多種加密方式,改變了預設的密碼格式,新的密碼儲存格式是“{id}…………”.前面的id是加密方式,id可以是bcrypt、sha256等,後面跟著的是加密後的密碼。也就是說,程式拿到傳過來的密碼的時候,會首先查詢被“{”和“}”包括起來的id,來確定後面的密碼是被怎麼樣加密的,如果找不到就認為id是null。這時候程式會報錯:There is no PasswordEncoder mapped for the id “null”.實際應用中也可以自定義加密方式,只需要繼承PasswordEncoder
介面即可。
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//建立一個使用者名稱為admin,密碼為123456,角色為ADMIN的使用者
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin")
.password(new BCryptPasswordEncoder().encode("123456"))
.roles("ADMIN");
//可指定多個使用者
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("zhangsan")
.password(new BCryptPasswordEncoder().encode("123456"))
.roles("DEMO");
}
}
2.2.2 有指定的角色,每個角色有指定的許可權
新增一個限定角色的請求,需要有ADMIN角色的才能訪問,“ROLE_”為RoleVoter
中定義的字首,在前面自定義決策中提到過。同時,這裡還需要注意的是,使用@PreAuthorize
這個註解時,一定要在類上加上@EnableGlobalMethodSecurity(prePostEnabled = true)
註解@PreAuthorize
才會生效。這樣admin使用者就可以訪問/roleAuth,但zhangsan則不可以訪問/roleAuth。
@SpringBootApplication
@RestController
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class DemoApplication {
/**中間程式碼省略**/
@PreAuthorize("hasRole('ROLE_ADMIN')")
@RequestMapping("/roleAuth")
public String role() {
return "admin auth";
}
}
實際場景中使用者角色一般是儲存在資料庫中的,前面提到過Spring Security的資料庫管理需要實現UserDetailsService
介面,定義資料庫相關查詢,返回UserDetails
物件。
package com.mall.demo;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
@Component
public class MyUserService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
return null;
}
}
@Autowired
private MyUserService myUserService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserService);
/**
* Spring Security提供的預設資料庫驗證
*/
auth.jdbcAuthentication()
.usersByUsernameQuery("") //查詢users
.authoritiesByUsernameQuery(""); //查詢許可權
}
資料庫管理在實際專案能更好的說明,這裡我們回到Spring Security許可權配置,之前使用過@PreAuthorize
這個註解來控制方法是否能被呼叫,實際上Spring Security提供了4個這樣的註解,分別是@PreAuthorize
、@PostAuthorize
、@PreFilter
、@PostFilter
,@PreAuthorize
和@PostAuthorize
的作用分別是在方法呼叫前和呼叫後對許可權進行檢查,@PreFilter
和@PostFilter
的作用是對集合類的引數或返回值進行過濾。
//傳入的id引數小於10
//傳入的username=當前使用者名稱
//傳入的User物件的使用者名稱=zhangsan
@PreAuthorize("#id<10 and principal.username.equals(#username) and #user.username.equals('zhangsan')")
//驗證返回結果是否是偶數
@PostAuthorize("returnObject%2==0")
@RequestMapping("/test1")
public Integer test1(Integer id, String username, User user) {
return id;
}
//過濾傳入的引數保留偶數
@PreFilter("filterObject%2==0")
//過濾返回結果保留被4整除的數
@PostFilter("filterObject%4==0")
@RequestMapping("/test2")
public List<Integer> test2(List<Integer> idList) {
return idList;
}