前言
本文主要講解的知識點有如下:java
-
權限管理的基礎知識web
- 模型
- 粗粒度和細粒度的概念
- 回顧URL攔截的實現
- Shiro的介紹與簡單入門
1、Shiro基礎知識
在學習Shiro這個框架以前,首先咱們要先了解Shiro須要的基礎知識:權限管理算法
1.1什麼是權限管理?
只要有用戶參與的系統通常都要有權限管理,權限管理實現對用戶訪問系統的控制,按照安全規則或者安全策略控制用戶能夠訪問並且只能訪問本身被受權的資源。spring
對權限的管理又分爲兩大類別:sql
- 用戶認證
- 用戶受權
1.1.1用戶認證
用戶認證,用戶去訪問系統,系統要驗證用戶身份的合法性數據庫
最經常使用的用戶身份驗證的方法:一、用戶名密碼方式、二、指紋打卡機、三、基於證書驗證方法。。系統驗證用戶身份合法,用戶方可訪問系統的資源。apache
舉個例子:segmentfault
- 當咱們輸入了本身的淘寶的帳戶和密碼,才能打開購物車
用戶認證的流程:緩存
- 判斷該資源可否不認證就能訪問【登錄頁面、首頁】
- 若是該資源須要認證後才能訪問,那麼判斷該訪問者是否定證了
- 若是尚未認證,那麼須要返回到【登錄頁面】進行認證
- 認證經過後才能訪問資源
從用戶認證咱們能夠抽取出這麼幾個概念tomcat
- subject主體:理解爲用戶,多是程序,都要去訪問系統的資源,系統須要對subject進行身份認證
- principal身份信息:一般是惟一的,一個主體還有多個身份信息,可是都有一個主身份信息(primary principal)【咱們能夠選擇身份證認證、學生證認證等等都是咱們的身份信息】
- credential憑證信息:能夠是密碼 、證書、指紋。
總結:主體在進行身份認證時須要提供身份信息和憑證信息。
1.1.2用戶受權
用戶受權,簡單理解爲訪問控制,在用戶認證經過後,系統對用戶訪問資源進行控制,用戶具備資源的訪問權限方可訪問。
用戶受權的流程
- 到達了用戶受權環節,固然是須要用戶認證以後了
- 用戶訪問資源,系統判斷該用戶是否有權限去操做該資源
- 若是該用戶有權限纔可以訪問,若是沒有權限就不能訪問了
受權的過程能夠簡單理解爲:主體認證以後,系統進行訪問控制
subject必須具有資源的訪問權限纔可訪問該資源..
權限/許可(permission) :針對資源的權限或許可,subject具備permission訪問資源,如何訪問/操做須要定義permission,權限好比:用戶添加、用戶修改、商品刪除
資源能夠分爲兩種
- 資源類型:系統的用戶信息就是資源類型,至關於java類。
- 資源實例:系統中id爲001的用戶就是資源實例,至關於new的java對象。
1.2權限管理模型
通常地,咱們能夠抽取出這麼幾個模型:
- 主體(帳號、密碼)
- 資源(資源名稱、訪問地址)
- 權限(權限名稱、資源id)
- 角色(角色名稱)
- 角色和權限關係(角色id、權限id)
- 主體和角色關係(主體id、角色id)
一般企業開發中將資源和權限表合併爲一張權限表,以下:
- 資源(資源名稱、訪問地址)
- 權限(權限名稱、資源id)
合併爲:
- 權限(權限名稱、資源名稱、資源訪問地址)
1.3分配權限
用戶須要分配相應的權限纔可訪問相應的資源。權限是對於資源的操做許可。
一般給用戶分配資源權限須要將權限信息持久化,好比存儲在關係數據庫中。把用戶信息、權限管理、用戶分配的權限信息寫到數據庫(權限數據模型)
1.3.1基於角色訪問控制
RBAC(role based access control),基於角色的訪問控制。
//若是該user是部門經理則能夠訪問if中的代碼 if(user.hasRole('部門經理')){ //系統資源內容 //用戶報表查看 }
角色針對人劃分的,人做爲用戶在系統中屬於活動內容,若是該 角色能夠訪問的資源出現變動,須要修改你的代碼了,
if(user.hasRole('部門經理') || user.hasRole('總經理') ){ //系統資源內容 //用戶報表查看 }
基於角色的訪問控制是不利於系統維護(可擴展性不強)。
1.3.2基於資源的訪問控制
RBAC(Resource based access control),基於資源的訪問控制。
資源在系統中是不變的,好比資源有:類中的方法,頁面中的按鈕。
對資源的訪問須要具備permission權限,代碼能夠寫爲:
if(user.hasPermission ('用戶報表查看(權限標識符)')){ //系統資源內容 //用戶報表查看 }
建議使用基於資源的訪問控制實現權限管理。
2、 粗粒度和細粒度權限
細粒度權限管理:對資源實例的權限管理。資源實例就資源類型的具體化,好比:用戶id爲001的修改鏈接,1110班的用戶信息、行政部的員工。細粒度權限管理就是數據級別的權限管理。
粗粒度權限管理好比:超級管理員能夠訪問戶添加頁面、用戶信息等所有頁面。部門管理員能夠訪問用戶信息頁面包括 頁面中全部按鈕。
粗粒度和細粒度例子:
系統有一個用戶列表查詢頁面,對用戶列表查詢分權限, 若是粗顆粒管理,張三和李四都有用戶列表查詢的權限,張三和李四均可以訪問用戶列表查詢。 進一步進行細顆粒管理,張三(行政部)和李四(開發部)只能夠查詢本身本部門的用戶信息。 張三隻能查看行政部 的用戶信息,李四隻能查看開發部門的用戶信息。 細粒度權限管理就是數據級別的權限管理。
2.1如何實現粗粒度權限管理?
粗粒度權限管理比較容易將權限管理的代碼抽取出來在系統架構級別統一處理。好比:經過springmvc的攔截器實現受權。
對細粒度權限管理在數據級別是沒有共性可言,針對細粒度權限管理就是系統業務邏輯的一部分,在業務層去處理相對比較簡單
好比:部門經理只查詢本部門員工信息,在service接口提供一個部門id的參數,controller中根據當前用戶的信息獲得該 用戶屬於哪一個部門,調用service時將部門id傳入service,實現該用戶只查詢本部門的員工。
2.1.1基於URL攔截
基於url攔截的方式實如今實際開發中比較經常使用的一種方式。
對於web系統,經過filter過慮器實現url攔截,也能夠springmvc的攔截器實現基於url的攔截。
2.2.2使用權限管理框架實現
對於粗粒度權限管理,建議使用優秀權限管理框架來實現,節省開發成功,提升開發效率。
shiro就是一個優秀權限管理框架。
3、回顧URL攔截
咱們在學習的路途上也是使用過幾回URL對權限進行攔截的
當時咱們作了權限的增刪該查的管理系統,可是在權限表中是沒有把資源添加進去,咱們使用的是Map集合來進行替代的。
http://blog.csdn.net/hon_3y/article/details/61926175
隨後,咱們學習了動態代理和註解,咱們也作了一個基於註解的攔截
- 在Controller獲得service對象的時候,service工廠返回的是一個動態代理對象回去
- Controller拿着代理對象去調用方法,代理對象就會去解析該方法上是否有註解
- 若是有註解,那麼就須要咱們進行判斷該主體是否定證了,若是認證了就判斷該主體是否有權限
- 當咱們解析出該主體的權限和咱們註解的權限是一致的時候,才放行!
http://blog.csdn.net/hon_3y/article/details/70767050
流程:
3.1認證的JavaBean
咱們以前認證都是放在默認的Javabean對象上的,如今既然咱們準備學Shiro了,咱們就得專業一點,弄一個專門存儲認證信息的JavaBean
/** * 用戶身份信息,存入session 因爲tomcat將session會序列化在本地硬盤上,因此使用Serializable接口 * * @author Thinkpad * */ public class ActiveUser implements java.io.Serializable { private String userid;//用戶id(主鍵) private String usercode;// 用戶帳號 private String username;// 用戶名稱 private List<SysPermission> menus;// 菜單 private List<SysPermission> permissions;// 權限 public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getUsercode() { return usercode; } public void setUsercode(String usercode) { this.usercode = usercode; } public String getUserid() { return userid; } public void setUserid(String userid) { this.userid = userid; } public List<SysPermission> getMenus() { return menus; } public void setMenus(List<SysPermission> menus) { this.menus = menus; } public List<SysPermission> getPermissions() { return permissions; } public void setPermissions(List<SysPermission> permissions) { this.permissions = permissions; } }
認證的服務
@Override public ActiveUser authenticat(String userCode, String password) throws Exception { /** 認證過程: 根據用戶身份(帳號)查詢數據庫,若是查詢不到用戶不存在 對輸入的密碼 和數據庫密碼 進行比對,若是一致,認證經過 */ //根據用戶帳號查詢數據庫 SysUser sysUser = this.findSysUserByUserCode(userCode); if(sysUser == null){ //拋出異常 throw new CustomException("用戶帳號不存在"); } //數據庫密碼 (md5密碼 ) String password_db = sysUser.getPassword(); //對輸入的密碼 和數據庫密碼 進行比對,若是一致,認證經過 //對頁面輸入的密碼 進行md5加密 String password_input_md5 = new MD5().getMD5ofStr(password); if(!password_input_md5.equalsIgnoreCase(password_db)){ //拋出異常 throw new CustomException("用戶名或密碼 錯誤"); } //獲得用戶id String userid = sysUser.getId(); //根據用戶id查詢菜單 List<SysPermission> menus =this.findMenuListByUserId(userid); //根據用戶id查詢權限url List<SysPermission> permissions = this.findPermissionListByUserId(userid); //認證經過,返回用戶身份信息 ActiveUser activeUser = new ActiveUser(); activeUser.setUserid(sysUser.getId()); activeUser.setUsercode(userCode); activeUser.setUsername(sysUser.getUsername());//用戶名稱 //放入權限範圍的菜單和url activeUser.setMenus(menus); activeUser.setPermissions(permissions); return activeUser; }
Controller處理認證,若是身份認證成功,那麼把認證信息存儲在Session中
@RequestMapping("/login") public String login(HttpSession session, String randomcode,String usercode,String password)throws Exception{ //校驗驗證碼,防止惡性攻擊 //從session獲取正確驗證碼 String validateCode = (String) session.getAttribute("validateCode"); //輸入的驗證和session中的驗證進行對比 if(!randomcode.equals(validateCode)){ //拋出異常 throw new CustomException("驗證碼輸入錯誤"); } //調用service校驗用戶帳號和密碼的正確性 ActiveUser activeUser = sysService.authenticat(usercode, password); //若是service校驗經過,將用戶身份記錄到session session.setAttribute("activeUser", activeUser); //重定向到商品查詢頁面 return "redirect:/first.action"; }
身份認證攔截器
//在執行handler以前來執行的 //用於用戶認證校驗、用戶權限校驗 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //獲得請求的url String url = request.getRequestURI(); //判斷是不是公開 地址 //實際開發中須要公開 地址配置在配置文件中 //從配置中取逆名訪問url List<String> open_urls = ResourcesUtil.gekeyList("anonymousURL"); //遍歷公開 地址,若是是公開 地址則放行 for(String open_url:open_urls){ if(url.indexOf(open_url)>=0){ //若是是公開 地址則放行 return true; } } //判斷用戶身份在session中是否存在 HttpSession session = request.getSession(); ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser"); //若是用戶身份在session中存在放行 if(activeUser!=null){ return true; } //執行到這裏攔截,跳轉到登錄頁面,用戶進行身份認證 request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response); //若是返回false表示攔截不繼續執行handler,若是返回true表示放行 return false; }
受權攔截器
//在執行handler以前來執行的 //用於用戶認證校驗、用戶權限校驗 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //獲得請求的url String url = request.getRequestURI(); //判斷是不是公開 地址 //實際開發中須要公開 地址配置在配置文件中 //從配置中取逆名訪問url List<String> open_urls = ResourcesUtil.gekeyList("anonymousURL"); //遍歷公開 地址,若是是公開 地址則放行 for(String open_url:open_urls){ if(url.indexOf(open_url)>=0){ //若是是公開 地址則放行 return true; } } //從配置文件中獲取公共訪問地址 List<String> common_urls = ResourcesUtil.gekeyList("commonURL"); //遍歷公用 地址,若是是公用 地址則放行 for(String common_url:common_urls){ if(url.indexOf(common_url)>=0){ //若是是公開 地址則放行 return true; } } //獲取session HttpSession session = request.getSession(); ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser"); //從session中取權限範圍的url List<SysPermission> permissions = activeUser.getPermissions(); for(SysPermission sysPermission:permissions){ //權限的url String permission_url = sysPermission.getUrl(); if(url.indexOf(permission_url)>=0){ //若是是權限的url 地址則放行 return true; } } //執行到這裏攔截,跳轉到無權訪問的提示頁面 request.getRequestDispatcher("/WEB-INF/jsp/refuse.jsp").forward(request, response); //若是返回false表示攔截不繼續執行handler,若是返回true表示放行 return false; }
攔截器配置:
<!--攔截器 --> <mvc:interceptors> <mvc:interceptor> <!-- 用戶認證攔截 --> <mvc:mapping path="/**" /> <bean class="cn.itcast.ssm.controller.interceptor.LoginInterceptor"></bean> </mvc:interceptor> <mvc:interceptor> <!-- 受權攔截 --> <mvc:mapping path="/**" /> <bean class="cn.itcast.ssm.controller.interceptor.PermissionInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
4、什麼是Shiro
shiro是apache的一個開源框架,是一個權限管理的框架,實現 用戶認證、用戶受權。
spring中有spring security (原名Acegi),是一個權限框架,它和spring依賴過於緊密,沒有shiro使用簡單。
shiro不依賴於spring,shiro不只能夠實現 web應用的權限管理,還能夠實現c/s系統,分佈式系統權限管理,shiro屬於輕量框架,愈來愈多企業項目開始使用shiro。
Shiro架構:
- subject:主體,能夠是用戶也能夠是程序,主體要訪問系統,系統須要對主體進行認證、受權。
- securityManager:安全管理器,主體進行認證和受權都 是經過securityManager進行。
- authenticator:認證器,主體進行認證最終經過authenticator進行的。
- authorizer:受權器,主體進行受權最終經過authorizer進行的。
- sessionManager:web應用中通常是用web容器對session進行管理,shiro也提供一套session管理的方式。
- SessionDao: 經過SessionDao管理session數據,針對個性化的session數據存儲須要使用sessionDao。
- cache Manager:緩存管理器,主要對session和受權數據進行緩存,好比將受權數據經過cacheManager進行緩存管理,和ehcache整合對緩存數據進行管理。
- realm:域,領域,至關於數據源,經過realm存取認證、受權相關數據。
cryptography:密碼管理,提供了一套加密/解密的組件,方便開發。好比提供經常使用的散列、加/解密等功能。
- 好比md5散列算法。
5、爲何使用Shiro
咱們在使用URL攔截的時候,要將全部的URL都配置起來,繁瑣、不易維護
而咱們的Shiro實現系統的權限管理,有效提升開發效率,從而下降開發成本。
6、Shiro認證
6.1導入jar包
咱們使用的是Maven的座標就好了
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-quartz</artifactId> <version>1.2.3</version> </dependency>
固然了,咱們也能夠把Shiro相關的jar包所有導入進去
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-all</artifactId> <version>1.2.3</version> </dependency>
6.2Shiro認證流程
6.2.1經過配置文件建立工廠
// 用戶登錄和退出 @Test public void testLoginAndLogout() { // 建立securityManager工廠,經過ini配置文件建立securityManager工廠 Factory<SecurityManager> factory = new IniSecurityManagerFactory( "classpath:shiro-first.ini"); // 建立SecurityManager SecurityManager securityManager = factory.getInstance(); // 將securityManager設置當前的運行環境中 SecurityUtils.setSecurityManager(securityManager); // 從SecurityUtils裏邊建立一個subject Subject subject = SecurityUtils.getSubject(); // 在認證提交前準備token(令牌) // 這裏的帳號和密碼 未來是由用戶輸入進去 UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "111111"); try { // 執行認證提交 subject.login(token); } catch (AuthenticationException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 是否定證經過 boolean isAuthenticated = subject.isAuthenticated(); System.out.println("是否定證經過:" + isAuthenticated); // 退出操做 subject.logout(); // 是否定證經過 isAuthenticated = subject.isAuthenticated(); System.out.println("是否定證經過:" + isAuthenticated); }
6.3小結
ModularRealmAuthenticator做用進行認證,須要調用realm查詢用戶信息(在數據庫中存在用戶信息)
ModularRealmAuthenticator進行密碼對比(認證過程)。
realm:須要根據token中的身份信息去查詢數據庫(入門程序使用ini配置文件),若是查到用戶返回認證信息,若是查詢不到返回null。
6.4自定義realm
從第一個認證程序咱們能夠看見,咱們所說的流程,是認證器去找realm去查詢咱們相對應的數據。而默認的realm是直接去與配置文件來比對的,通常地,咱們在開發中都是讓realm去數據庫中比對。
所以,咱們須要自定義realm
public class CustomRealm extends AuthorizingRealm { // 設置realm的名稱 @Override public void setName(String name) { super.setName("customRealm"); } // 用於認證 @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { // token是用戶輸入的 // 第一步從token中取出身份信息 String userCode = (String) token.getPrincipal(); // 第二步:根據用戶輸入的userCode從數據庫查詢 // .... // 若是查詢不到返回null //數據庫中用戶帳號是zhangsansan /*if(!userCode.equals("zhangsansan")){// return null; }*/ // 模擬從數據庫查詢到密碼 String password = "111112"; // 若是查詢到返回認證信息AuthenticationInfo SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo( userCode, password, this.getName()); return simpleAuthenticationInfo; } // 用於受權 @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { // TODO Auto-generated method stub return null; } }
6.5配置realm
須要在shiro-realm.ini配置realm注入到securityManager中。
6.6測試自定義realm
同上邊的入門程序,須要更改ini配置文件路徑:
同上邊的入門程序,須要更改ini配置文件路徑:
Factory<SecurityManager> factory = new IniSecurityManagerFactory( "classpath:shiro-realm.ini");
6.7散列算法
咱們若是知道md5,咱們就會知道md5是不可逆的,可是若是設置了一些安全性比較低的密碼:111111...即時是不可逆的,但仍是能夠經過暴力算法來獲得md5對應的明文...
建議對md5進行散列時加salt(鹽),進行加密至關 於對原始密碼+鹽進行散列。\
正常使用時散列方法:
- 在程序中對原始密碼+鹽進行散列,將散列值存儲到數據庫中,而且還要將鹽也要存儲在數據庫中。
測試:
public class MD5Test { public static void main(String[] args) { //原始 密碼 String source = "111111"; //鹽 String salt = "qwerty"; //散列次數 int hashIterations = 2; //上邊散列1次:f3694f162729b7d0254c6e40260bf15c //上邊散列2次:36f2dfa24d0a9fa97276abbe13e596fc //構造方法中: //第一個參數:明文,原始密碼 //第二個參數:鹽,經過使用隨機數 //第三個參數:散列的次數,好比散列兩次,至關 於md5(md5('')) Md5Hash md5Hash = new Md5Hash(source, salt, hashIterations); String password_md5 = md5Hash.toString(); System.out.println(password_md5); //第一個參數:散列算法 SimpleHash simpleHash = new SimpleHash("md5", source, salt, hashIterations); System.out.println(simpleHash.toString()); } }
6.8自定義realm支持md5
自定義realm
public class CustomRealmMd5 extends AuthorizingRealm { // 設置realm的名稱 @Override public void setName(String name) { super.setName("customRealmMd5"); } // 用於認證 @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { // token是用戶輸入的 // 第一步從token中取出身份信息 String userCode = (String) token.getPrincipal(); // 第二步:根據用戶輸入的userCode從數據庫查詢 // .... // 若是查詢不到返回null // 數據庫中用戶帳號是zhangsansan /* * if(!userCode.equals("zhangsansan")){// return null; } */ // 模擬從數據庫查詢到密碼,散列值 String password = "f3694f162729b7d0254c6e40260bf15c"; // 從數據庫獲取salt String salt = "qwerty"; //上邊散列值和鹽對應的明文:111111 // 若是查詢到返回認證信息AuthenticationInfo SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo( userCode, password, ByteSource.Util.bytes(salt), this.getName()); return simpleAuthenticationInfo; } // 用於受權 @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { // TODO Auto-generated method stub return null; } }
配置文件:
測試:
// 自定義realm實現散列值匹配 @Test public void testCustomRealmMd5() { // 建立securityManager工廠,經過ini配置文件建立securityManager工廠 Factory<SecurityManager> factory = new IniSecurityManagerFactory( "classpath:shiro-realm-md5.ini"); // 建立SecurityManager SecurityManager securityManager = factory.getInstance(); // 將securityManager設置當前的運行環境中 SecurityUtils.setSecurityManager(securityManager); // 從SecurityUtils裏邊建立一個subject Subject subject = SecurityUtils.getSubject(); // 在認證提交前準備token(令牌) // 這裏的帳號和密碼 未來是由用戶輸入進去 UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "222222"); try { // 執行認證提交 subject.login(token); } catch (AuthenticationException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 是否定證經過 boolean isAuthenticated = subject.isAuthenticated(); System.out.println("是否定證經過:" + isAuthenticated); }
7、總結
- 用戶認證和用戶受權是Shiro的基礎,用戶認證其實上就是登錄操做、用戶受權實際上就是對資源攔截的操做。
- 權限管理的模型通常咱們都將資源放在權限表中進行管理起來。
- 咱們能夠基於角色攔截,也能夠基於資源攔截。要是基於角色攔截的話,那麼若是角色的權限發生變化了,那就須要修改代碼了。推薦使用基於資源進行攔截
- 此次URL攔截,咱們使用一個JavaBean來封裝全部的認證信息。當用戶登錄了以後,咱們就把用戶對菜單欄的訪問、對資源的訪問權限都封裝到該JavaBean中
- 當使用攔截器進行用戶認證的時候,咱們只要判斷Session域有沒有JavaBen對象便可了。
- 當時候攔截器進行用戶受權的時候,咱們要判斷JavaBean中的權限是否可以訪問該資源。
- 之前URL攔截的方式須要把全部的URL都在數據庫進行管理。很是麻煩,不易維護。
- 咱們但願Shiro去認證的時候是經過realm去數據庫查詢數據的。而咱們reaml默認是查詢配置文件的數據的。
- 所以,咱們須要自定義reaml,使得它是去數據庫查詢數據。只要繼承AuthorizingRealm類就好了。
- 固然了,自定義後的reaml也須要在配置文件中寫上咱們的自定義reaml的位置的。
- 散列算法就是爲了讓密碼不被別人給破解。咱們可對原始的密碼加鹽再進行散列,這就加大了破解的難度了。
- 自定義的reaml也是支持散列算法的,相同的,仍是須要咱們在配置文件中配置一下就行了。