1. 程式人生 > >淺談spring security中的許可權控制

淺談spring security中的許可權控制

當我們在OAuth登陸後,獲取了登陸的令牌,使用該令牌,我們就有了訪問一些受OAuth保護的介面的能力。具體可以看本人的這兩篇部落格OAuth2.0使用者名稱,密碼登入解析 OAuth2.0通過token獲取受保護資源的解析

但現在我們要區分這些登陸人員的具體分工,哪些介面歸哪些登陸人員可以訪問,這就要用到了spring security中的許可權控制。

首先我們需要有一個許可權的物件

/**
 * 許可權標識
 */
@Data
public class SysPermission implements Serializable {

   private static final long 
serialVersionUID = 280565233032255804L; private Long id; //許可權id private String permission; //具體的許可權 private String name; //許可權名稱 private Date createTime; private Date updateTime; }

對應於資料庫中的許可權表

那麼問題來了,我們要對許可權進行管理需要什麼樣的許可權呢,當然我們需要許可權管理許可權,這是在系統一開始建立的時候儲存進資料庫的

這四個許可權並不是通過前端寫入的。

現在我們需要通過前端介面增加其他的許可權就需要使用到這四個許可權之一。

在這裡我們給出一些許可權的增刪改查的mybatis dao

@Mapper
public interface SysPermissionDao {

   @Options(useGeneratedKeys = true, keyProperty = "id")
   @Insert("insert into sys_permission(permission, name, createTime, updateTime) values(#{permission}, #{name}, #{createTime}, #{createTime})"
) int save(SysPermission sysPermission); @Update("update sys_permission t set t.name = #{name}, t.permission = #{permission}, t.updateTime = #{updateTime} where t.id = #{id}") int update(SysPermission sysPermission); @Delete("delete from sys_permission where id = #{id}") int delete(Long id); @Select("select * from sys_permission t where t.id = #{id}") SysPermission findById(Long id); @Select("select * from sys_permission t where t.permission = #{permission}") SysPermission findByPermission(String permission); int count(Map<String, Object> params); List<SysPermission> findData(Map<String, Object> params); }

現在我們要在Controller中增加一個新的許可權

/**
 * 管理後臺新增許可權
 * 
 * @param sysPermission
 * @return
 */
@LogAnnotation(module = LogModule.ADD_PERMISSION)
@PreAuthorize("hasAuthority('back:permission:save')")
@PostMapping("/permissions")
public SysPermission save(@RequestBody SysPermission sysPermission) {
   if (StringUtils.isBlank(sysPermission.getPermission())) {
      throw new IllegalArgumentException("許可權標識不能為空");
   }
   if (StringUtils.isBlank(sysPermission.getName())) {
      throw new IllegalArgumentException("許可權名不能為空");
   }

   sysPermissionService.save(sysPermission);

   return sysPermission;
}

我們可以看到這個標籤@PreAuthorize("hasAuthority('back:permission:save')"),首先我們是通過access_token令牌訪問的該介面,系統可以知道登陸的是哪一個使用者,以此看看該使用者是否有back:permission:save的訪問許可權

我們來看看使用者角色

@Data
public class SysRole implements Serializable {

   private static final long serialVersionUID = -2054359538140713354L;

