主頁 > 後端開發 > Java安全框架(一)Spring Security

Java安全框架(一)Spring Security

2020-11-03 16:00:16 後端開發

文章主要分三部分
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包含三個主要的組件:SecurityContextAuthenticationManagerAccessDecisionManager.

Spring Security主要組件圖
圖1-1 Spring Security主要組件

1.1 認證

? Spring Security提供了很多過濾器,它們攔截Servlet請求,并將這些請求轉交給認證處理過濾器和訪問決策過濾器進行處理,并強制安全性認證用戶身份和用戶權限以達到保護WEB資源的目的,Spring Security安全機制包括兩個主要的操作,認證驗證,驗證也可以稱為權限控制,這是Spring Security兩個主要的方向,認證是為用戶建立一個他所宣告的主體的程序,這個主體一般是指用戶設備或可以在系統中執行行動的其他系統,驗證指用戶能否在應用中執行某個操作,在到達授權判斷之前身份的主體已經由身份認證程序建立了,下面列出幾種常用認證模式,這里不對它們作詳細介紹,需要詳細了解的老鐵們可以自行查查對應的資料,

  1. BasicHTTP1.0提出,一種基于challenge/response的認證模式,針對特定的realm需要提供用戶名和密碼認證后才可訪問,其中密碼使用明文傳輸,缺點:①無狀態導致每次通信都要帶上認證資訊,即使是已經認證過的資源;②傳輸安全性不足,認證資訊用Base64編碼,基本就是明文傳輸,很容易對報文截取并盜用認證資訊,
  2. DigestHTTP1.1提出,它主要是為了解決Basic模式安全問題,用于替代原來的Basic認證模式,Digest認證也是采用challenge/response認證模式,基本的認證流程比較類似,Digest模式避免了密碼在網路上明文傳輸,提高了安全性,但它仍然存在缺點,例如認證報文被攻擊者攔截到攻擊者可以獲取到資源,
  3. X.509:證書認證,X.509是一種非常通用的證書格式,證書包含版本號、序列號(唯一)、簽名、頒發者、有效期、主體、主體公鑰,
  4. LDAP:輕量級目錄訪問協議(Lightweight Directory Access Protocol),
  5. Form:基于表單的認證模式,

1.2 權限攔截

image-20201024122405276
圖1-2 用戶請求
圖1-3 過濾器

? Spring Security提供了很多過濾器,其中SecurityContextPersistenceFilterUsernamePasswordAuthenticationFilterFilterSecurityInterceptor分別對應SecurityContextAuthenticationManagerAccessDecisionManager的處理,

Spring Security過濾鏈流程圖
圖1-4 Spring Security過濾鏈流程圖

下面分別介紹各個過濾器的功能,

過濾器 描述
WebAsyncManagerIntegrationFilter 設定SecurityContext到異步執行緒中,用于獲取用戶背景關系資訊
SecurityContextPersistenceFilter 整個請求程序中SecurityContext的創建和清理
1.未登錄,SecurityContext為null,創建一個新的ThreadLocalSecurityContext填充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核心處理流程
圖1-5 Spring Security核心處理流程

? 上圖展示的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(); //賬戶是否有效
}

? 這里需要注意的是AuthenticationUserDetails物件的區分,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類的構造接收一個用于真正加載UserDetailsUserDetailsService實作類,當需要加載UserDetails時,會首先從快取中獲取,如果快取中沒有UserDetails存在,則使用持有的UserDetailsService實作類進行加載,然后將加載后的結果存在快取中,UserDetails與快取的互動是通過UserCache介面來實作的,CachingUserDetailsService默認擁有一個UserCacheNullUserCache()實作,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;
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/201081.html

標籤:其他

上一篇:挑戰Redis單實體記憶體最大極限,“遭遇”NUMA陷阱!

下一篇:springcloud vue.js 微服務分布式 flowable 作業流 前后分離 集成代碼生成器 shiro權限

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more