   private Long id; //角色id
   private String code; //角色編碼
   private String name; //角色名稱
   private Date createTime;
   private Date updateTime;
}

對應資料庫中的表結構如下

並給定一個管理員角色

該角色對應於哪些許可權,這裡可以看到是所有許可權

而我們的使用者是哪個角色呢

我們可以看到這裡有兩個使用者,他們都屬於管理員角色

如果我們現在用其中的一個使用者登陸,並獲取該使用者的資訊如下

{ "code" : 200 , "data" : { "access_token" : "aaf4cd90-497e-4c33-adde-b580ab0f0c65" , "user" : { "accountNonExpired" : true , "accountNonLocked" : true , "authorities" : [ { "authority" : "back:menu:set2role" }, { "authority" : "mail:update" }, { "authority" : "back:permission:delete" }, { "authority" : "role:permission:byroleid" }, { "authority" : "back:menu:save" }, { "authority" : "back:menu:query" }, { "authority" : "ip:black:query" }, { "authority" : "ip:black:save" }, { "authority" : "file:del" }, { "authority" : "ip:black:delete" }, { "authority" : "mail:query" }, { "authority" : "back:user:query" }, { "authority" : "back:role:permission:set" }, { "authority" : "sms:query" }, { "authority" : "back:role:query" }, { "authority" : "back:permission:query" }, { "authority" : "back:user:role:set" }, { "authority" : "back:role:save" }, { "authority" : "log:query" }, { "authority" : "file:query" }, { "authority" : "back:menu:update" }, { "authority" : "back:role:update" }, { "authority" : "back:role:delete" }, { "authority" : "back:user:password" }, { "authority" : "ROLE_SUPER_ADMIN" }, { "authority" : "back:menu:delete" }, { "authority" : "back:user:update" }, { "authority" : "menu:byroleid" }, { "authority" : "mail:save" }, { "authority" : "user:role:byuid" }, { "authority" : "back:permission:save" }, { "authority" : "back:permission:update" } ], "createTime" : "2018-01-17T16:56:59.000+0800" , "credentialsNonExpired" : true , "enabled" : true , "headImgUrl" : "" , "id" : 1 , "nickname" : "測試1" , "password" : "$2a$10$QpeXBJpWYetNwfWEHnkvLeK0jS0P9R6V8QqCj37zeNGroqYvdvW.C" , "permissions" : [ "back:menu:set2role" , "mail:update" , "back:permission:delete" , "role:permission:byroleid" , "back:menu:save" , "back:menu:query" , "ip:black:query" , "ip:black:save" , "file:del" , "ip:black:delete" , "mail:query" , "back:user:query" , "back:role:permission:set" , "sms:query" , "back:role:query" , "back:permission:query" , "back:user:role:set" , "back:role:save" , "log:query" , "file:query" , "back:menu:update" , "back:role:update" , "back:role:delete" , "back:user:password" , "back:menu:delete" , "back:user:update" , "menu:byroleid" , "mail:save" , "user:role:byuid" , "back:permission:save" , "back:permission:update" ], "phone" : "" , "sex" : 1 , "sysRoles" : [ { "code" : "SUPER_ADMIN" , "createTime" : "2018-01-19T20:32:16.000+0800" , "id" : 1 , "name" : "超級管理員" , "updateTime" : "2018-01-19T20:32:18.000+0800" } ], "type" : "APP" , "updateTime" : "2018-01-17T16:57:01.000+0800" , "username" : "admin" } }, "msg" : "操作成功" } 通過返回的資訊我們可以看到他的許可權,跟 @PreAuthorize( "hasAuthority('back:permission:save')")比對,我們知道登陸使用者擁有該許可權。可以訪問該介面。 上面都是前菜,下面進入正題。 我們來看一下 @PreAuthorize 標籤的原始碼,它位於org.springframework.security.access.prepost包下
/**
 * 用於指定將計算為的方法訪問控制表示式的批註
 * 決定是否允許方法呼叫。
 *
 * @author Luke Taylor
 * @since 3.0
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PreAuthorize {
   /**
    * @return 在執行這個受保護的方法前進行Spring EL表示式的解析
    */
   String value();
}

這裡有一個Spring EL表示式都解析,我們來看一下什麼是Spring EL表示式

public class SpringELTest {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        //解析字串,該字串具有一段程式碼的味道
        Expression expression = parser.parseExpression("'Hello World'.bytes.length");
        int length = (int)expression.getValue();
        System.out.println(length);
    }
}

這個"'Hello World'.bytes.length"就是一段Spring EL表示式,雖然是一段字串,但它有一段程式碼的含義,可以被解析執行

執行結果

11

那麼很明顯"hasAuthority('back:permission:save')"就是一段Spring EL表示式,它是可以被執行的。

要想使標籤@PreAuthorize生效,我們需要設定一下OAuth的資源服務設定

/**
 * 資源服務配置
 */
@EnableResourceServer
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

   @Override
   public void configure(HttpSecurity http) throws Exception {
      http.csrf().disable().exceptionHandling()
            .authenticationEntryPoint(
                  (request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
            .and().authorizeRequests().antMatchers(PermitAllUrl.permitAllUrl("/users-anon/**", "/sys/login","/wechat/**")).permitAll()
            .anyRequest().authenticated().and().httpBasic();
   }
   @Override
   public void configure(ResourceServerSecurityConfigurer resource) throws Exception {
      //這裡把自定義異常加進去
      resource.authenticationEntryPoint(new AuthExceptionEntryPoint())
            .accessDeniedHandler(new CustomAccessDeniedHandler());
   }


   @Bean
   public BCryptPasswordEncoder bCryptPasswordEncoder() {
      return new BCryptPasswordEncoder();
   }

}

其中@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)就是開啟@PreAuthorize進行攔截生效的標籤,當然一定要設定prePostEnabled = true。

既然"hasAuthority('back:permission:save')"是一段Spring EL表示式,那麼hasAuthority()就一定是一個可以執行的方法,該方法位於SecurityExpressionRoot類下,該類位於org.springframework.security.access.expression包中。

Spring Security可用表示式物件的基類就是SecurityExpressionRoot,它支援很多的方法

表示式

描述

hasRole([role])

當前使用者是否擁有指定角色。

hasAnyRole([role1,role2])

多個角色是一個以逗號進行分隔的字串。如果當前使用者擁有指定角色中的任意一個則返回true。

hasAuthority([auth])

等同於hasRole

hasAnyAuthority([auth1,auth2])

等同於hasAnyRole

Principle

代表當前使用者的principle物件

authentication

直接從SecurityContext獲取的當前Authentication物件

permitAll

總是返回true,表示允許所有的

denyAll

總是返回false,表示拒絕所有的

isAnonymous()

當前使用者是否是一個匿名使用者

isRememberMe()

表示當前使用者是否是通過Remember-Me自動登入的

isAuthenticated()

表示當前使用者是否已經登入認證成功了。

isFullyAuthenticated()

如果當前使用者既不是一個匿名使用者,同時又不是通過Remember-Me自動登入的,則返回true。

我們具體看一下hasAuthority這個方法的實現,只有當這個方法返回的結果為true的時候,我們才能進一步訪問我們的介面程式碼

這裡面傳入的authority為"back:permission:save"

public final boolean hasAuthority(String authority) {
   return hasAnyAuthority(authority);
}
public final boolean hasAnyAuthority(String... authorities) {
   return hasAnyAuthorityName(null, authorities);
}
private boolean hasAnyAuthorityName(String prefix, String... roles) {
   //獲取所有的使用者角色許可權
   Set<String> roleSet = getAuthoritySet();
   //由於我們這裡傳入的roles只有"back:permission:save"
   //所以role即為"back:permission:save",prefix則為null
   for (String role : roles) {
      //defaultedRole依然為"back:permission:save"
      String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
      //在許可權集合中是否包含"back:permission:save"的該許可權
      //根據我們之前登入的返回資訊,可以看到"authority": "back:permission:save"的存在
      //所以此處是可以通過許可權驗證的。
      if (roleSet.contains(defaultedRole)) {
         return true;
      }
   }

   return false;
}
private Set<String> getAuthoritySet() {
   //Set<String> rolesSecurityExpressionRoot的屬性
   //我們可以看到它是從一系列使用者認證裡面獲取到的許可權集合
   if (roles == null) {
      roles = new HashSet<>();
      //authenticationSecurityExpressionRoot極為重要的一個屬性,它本身是一個介面
      //管理著使用者認證資訊的各個方法
      Collection<? extends GrantedAuthority> userAuthorities = authentication
            .getAuthorities();

      if (roleHierarchy != null) {
         userAuthorities = roleHierarchy
               .getReachableGrantedAuthorities(userAuthorities);
      }
      
      roles = AuthorityUtils.authorityListToSet(userAuthorities);
   }

   return roles;
}
private static String getRoleWithDefaultPrefix(String defaultRolePrefix, String role) {
   if (role == null) {
      return role;
   }
   //由於defaultRolePrefix為null,所以此處返回的就是"back:permission:save"
   if (defaultRolePrefix == null || defaultRolePrefix.length() == 0) {
      return role;
   }
   if (role.startsWith(defaultRolePrefix)) {
      return role;
   }
   return defaultRolePrefix + role;
}

我們可以看一下AuthorityUtils.authorityListToSet()方法

public static Set<String> authorityListToSet(
      Collection<? extends GrantedAuthority> userAuthorities) {
   Set<String> set = new HashSet<>(userAuthorities.size());
   //很明顯這裡是把認證使用者的所有許可權給轉化為Set集合
   for (GrantedAuthority authority : userAuthorities) {
      set.add(authority.getAuthority());
   }

   return set
            
           

相關推薦

spring security許可權控制

當我們在OAuth登陸後,獲取了登陸的令牌,使用該令牌,我們就有了訪問一些受OAuth保護的介面的能力。具體可以看本人的這兩篇部落

Spring Boot使用Spring Security實現許可權控制

Spring Boot框架我們前面已經介紹了很多了,相信看了前面的部落格的小夥伴對Spring Boot應該有一個大致的瞭解了吧,如果有小夥伴對Spring Boot尚不熟悉,可以先移步這裡從SpringMVC到Spring Boot,老司機請略過。OK,那我們

SpringBoot使用Spring Security實現許可權控制

Spring Security,這是一個專門針對基於Spring的專案的安全框架,它主要是利用了AOP來實現的。以前在Spring框架中使用Spring Security需要我們進行大量的XML配置,但是,Spring Boot針對Spring Security

Spring Security原始碼分析十五:Spring Security 頁面許可權控制

Spring Security是一個能夠為基於Spring的企業應用系統提供宣告式的安全訪問控制解決方案的安全框架。它提供了一組可以在Spring應用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反轉Inversion of Contr

spring security 403機制

403就是access denied ,就是請求拒絕,因為許可權不足 三種許可權級別 一、無許可權訪問 <security:http security="none" pattern="/index.jsp"   /> 這種即是不需要登入,也可以訪問的,但是不會傳

springAOP以及springAOP的註解方式

早就 好的 面向 XML ram ati alt 返回 增強   AOP(Aspect Oriented Programming):AOP的專業術語是"面向切面編程" 什麽是面向切面編程,我的理解就是:在不修改源代碼的情況下增強功能.好了,下面在講述aop註解方式的情況下順

Spring的事務回滾

spec style try 常見 產生原因 turn prop ret run 使用Spring管理事務過程中,碰到過一些坑,因此也稍微總結一下,方便後續查閱。1.代碼中事務控制的3種方式編程式事務:就是直接在代碼裏手動開啟事務,手動提交,手動回滾。優點就是可以靈

企業分布式微服務雲SpringCloud SpringBoot mybatis (六)Spring Boot使用Spring Security進行安全控制

spring ron public 控制 應用 app ebs cloud 來源 準備工作 首先,構建一個簡單的Web工程,以用於後續添加安全控制,也可以用之前Chapter3-1-2做為基礎工程。若對如何使用Spring Boot構建Web應用,可以先閱讀《Spring

Linux系統許可權

Linux許可權 使用者許可權 切換使用者命令: su [使用者名稱] 切換到root使用者時,命令su root可以省略root Linux中有兩種使用者,超級使用者和普通使用者: 超級使用者 擁有系統的所有許可權,可以在系統中不受限制地做任何操作 普通使用

Spring Boot使用Spring Security進行安全控制

一 點睛 我們在編寫Web應用時,經常需要對頁面做一些安全控制,比如:對於沒有訪問許可權的使用者需要轉到登入表單頁面。要實現訪問控制的方法多種多樣,可以通過Aop、攔截器實現,也可以通過框架實現(如:Apache Shiro、Spring Security)。 本篇將具體

SpringBoot使用spring security進行安全控制

一、引入 在SpringBoot中引入SpringSecurity <!--spring boot security引入--> <dependency> <groupId>org.springframewo

springBeanDefinitionDocumentReader的作用

在spring中BeanDefinitionDocumentReader的主要作用是解析bean.xml配置檔案 BeanDefinitionDocumentReader是一個介面,通過例項化工作createBeanDefinitionDocumentReader()而獲

【遊戲開發】遊戲開發常見的設計原則

依賴關系 unity 說過 srp des log gof https 類繼承   俗話說得好:“設計模式,常讀常新~”。的確,每讀一遍設計模式都會有些新的體會和收獲。馬三不才,才讀了兩遍設計模式(還有一遍是在學校學的),屬於菜鳥級別的。這次準備把閱

Spring的AOP實現-動態代理

out handle 多功能 額外 java oid callback 淺談 驗證   說起Spring的AOP(Aspect-Oriented Programming)面向切面編程大家都很熟悉(Spring不是這次博文的重點),但是我先提出幾個問題,看看同學們是否了解,如

網絡的IP地址

網絡ip 思科 華為 IP地址是現在生活中不可或缺的,互聯網的運用,使我們的生活變得多元化,充滿樂趣。想了解這一切,需要先從根本了解,今天淺談以下IP地址,從以下幾個方面介紹; 一.IP地址的作用:在一定範圍,唯一的標示,一個上網的設備;(凡是需要上網的設備,必須得有IP地址)

JAVAEE企業級應用開發之MVC 的V-VIEW視圖

插入 第一次 開發 優點 就會 mil 是否 javaee 方便 Step1.情景概要 Hello,小夥伴們,好久不見,之前跟大家分享了三層架構與MVC思想,相信大家對於這兩塊內容有了相對清晰的個人認識了,既然我們講到了MVC,這裏我們接著這塊內容繼續往下深入,今天我們來看

前端知識 | 在React使用echarts

har family spa microsoft -- nbsp date text break 方法一:echarts-for-react 是一個非常簡單的針對於 React 的 Echarts 封裝插件。和使用所有其他插件一樣,首先,我們需要 install 它:第一步

C#語言的各種數據類型,與數據類型之間的轉換

優化配置 line com 歸類 浮點 初學者 結構 ali 順序 什麽是數據類型? 數據類型,百度百科是這樣解釋的:數據類型在數據結構中的定義是一個值的集合以及定義在這個值集上的一組操作。這樣的解釋對於一個初學者來說未必太過於深奧。 簡單點說,數據類型就是不同長度的數據的

Exchange 2010客戶端訪問服務器陣列ClientAccessArray

忽略 介紹 con 單獨 mark cit type cto 創建 除了 RPC 客戶端訪問服務之外,Exchange 2010 還向 Exchange 組織引入了一個新的邏輯結構:客戶端訪問服務器陣列(Client Access Server Array)。當在某個 Ac

Spring的PropertyPlaceholderConfigurer

意思 ace erl string nbsp 重要 配置信息 classpath 提高 大型項目中,我們往往會對我們的系統的配置信息進行統一管理,一般做法是將配置信息配置與一個cfg.properties的文件中,然後在我們系統初始化的時候,系統自動讀取cfg.proper