Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 国产日韩精品一区在线不卡,最近中文字幕最新在线视频,亚洲精品国产成人

          整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          Spring Boot 整合 Shiro-登錄認證和

          Spring Boot 整合 Shiro-登錄認證和權限管理

          篇文章我們來學習如何使用 Spring Boot 集成 Apache Shiro 。安全應該是互聯網公司的一道生命線,幾乎任何的公司都會涉及到這方面的需求。在 Java 領域一般有 Spring Security、 Apache Shiro 等安全框架,但是由于 Spring Security 過于龐大和復雜,大多數公司會選擇 Apache Shiro 來使用,這篇文章會先介紹一下 Apache Shiro ,在結合 Spring Boot 給出使用案例。

          Apache Shiro

          What is Apache Shiro?

          Apache Shiro 是一個功能強大、靈活的,開源的安全框架。它可以干凈利落地處理身份驗證、授權、企業會話管理和加密。

          Apache Shiro 的首要目標是易于使用和理解。安全通常很復雜,甚至讓人感到很痛苦,但是 Shiro 卻不是這樣子的。一個好的安全框架應該屏蔽復雜性,向外暴露簡單、直觀的 API,來簡化開發人員實現應用程序安全所花費的時間和精力。

          Shiro 能做什么呢?

          • 驗證用戶身份
          • 用戶訪問權限控制,比如:1、判斷用戶是否分配了一定的安全角色。2、判斷用戶是否被授予完成某個操作的權限
          • 在非 Web 或 EJB 容器的環境下可以任意使用 Session API
          • 可以響應認證、訪問控制,或者 Session 生命周期中發生的事件
          • 可將一個或以上用戶安全數據源數據組合成一個復合的用戶 “view”(視圖)
          • 支持單點登錄(SSO)功能
          • 支持提供“Remember Me”服務,獲取用戶關聯信息而無需登錄

          等等——都集成到一個有凝聚力的易于使用的 API。

          Shiro 致力在所有應用環境下實現上述功能,小到命令行應用程序,大到企業應用中,而且不需要借助第三方框架、容器、應用服務器等。當然 Shiro 的目的是盡量的融入到這樣的應用環境中去,但也可以在它們之外的任何環境下開箱即用。

          Apache Shiro Features 特性

          Apache Shiro 是一個全面的、蘊含豐富功能的安全框架。下圖為描述 Shiro 功能的框架圖:

          Authentication(認證), Authorization(授權), Session Management(會話管理), Cryptography(加密)被 Shiro 框架的開發團隊稱之為應用安全的四大基石。那么就讓我們來看看它們吧:

          • Authentication(認證):用戶身份識別,通常被稱為用戶“登錄”
          • Authorization(授權):訪問控制。比如某個用戶是否具有某個操作的使用權限。
          • Session Management(會話管理):特定于用戶的會話管理,甚至在非web 或 EJB 應用程序。
          • Cryptography(加密):在對數據源使用加密算法加密的同時,保證易于使用。

          還有其他的功能來支持和加強這些不同應用環境下安全領域的關注點。特別是對以下的功能支持:

          • Web支持:Shiro 提供的 Web 支持 api ,可以很輕松的保護 Web 應用程序的安全。
          • 緩存:緩存是 Apache Shiro 保證安全操作快速、高效的重要手段。
          • 并發:Apache Shiro 支持多線程應用程序的并發特性。
          • 測試:支持單元測試和集成測試,確保代碼和預想的一樣安全。
          • “Run As”:這個功能允許用戶假設另一個用戶的身份(在許可的前提下)。
          • “Remember Me”:跨 session 記錄用戶的身份,只有在強制需要時才需要登錄。

          注意: Shiro 不會去維護用戶、維護權限,這些需要我們自己去設計/提供,然后通過相應的接口注入給 Shiro

          High-Level Overview 高級概述

          在概念層,Shiro 架構包含三個主要的理念:Subject,SecurityManager和 Realm。下面的圖展示了這些組件如何相互作用,我們將在下面依次對其進行描述。

          • Subject:當前用戶,Subject 可以是一個人,但也可以是第三方服務、守護進程帳戶、時鐘守護任務或者其它–當前和軟件交互的任何事件。
          • SecurityManager:管理所有Subject,SecurityManager 是 Shiro 架構的核心,配合內部安全組件共同組成安全傘。
          • Realms:用于進行權限信息的驗證,我們自己實現。Realm 本質上是一個特定的安全 DAO:它封裝與數據源連接的細節,得到Shiro 所需的相關的數據。在配置 Shiro 的時候,你必須指定至少一個Realm 來實現認證(authentication)和/或授權(authorization)。

          我們需要實現Realms的Authentication 和 Authorization。其中 Authentication 是用來驗證用戶身份,Authorization 是授權訪問控制,用于對用戶進行的操作授權,證明該用戶是否允許進行當前操作,如訪問某個鏈接,某個資源文件等。

          快速上手

          基礎信息

          pom包依賴

          <dependencies>
          	<dependency>
          		<groupId>org.springframework.boot</groupId>
          		<artifactId>spring-boot-starter-data-jpa</artifactId>
          	</dependency>
          	<dependency>
          		<groupId>org.springframework.boot</groupId>
          		<artifactId>spring-boot-starter-thymeleaf</artifactId>
          	</dependency>
          	<dependency>
          		<groupId>net.sourceforge.nekohtml</groupId>
          		<artifactId>nekohtml</artifactId>
          		<version>1.9.22</version>
          	</dependency>
          	<dependency>
          		<groupId>org.springframework.boot</groupId>
          		<artifactId>spring-boot-starter-web</artifactId>
          	</dependency>
          	<dependency>
          		<groupId>org.apache.shiro</groupId>
          		<artifactId>shiro-spring</artifactId>
          		<version>1.4.0</version>
          	</dependency>
          	<dependency>
          		<groupId>mysql</groupId>
          		<artifactId>mysql-connector-java</artifactId>
          		<scope>runtime</scope>
          	</dependency>
          </dependencies>
          

          重點是 shiro-spring 包

          配置文件

          spring:
              datasource:
                url: jdbc:mysql://localhost:3306/test
                username: root
                password: root
                driver-class-name: com.mysql.jdbc.Driver
          
              jpa:
                database: mysql
                show-sql: true
                hibernate:
                  ddl-auto: update
                  naming:
                    strategy: org.hibernate.cfg.DefaultComponentSafeNamingStrategy
                properties:
                   hibernate:
                      dialect: org.hibernate.dialect.MySQL5Dialect
          
              thymeleaf:
                 cache: false
                 mode: LEGACYHTML5
          

          thymeleaf的配置是為了去掉html的校驗

          頁面

          我們新建了六個頁面用來測試:

          • index.html :首頁
          • login.html :登錄頁
          • userInfo.html : 用戶信息頁面
          • userInfoAdd.html :添加用戶頁面
          • userInfoDel.html :刪除用戶頁面
          • 403.html : 沒有權限的頁面

          除過登錄頁面其它都很簡單,大概如下:

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>Title</title>
          </head>
          <body>
          <h1>index</h1>
          </body>
          </html>
          

          RBAC

          RBAC 是基于角色的訪問控制(Role-Based Access Control )在 RBAC 中,權限與角色相關聯,用戶通過成為適當角色的成員而得到這些角色的權限。這就極大地簡化了權限的管理。這樣管理都是層級相互依賴的,權限賦予給角色,而把角色又賦予用戶,這樣的權限設計很清楚,管理起來很方便。

          采用 Jpa 技術來自動生成基礎表格,對應的實體如下:

          用戶信息

          @Entity
          public class UserInfo implements Serializable {
              @Id
              @GeneratedValue
              private Integer uid;
              @Column(unique=true)
              private String username;//帳號
              private String name;//名稱(昵稱或者真實姓名,不同系統不同定義)
              private String password; //密碼;
              private String salt;//加密密碼的鹽
              private byte state;//用戶狀態,0:創建未認證(比如沒有激活,沒有輸入驗證碼等等)--等待驗證的用戶 , 1:正常狀態,2:用戶被鎖定.
              @ManyToMany(fetch=FetchType.EAGER)//立即從數據庫中進行加載數據;
              @JoinTable(name="SysUserRole", joinColumns={ @JoinColumn(name="uid") }, inverseJoinColumns={@JoinColumn(name="roleId") })
              private List<SysRole> roleList;// 一個用戶具有多個角色
          
              // 省略 get set 方法
           }
          

          角色信息

          @Entity
          public class SysRole {
              @Id@GeneratedValue
              private Integer id; // 編號
              private String role; // 角色標識程序中判斷使用,如"admin",這個是唯一的:
              private String description; // 角色描述,UI界面顯示使用
              private Boolean available=Boolean.FALSE; // 是否可用,如果不可用將不會添加給用戶
          
              //角色 -- 權限關系:多對多關系;
              @ManyToMany(fetch=FetchType.EAGER)
              @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")})
              private List<SysPermission> permissions;
          
              // 用戶 - 角色關系定義;
              @ManyToMany
              @JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="uid")})
              private List<UserInfo> userInfos;// 一個角色對應多個用戶
          
              // 省略 get set 方法
           }
          

          權限信息

          @Entity
          public class SysPermission implements Serializable {
              @Id@GeneratedValue
              private Integer id;//主鍵.
              private String name;//名稱.
              @Column(columnDefinition="enum('menu','button')")
              private String resourceType;//資源類型,[menu|button]
              private String url;//資源路徑.
              private String permission; //權限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
              private Long parentId; //父編號
              private String parentIds; //父編號列表
              private Boolean available=Boolean.FALSE;
              @ManyToMany
              @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")})
              private List<SysRole> roles;
          
              // 省略 get set 方法
           }
          

          根據以上的代碼會自動生成 user_info(用戶信息表)、sys_role(角色表)、sys_permission(權限表)、sys_user_role(用戶角色表)、sys_role_permission(角色權限表)這五張表,為了方便測試我們給這五張表插入一些初始化數據:

          INSERT INTO `user_info` (`uid`,`username`,`name`,`password`,`salt`,`state`) VALUES ('1', 'admin', '管理員', 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', 0);
          INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (1,0,'用戶管理',0,'0/','userInfo:view','menu','userInfo/userList');
          INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (2,0,'用戶添加',1,'0/1','userInfo:add','button','userInfo/userAdd');
          INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (3,0,'用戶刪除',1,'0/1','userInfo:del','button','userInfo/userDel');
          INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (1,0,'管理員','admin');
          INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (2,0,'VIP會員','vip');
          INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (3,1,'test','test');
          INSERT INTO `sys_role_permission` VALUES ('1', '1');
          INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (1,1);
          INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (2,1);
          INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (3,2);
          INSERT INTO `sys_user_role` (`role_id`,`uid`) VALUES (1,1);
          

          Shiro 配置

          首先要配置的是 ShiroConfig 類,Apache Shiro 核心通過 Filter 來實現,就好像 SpringMvc 通過 DispachServlet 來主控制一樣。 既然是使用 Filter 一般也就能猜到,是通過 URL 規則來進行過濾和權限校驗,所以我們需要定義一系列關于 URL 的規則和訪問權限。

          ShiroConfig

          @Configuration
          public class ShiroConfig {
          	@Bean
          	public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
          		System.out.println("ShiroConfiguration.shirFilter()");
          		ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
          		shiroFilterFactoryBean.setSecurityManager(securityManager);
          		//攔截器.
          		Map<String,String> filterChainDefinitionMap=new LinkedHashMap<String,String>();
          		// 配置不會被攔截的鏈接 順序判斷
          		filterChainDefinitionMap.put("/static/**", "anon");
          		//配置退出 過濾器,其中的具體的退出代碼Shiro已經替我們實現了
          		filterChainDefinitionMap.put("/logout", "logout");
          		//<!-- 過濾鏈定義,從上向下順序執行,一般將/**放在最為下邊 -->:這是一個坑呢,一不小心代碼就不好使了;
          		//<!-- authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問-->
          		filterChainDefinitionMap.put("/**", "authc");
          		// 如果不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面
          		shiroFilterFactoryBean.setLoginUrl("/login");
          		// 登錄成功后要跳轉的鏈接
          		shiroFilterFactoryBean.setSuccessUrl("/index");
          
          		//未授權界面;
          		shiroFilterFactoryBean.setUnauthorizedUrl("/403");
          		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
          		return shiroFilterFactoryBean;
          	}
          
          	@Bean
          	public MyShiroRealm myShiroRealm(){
          		MyShiroRealm myShiroRealm=new MyShiroRealm();
          		return myShiroRealm;
          	}
          
          
          	@Bean
          	public SecurityManager securityManager(){
          		DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
          		securityManager.setRealm(myShiroRealm());
          		return securityManager;
          	}
          }
          

          Filter Chain 定義說明:

          • 1、一個URL可以配置多個 Filter,使用逗號分隔
          • 2、當設置多個過濾器時,全部驗證通過,才視為通過
          • 3、部分過濾器可指定參數,如 perms,roles

          Shiro 內置的 FilterChain

          Filter NameClassanonorg.apache.shiro.web.filter.authc.AnonymousFilterauthcorg.apache.shiro.web.filter.authc.FormAuthenticationFilterauthcBasicorg.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilterpermsorg.apache.shiro.web.filter.authz.PermissionsAuthorizationFilterportorg.apache.shiro.web.filter.authz.PortFilterrestorg.apache.shiro.web.filter.authz.HttpMethodPermissionFilterrolesorg.apache.shiro.web.filter.authz.RolesAuthorizationFiltersslorg.apache.shiro.web.filter.authz.SslFilteruserorg.apache.shiro.web.filter.authc.UserFilter

          • anon:所有 url 都都可以匿名訪問
          • authc: 需要認證才能進行訪問
          • user:配置記住我或認證通過可以訪問

          登錄認證實現

          在認證、授權內部實現機制中都有提到,最終處理都將交給Real進行處理。因為在 Shiro 中,最終是通過 Realm 來獲取應用程序中的用戶、角色及權限信息的。通常情況下,在 Realm 中會直接從我們的數據源中獲取 Shiro 需要的驗證信息。可以說,Realm 是專用于安全框架的 DAO. Shiro 的認證過程最終會交由 Realm 執行,這時會調用 Realm 的getAuthenticationInfo(token)方法。

          該方法主要執行以下操作:

          • 1、檢查提交的進行認證的令牌信息
          • 2、根據令牌信息從數據源(通常為數據庫)中獲取用戶信息
          • 3、對用戶信息進行匹配驗證。
          • 4、驗證通過將返回一個封裝了用戶信息的AuthenticationInfo實例。
          • 5、驗證失敗則拋出AuthenticationException異常信息。

          而在我們的應用程序中要做的就是自定義一個 Realm 類,繼承AuthorizingRealm 抽象類,重載 doGetAuthenticationInfo(),重寫獲取用戶信息的方法。

          doGetAuthenticationInfo 的重寫

          @Override
          protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
                  throws AuthenticationException {
              System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
              //獲取用戶的輸入的賬號.
              String username=(String)token.getPrincipal();
              System.out.println(token.getCredentials());
              //通過username從數據庫中查找 User對象,如果找到,沒找到.
              //實際項目中,這里可以根據實際情況做緩存,如果不做,Shiro自己也是有時間間隔機制,2分鐘內不會重復執行該方法
              UserInfo userInfo=userInfoService.findByUsername(username);
              System.out.println("----->>userInfo="+userInfo);
              if(userInfo==null){
                  return null;
              }
              SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(
                      userInfo, //用戶名
                      userInfo.getPassword(), //密碼
                      ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt
                      getName()  //realm name
              );
              return authenticationInfo;
          }
          

          鏈接權限的實現

          Shiro 的權限授權是通過繼承AuthorizingRealm抽象類,重載doGetAuthorizationInfo();當訪問到頁面的時候,鏈接配置了相應的權限或者 Shiro 標簽才會執行此方法否則不會執行,所以如果只是簡單的身份認證沒有權限的控制的話,那么這個方法可以不進行實現,直接返回 null 即可。在這個方法中主要是使用類:SimpleAuthorizationInfo進行角色的添加和權限的添加。

          @Override
          protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
              System.out.println("權限配置-->MyShiroRealm.doGetAuthorizationInfo()");
              SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
              UserInfo userInfo=(UserInfo)principals.getPrimaryPrincipal();
              for(SysRole role:userInfo.getRoleList()){
                  authorizationInfo.addRole(role.getRole());
                  for(SysPermission p:role.getPermissions()){
                      authorizationInfo.addStringPermission(p.getPermission());
                  }
              }
              return authorizationInfo;
          }
          

          當然也可以添加 set 集合:roles 是從數據庫查詢的當前用戶的角色,stringPermissions 是從數據庫查詢的當前用戶對應的權限

          authorizationInfo.setRoles(roles);
          authorizationInfo.setStringPermissions(stringPermissions);
          

          就是說如果在shiro配置文件中添加了filterChainDefinitionMap.put(“/add”, “perms[權限添加]”);就說明訪問/add這個鏈接必須要有“權限添加”這個權限才可以訪問,如果在shiro配置文件中添加了filterChainDefinitionMap.put(“/add”, “roles[100002],perms[權限添加]”);就說明訪問/add這個鏈接必須要有“權限添加”這個權限和具有“100002”這個角色才可以訪問。

          登錄實現

          登錄過程其實只是處理異常的相關信息,具體的登錄驗證交給 Shiro 來處理

          @RequestMapping("/login")
          public String login(HttpServletRequest request, Map<String, Object> map) throws Exception{
              System.out.println("HomeController.login()");
              // 登錄失敗從request中獲取shiro處理的異常信息。
              // shiroLoginFailure:就是shiro異常類的全類名.
              String exception=(String) request.getAttribute("shiroLoginFailure");
              System.out.println("exception=" + exception);
              String msg="";
              if (exception !=null) {
                  if (UnknownAccountException.class.getName().equals(exception)) {
                      System.out.println("UnknownAccountException -- > 賬號不存在:");
                      msg="UnknownAccountException -- > 賬號不存在:";
                  } else if (IncorrectCredentialsException.class.getName().equals(exception)) {
                      System.out.println("IncorrectCredentialsException -- > 密碼不正確:");
                      msg="IncorrectCredentialsException -- > 密碼不正確:";
                  } else if ("kaptchaValidateFailed".equals(exception)) {
                      System.out.println("kaptchaValidateFailed -- > 驗證碼錯誤");
                      msg="kaptchaValidateFailed -- > 驗證碼錯誤";
                  } else {
                      msg="else >> "+exception;
                      System.out.println("else -- >" + exception);
                  }
              }
              map.put("msg", msg);
              // 此方法不處理登錄成功,由shiro進行處理
              return "/login";
          }
          

          其它 Dao 層和 Service 的代碼就不貼出來了大家直接看代碼。

          測試

          1、編寫好后就可以啟動程序,訪問http://localhost:8080/userInfo/userList頁面,由于沒有登錄就會跳轉到http://localhost:8080/login頁面。登錄之后就會跳轉到 index 頁面,登錄后,直接在瀏覽器中輸入http://localhost:8080/userInfo/userList訪問就會看到用戶信息。上面這些操作時候觸發MyShiroRealm.doGetAuthenticationInfo()這個方法,也就是登錄認證的方法。

          2、登錄admin賬戶,訪問:http://127.0.0.1:8080/userInfo/userAdd顯示用戶添加界面,訪問http://127.0.0.1:8080/userInfo/userDel顯示403沒有權限。上面這些操作時候觸發MyShiroRealm.doGetAuthorizationInfo()這個方面,也就是權限校驗的方法。

          3、修改 admin不 同的權限進行測試

          Shiro 很強大,這僅僅是完成了登錄認證和權限管理這兩個功能,更多內容以后有時間再做探討。

          后臺權限管理系統

          相關:

          spring boot + mybatis + layui + shiro后臺權限管理系統

          springboot + shiro之登錄人數限制、登錄判斷重定向、session時間設置

          springboot + shiro 動態更新用戶信息

          基于前篇,新增功能:

          1. 新增shiro權限注解;
          2. 請求亂碼問題解決;
          3. 統一異常處理。

          源碼已集成到項目中:

          github源碼: https://github.com/wyait/manage.git

          碼云:https://gitee.com/wyait/manage.git

          github對應項目源碼目錄:wyait-manage-1.2.0

          碼云對應項目源碼目錄:wyait-manage-1.2.0

          shiro注解的使用

          shiro權限注解

          Shiro 提供了相應的注解用于權限控制,如果使用這些注解就需要使用AOP 的功能來進行判斷,如Spring AOP;Shiro 提供了Spring AOP 集成用于權限注解的解析和驗證。

           @RequiresAuthentication
            表示當前Subject已經通過login 進行了身份驗證;即Subject.isAuthenticated()返回true。
            @RequiresUser
            表示當前Subject已經身份驗證或者通過記住我登錄的。
            @RequiresGuest
            表示當前Subject沒有身份驗證或通過記住我登錄過,即是游客身份。
            @RequiresRoles(value={“admin”, “user”}, logical=Logical.AND)
            @RequiresRoles(value={“admin”})
            @RequiresRoles({“admin“})
            表示當前Subject需要角色admin 和user。
            @RequiresPermissions (value={“user:a”, “user:b”}, logical=Logical.OR)
            表示當前Subject需要權限user:a或user:b。
          

          Shiro的認證注解處理是有內定的處理順序的,如果有多個注解的話,前面的通過了會繼續檢查后面的,若不通過則直接返回,處理順序依次為(與實際聲明順序無關):

          RequiresRoles
          RequiresPermissions
          RequiresAuthentication
          RequiresUser
          RequiresGuest
          

          以上注解既可以用在controller中,也可以用在service中使用;

          建議將shiro注解放在controller中,因為如果service層使用了spring的事物注解,那么shiro注解將無效。

          shiro權限注解springAOP配置

          shiro權限注解要生效,必須配置springAOP通過設置shiro的SecurityManager進行權限驗證。

          /**
           * 
           * @描述:開啟Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP掃描使用Shiro注解的類,并在必要時進行安全邏輯驗證
           * 配置以下兩個bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)即可實現此功能
           * </br>Enable Shiro Annotations for Spring-configured beans. Only run after the lifecycleBeanProcessor(保證實現了Shiro內部lifecycle函數的bean執行) has run
           * </br>不使用注解的話,可以注釋掉這兩個配置
           * @創建人:wyait
           * @創建時間:2018年5月21日 下午6:07:56
           * @return
           */
           @Bean
           public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
           DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
           advisorAutoProxyCreator.setProxyTargetClass(true);
           return advisorAutoProxyCreator;
           }
           @Bean
           public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
           AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor=new AuthorizationAttributeSourceAdvisor();
           authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
           return authorizationAttributeSourceAdvisor;
           }
          

          springboot異常處理原理

          場景:當用戶正常訪問網站時,因為某種原因后端出現exception的時候,直接暴露異常信息或頁面顯示給用戶;

          這種操作體驗不是我們想要的。所以要對異常進行統一管理,能提高用戶體驗的同時,后臺能詳細定位到異常的問題點。

          springboot異常概況

          Spring Boot提供了默認的統一錯誤頁面,這是Spring MVC沒有提供的。在理解了Spring Boot提供的錯誤處理相關內容之后,我們可以方便的定義自己的錯誤返回的格式和內容。

          編寫by zero異常

          在home頁面,手動創建兩個異常:普通異常和異步異常!

          • 前端頁面:
          <p>
           普通請求異常:
           <a href="/error/getError">點擊</a>
          </p>
          <p>
           ajax異步請求異常:
           <a href="javascript:void(0)" onclick="ajaxError()">點擊</a>
          </p>
          ... 
          //js代碼
          function ajaxError(){
           $.get("/error/ajaxError",function(data){
           layer.alert(data);
           });
          }
          
          • 后端代碼:
          /**
           * 
           * @描述:普通請求異常
           * @創建人:wyait
           * @創建時間:2018年5月24日 下午5:30:50
           */
          @RequestMapping("getError")
          public void toError(){
           System.out.println(1/0);
          }
          /**
           * 
           * @描述:異步異常
           * @創建人:wyait
           * @創建時間:2018年5月24日 下午5:30:39
           */
          @RequestMapping("ajaxError")
          @ResponseBody
          public String ajaxError(){
           System.out.println(1/0);
           return "異步請求成功!";
          }
          

          異常效果

          • 普通異常:

          console錯誤信息:

          [2018-05-25 09:30:04.669][http-nio-8077-exec-8][ERROR][org.apache.juli.logging.DirectJDKLog][181]:Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause
          java.lang.ArithmeticException: / by zero
           at com.wyait.manage.web.error.IndexErrorController.toError(IndexErrorController.java:18) ~[classes/:?]
           ...
           at java.lang.Thread.run(Thread.java:748) [?:1.8.0_131]
          ...
          [2018-05-25 09:30:04.676][http-nio-8077-exec-8][DEBUG][org.springframework.web.servlet.handler.AbstractHandlerMethodMapping][317]:Returning handler method [public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)]
          [2018-05-25 09:30:04.676][http-nio-8077-exec-8][DEBUG][org.springframework.web.servlet.handler.AbstractHandlerMethodMapping][317]:Returning handler method [public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)]
          [2018-05-25 09:30:04.676][http-nio-8077-exec-8][DEBUG][org.springframework.beans.factory.support.AbstractBeanFactory][251]:Returning cached instance of singleton bean 'basicErrorController'
          [2018-05-25 09:30:04.676][http-nio-8077-exec-8][DEBUG][org.springframework.beans.factory.support.AbstractBeanFactory][251]:Returning cached instance of singleton bean 'basicErrorController'
          ...
          [2018-05-25 09:30:04.686][http-nio-8077-exec-8][DEBUG][org.springframework.web.servlet.view.ContentNegotiatingViewResolver][263]:Requested media types are [text/html, text/html;q=0.8] based on Accept header types and producible media types [text/html])
          [2018-05-25 09:30:04.686][http-nio-8077-exec-8][DEBUG][org.springframework.web.servlet.view.ContentNegotiatingViewResolver][263]:Requested media types are [text/html, text/html;q=0.8] based on Accept header types and producible media types [text/html])
          [2018-05-25 09:30:04.686][http-nio-8077-exec-8][DEBUG][org.springframework.beans.factory.support.AbstractBeanFactory][251]:Returning cached instance of singleton bean 'error'
          [2018-05-25 09:30:04.686][http-nio-8077-exec-8][DEBUG][org.springframework.beans.factory.support.AbstractBeanFactory][251]:Returning cached instance of singleton bean 'error'
          [2018-05-25 09:30:04.686][http-nio-8077-exec-8][DEBUG][org.springframework.web.servlet.view.ContentNegotiatingViewResolver][338]:Returning [org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$SpelView@6ffd99fb] based on requested media type 'text/html'
          [2018-05-25 09:30:04.686][http-nio-8077-exec-8][DEBUG][org.springframework.web.servlet.view.ContentNegotiatingViewResolver][338]:Returning [org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$SpelView@6ffd99fb] based on requested media type 'text/html'
          ...
          

          通過日志可知,springboot返回的錯誤頁面,是通過:org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml處理返回ModelAndView。

          • 異步異常:


          • console日志信息:
          [2018-05-25 09:31:19.958][http-nio-8077-exec-6][ERROR][org.apache.juli.logging.DirectJDKLog][181]:Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause
          java.lang.ArithmeticException: / by zero
           at com.wyait.manage.web.error.IndexErrorController.ajaxError(IndexErrorController.java:29) ~[classes/:?]
           ...
           at java.lang.Thread.run(Thread.java:748) [?:1.8.0_131]
          ...
          [2018-05-25 09:31:19.960][http-nio-8077-exec-6][DEBUG][org.springframework.web.servlet.handler.AbstractHandlerMethodMapping][317]:Returning handler method [public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)]
          [2018-05-25 09:31:19.960][http-nio-8077-exec-6][DEBUG][org.springframework.web.servlet.handler.AbstractHandlerMethodMapping][317]:Returning handler method [public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)]
          [2018-05-25 09:31:19.960][http-nio-8077-exec-6][DEBUG][org.springframework.beans.factory.support.AbstractBeanFactory][251]:Returning cached instance of singleton bean 'basicErrorController'
          [2018-05-25 09:31:19.960][http-nio-8077-exec-6][DEBUG][org.springframework.beans.factory.support.AbstractBeanFactory][251]:Returning cached instance of singleton bean 'basicErrorController'
          ...
          [2018-05-25 09:31:19.961][http-nio-8077-exec-6][DEBUG][org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor][234]:Written [{timestamp=Fri May 25 09:31:19 CST 2018, status=500, error=Internal Server Error, exception=java.lang.ArithmeticException, message=/ by zero, path=/error/ajaxError}] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@2729eae5]
          [2018-05-25 09:31:19.961][http-nio-8077-exec-6][DEBUG][org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor][234]:Written [{timestamp=Fri May 25 09:31:19 CST 2018, status=500, error=Internal Server Error, exception=java.lang.ArithmeticException, message=/ by zero, path=/error/ajaxError}] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@2729eae5]
          [2018-05-25 09:31:19.961][http-nio-8077-exec-6][DEBUG][org.springframework.web.servlet.DispatcherServlet][1048]:Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling
          ...
          

          通過日志可知,springboot返回的錯誤信息,是通過:org.springframework.boot.autoconfigure.web.BasicErrorController.error處理返回ResponseEntity<String,Object>。

          • 異常都是通過org.springframework.boot.autoconfigure.web.BasicErrorController控制處理的。

          springboot異常處理解析

          查看org.springframework.boot.autoconfigure.web包下面的類,跟蹤springboot對error異常處理機制。自動配置通過一個MVC error控制器處理錯誤

          通過spring-boot-autoconfigure引入

          查看springboot 處理error的類

          springboot的自動配置,在web中處理error相關的自動配置類:ErrorMvcAutoConfiguration。查看與處理error相關的類:

          • ErrorMvcAutoConfiguration.class
          • ErrorAttibutes.class
          • ErrorController.class
          • ErrorProperties.class
          • ErrorViewResolver.class
          • ...

          ErrorAutoConfiguration類源碼//TODO

          ErrorAutoConfiguration注冊的bean

          //4個BEAN
          @Bean
          @ConditionalOnMissingBean(value=ErrorAttributes.class, search=SearchStrategy.CURRENT)
          public DefaultErrorAttributes errorAttributes() {
           return new DefaultErrorAttributes();
          }
          @Bean
          @ConditionalOnMissingBean(value=ErrorController.class, search=SearchStrategy.CURRENT)
          public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
           return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
           this.errorViewResolvers);
          }
          @Bean
          public ErrorPageCustomizer errorPageCustomizer() {
           return new ErrorPageCustomizer(this.serverProperties);
          }
          @Bean
          public static PreserveErrorControllerTargetClassPostProcessor preserveErrorControllerTargetClassPostProcessor() {
           return new PreserveErrorControllerTargetClassPostProcessor();
          }
          
          1. DefaultErrorAttributes類
          @Order(Ordered.HIGHEST_PRECEDENCE)
          public class DefaultErrorAttributes
           implements ErrorAttributes, HandlerExceptionResolver, Ordered {
           ... 
           }
          

          ErrorAttributes:

          public interface ErrorAttributes {
           Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
           boolean includeStackTrace);
           Throwable getError(RequestAttributes requestAttributes);
          }
          

          HandlerExceptionResolver:

          public interface HandlerExceptionResolver {
           /**
           * Try to resolve the given exception that got thrown during handler execution,
           * returning a {@link ModelAndView} that represents a specific error page if appropriate.
           */
           ModelAndView resolveException(
           HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
          }
          

          DefaultErrorAttributes類:

          • 實現了ErrorAttributes接口,當處理/error錯誤頁面時,可以在該bean中讀取錯誤信息響應返回;
          • 實現了HandlerExceptionResolver接口。

          debug跟蹤源碼:即DispatcherServlet在doDispatch過程中有異常拋出時:

          一. 先由HandlerExceptionResolver.resolveException解析異常并保存在request中;

          二. 再DefaultErrorAttributes.getErrorAttributes處理;DefaultErrorAttributes在處理過程中,從request中獲取錯誤信息,將錯誤信息保存到RequestAttributes中;

          三. 最后在獲取錯誤信息getError(RequestAttributes)時,從RequestAttributes中取到錯誤信息。

          1. BasicErrorController類
          @Controller
          @RequestMapping("${server.error.path:${error.path:/error}}")
          public class BasicErrorController extends AbstractErrorController {
           private final ErrorProperties errorProperties;
           ...
           @RequestMapping(produces="text/html")
           public ModelAndView errorHtml(HttpServletRequest request,
           HttpServletResponse response) {
           HttpStatus status=getStatus(request);
           Map<String, Object> model=Collections.unmodifiableMap(getErrorAttributes(
           request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
           response.setStatus(status.value());
           ModelAndView modelAndView=resolveErrorView(request, response, status, model);
           return (modelAndView==null ? new ModelAndView("error", model) : modelAndView);
           }
           @RequestMapping
           @ResponseBody
           public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
           Map<String, Object> body=getErrorAttributes(request,
           isIncludeStackTrace(request, MediaType.ALL));
           HttpStatus status=getStatus(request);
           return new ResponseEntity<Map<String, Object>>(body, status);
           }
           ...
          }
          

          resolveErrorView方法(查找=error/“錯誤狀態碼”;的資源):

          如果不是異常請求,會執行resolveErrorView方法;該方法會先在默認或配置的靜態資源路徑下查找error/HttpStatus(錯誤狀態碼)的資源文件,如果沒有;使用默認的error頁面。

          public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
           ...
           @Override
           public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
           Map<String, Object> model) {
           //status:異常錯誤狀態碼
           ModelAndView modelAndView=resolve(String.valueOf(status), model);
           if (modelAndView==null && SERIES_VIEWS.containsKey(status.series())) {
           modelAndView=resolve(SERIES_VIEWS.get(status.series()), model);
           }
           return modelAndView;
           }
           private ModelAndView resolve(String viewName, Map<String, Object> model) {
           //視圖名稱,默認是error/+“status”錯誤狀態碼;比如:error/500、error/404
           String errorViewName="error/" + viewName;
           TemplateAvailabilityProvider provider=this.templateAvailabilityProviders
           .getProvider(errorViewName, this.applicationContext);
           if (provider !=null) {
           return new ModelAndView(errorViewName, model);
           }
           return resolveResource(errorViewName, model);
           }
           //在資源文件中查找error/500或error/404等頁面
           private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
           for (String location : this.resourceProperties.getStaticLocations()) {
           try {
           Resource resource=this.applicationContext.getResource(location);
           resource=resource.createRelative(viewName + ".html");
           if (resource.exists()) {
           return new ModelAndView(new HtmlResourceView(resource), model);
           }
           }
           catch (Exception ex) {
           }
           }
           return null;
           }
           ...
          }
          

          BasicErrorController根據Accept頭的內容,輸出不同格式的錯誤響應。比如針對瀏覽器的請求生成html頁面,針對其它請求生成json格式的返回。

          可以通過配置error/HttpStatus頁面實現自定義錯誤頁面。

          1. ErrorPageCustomizer類
          /**
           * {@link EmbeddedServletContainerCustomizer} that configures the container's error
           * pages.
           */
          private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
           private final ServerProperties properties;
           protected ErrorPageCustomizer(ServerProperties properties) {
           this.properties=properties;
           }
           @Override
           public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
           ErrorPage errorPage=new ErrorPage(this.properties.getServletPrefix()
           + this.properties.getError().getPath());
           errorPageRegistry.addErrorPages(errorPage);
           }
           @Override
           public int getOrder() {
           return 0;
           }
          }
          

          將錯誤頁面注冊到內嵌的tomcat的servlet容器中。

          1. PreserveErrorControllerTargetClassPostProcessor實現BeanFactoryPostProcessor接口,可以修改BEAN的配置信息

          ErrorAutoConfiguration內的兩個配置

          //2個config配置
          @Configuration
          static class DefaultErrorViewResolverConfiguration {
           private final ApplicationContext applicationContext;
           private final ResourceProperties resourceProperties;
           DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext,
           ResourceProperties resourceProperties) {
           this.applicationContext=applicationContext;
           this.resourceProperties=resourceProperties;
           }
           @Bean
           @ConditionalOnBean(DispatcherServlet.class)
           @ConditionalOnMissingBean
           public DefaultErrorViewResolver conventionErrorViewResolver() {
           return new DefaultErrorViewResolver(this.applicationContext,
           this.resourceProperties);
           }
          }
          @Configuration
          @ConditionalOnProperty(prefix="server.error.whitelabel", name="enabled", matchIfMissing=true)
          @Conditional(ErrorTemplateMissingCondition.class)
          protected static class WhitelabelErrorViewConfiguration {
           private final SpelView defaultErrorView=new SpelView(
           "<html><body><h1>Whitelabel Error Page</h1>"
           + "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>"
           + "<div id='created'>${timestamp}</div>"
           + "<div>There was an unexpected error (type=${error}, status=${status}).</div>"
           + "<div>${message}</div></body></html>");
           @Bean(name="error")
           @ConditionalOnMissingBean(name="error")
           public View defaultErrorView() {
           return this.defaultErrorView;
           }
           // If the user adds @EnableWebMvc then the bean name view resolver from
           // WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment.
           @Bean
           @ConditionalOnMissingBean(BeanNameViewResolver.class)
           public BeanNameViewResolver beanNameViewResolver() {
           BeanNameViewResolver resolver=new BeanNameViewResolver();
           resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
           return resolver;
           }
          }
          
          1. DefaultErrorViewResolverConfiguration:默認的error視圖解析配置;
          2. WhitelabelErrorViewConfiguration:默認設置了/error的頁面,和Whitelabel Error Page頁面響應內容。

          如果Spring MVC在處理業務的過程中拋出異常,會被 Servlet 容器捕捉到,Servlet 容器再將請求轉發給注冊好的異常處理映射 /error 做響應處理。

          springboot配置文件默認error相關配置

          springboot配置文件application.properties中關于error默認配置:

          server.error.include-stacktrace=never # When to include a "stacktrace" attribute.
          server.error.path=/error # Path of the error controller.
          server.error.whitelabel.enabled=true # Enable the default error page displayed in browsers in case of a server error.
          

          springboot 自定義異常處理

          通過跟蹤springboot對異常處理得源碼跟蹤,根據業務需要,可以細分前端響應的錯誤頁面,也可以統一使用/error頁面+錯誤提示信息進行處理。

          根據自己的需求自定義異常處理機制;具體可實施的操作如下:

          1. 可以通過配置error/HttpStatus(錯誤狀態碼)頁面實現自定義錯誤頁面【底層實現,詳見:BasicErrorController源碼】;
          2. 可以實現BasicErrorController,自定義普通請求的異常頁面響應信息和異步請求的響應信息,統一使用/error頁面進行錯誤響應提示;
          3. 自定義實現ErrorAttributes接口,覆蓋DefaultErrorAttributes實現,或是繼承DefaultErrorAttributes類,重寫里面的方法【TODO,不推薦】。

          1和2的方法可單獨使用,也可以結合使用。

          自定義異常頁面

          可以根據不同的錯誤狀態碼,在前端細分不同的響應界面給用戶進行提示;資源路徑必須是:靜態資源路徑下/error/HttpStats(比如:/error/404等)

          1. 自定義異常頁面
          <!DOCTYPE html>
          <html lang="en">
          <head>
           <meta charset="UTF-8"></meta>
           <title>404友情提示</title>
          </head>
          <body>
          <h1>訪問的資源未找到(404)</h1>
          </body>
          </html>
          

          404.html

          500.html等,這里只演示404。

          統一異常處理

          普通請求,前端使用error頁面+自定義錯誤響應信息;

          其他請求(異步),統一自定義錯誤響應信息,規范處理異步響應的錯誤判斷和處理。

          使用springMVC注解ControllerAdvice

          /**
           * 
           * @項目名稱:wyait-manage
           * @類名稱:GlobalExceptionHandler
           * @類描述:統一異常處理,包括【普通調用和ajax調用】
           * </br>ControllerAdvice來做controller內部的全局異常處理,但對于未進入controller前的異常,該處理方法是無法進行捕獲處理的,SpringBoot提供了ErrorController的處理類來處理所有的異常(TODO)。
           * </br>1.當普通調用時,跳轉到自定義的錯誤頁面;2.當ajax調用時,可返回約定的json數據對象,方便頁面統一處理。
           * @創建人:wyait
           * @創建時間:2018年5月22日 上午11:44:55 
           * @version:
           */
          @ControllerAdvice
          public class GlobalExceptionHandler {
           private static final Logger logger=LoggerFactory
           .getLogger(GlobalExceptionHandler.class);
           public static final String DEFAULT_ERROR_VIEW="error";
           /**
           * 
           * @描述:針對普通請求和ajax異步請求的異常進行處理
           * @創建人:wyait
           * @創建時間:2018年5月22日 下午4:48:58
           * @param req
           * @param e
           * @return
           * @throws Exception
           */
           @ExceptionHandler(value=Exception.class)
           @ResponseBody
           public ModelAndView errorHandler(HttpServletRequest request,
           HttpServletResponse response, Exception e) {
           logger.debug(getClass().getName() + ".errorHandler】統一異常處理:request="+request);
           ModelAndView mv=new ModelAndView();
           logger.info(getClass().getName() + ".errorHandler】統一異常處理:"+e.getMessage());
           //1 獲取錯誤狀態碼
           HttpStatus httpStatus=getStatus(request);
           logger.info(getClass().getName() + ".errorHandler】統一異常處理!錯誤狀態碼httpStatus:"+httpStatus);
           //2 返回錯誤提示
           ExceptionEnum ee=getMessage(httpStatus);
           //3 將錯誤信息放入mv中
           mv.addObject("type", ee.getType());
           mv.addObject("code", ee.getCode());
           mv.addObject("msg", ee.getMsg());
           if(!ShiroFilterUtils.isAjax(request)){
           //不是異步請求
           mv.setViewName(DEFAULT_ERROR_VIEW);
           logger.debug(getClass().getName() + ".errorHandler】統一異常處理:普通請求。");
           }
           logger.debug(getClass().getName() + ".errorHandler】統一異常處理響應結果:MV="+mv);
           return mv;
           }
           ...
          }
          

          運行測試:先走GlobalExceptionHandler(使用注解@ControllerAdvice)類里面的方法,而后又執行了BasicErrorController方法;被springboot自帶的BasicErrorController覆蓋。

          實現springboot的AbstractErrorController

          自定義實現AbstractErrorController,添加響應的錯誤提示信息。

          @RequestMapping(produces="text/html")
           public ModelAndView errorHtml(HttpServletRequest request,
           HttpServletResponse response) {
           ModelAndView mv=new ModelAndView(ERROR_PATH);
           /** model對象包含了異常信息 */
           Map<String, Object> model=getErrorAttributes(request,
           isIncludeStackTrace(request, MediaType.TEXT_HTML));
           // 1 獲取錯誤狀態碼(也可以根據異常對象返回對應的錯誤信息)
           HttpStatus httpStatus=getStatus(request);
           // 2 返回錯誤提示
           ExceptionEnum ee=getMessage(httpStatus);
           Result<String> result=new Result<String>(
           String.valueOf(ee.getType()), ee.getCode(), ee.getMsg());
           // 3 將錯誤信息放入mv中
           mv.addObject("result", result);
           logger.info("統一異常處理【" + getClass().getName()
           + ".errorHtml】統一異常處理!錯誤信息mv:" + mv);
           return mv;
           }
           @RequestMapping
           @ResponseBody
           //設置響應狀態碼為:200,結合前端約定的規范處理。也可不設置狀態碼,前端ajax調用使用error函數進行控制處理
           @ResponseStatus(value=HttpStatus.OK)
           public Result<String> error(HttpServletRequest request, Exception e) {
           /** model對象包含了異常信息 */
           Map<String, Object> model=getErrorAttributes(request,
           isIncludeStackTrace(request, MediaType.TEXT_HTML));
           // 1 獲取錯誤狀態碼(也可以根據異常對象返回對應的錯誤信息)
           HttpStatus httpStatus=getStatus(request);
           // 2 返回錯誤提示
           ExceptionEnum ee=getMessage(httpStatus);
           Result<String> result=new Result<String>(
           String.valueOf(ee.getType()), ee.getCode(), ee.getMsg());
           // 3 將錯誤信息返回
          // ResponseEntity
           logger.info("統一異常處理【" + getClass().getName()
           + ".error】統一異常處理!錯誤信息result:" + result);
           return result;
           }
          

          針對異步請求,統一指定響應狀態碼:200;也可以不指定,前端在處理異步請求的時候,可以通過ajax的error函數進行控制。

          這里是繼承的AbstractErrorController類,自定義實現統一異常處理,也可以直接實現ErrorController接口。

          前端ajax異步統一處理:

          通過約定,前端ajax異步請求,進行統一的錯誤處理。

          /**
           * 針對不同的錯誤可結合業務自定義處理方式
           * @param result
           * @returns {Boolean}
           */
          function isError(result){
           var flag=true;
           if(result && result.status){
           flag=false;
           if(result.status=='-1' || result.status=='-101' || result.status=='400' || result.status=='404' || result.status=='500'){
           layer.alert(result.data);
           }else if(result.status=='403'){
           layer.alert(result.data,function(){
           //跳轉到未授權界面
           window.location.href="/403";
           });
           }
           }
           return flag;//返回true
          }
          

          使用方式:

           ...
           success:function(data){
           //異常過濾處理
           if(isError(data)){
           alert(data);
           }
           },
           ...
          

          error.html頁面:

          <!DOCTYPE html>
          <html lang="en" xmlns:th="http://www.thymeleaf.org">
          <head th:include="layout :: htmlhead" th:with="title='wyait后臺管理'">
           <meta charset="UTF-8"></meta>
           <title th:text="${result.status}"></title>
          </head>
          <body>
          <h1>出錯了</h1>
          <p><span th:text="${result.message}"></span>(<span th:text="${result.data}"></span>)</p>
          </body>
          </html>
          

          測試效果

          普通請求:

          異步請求:

          線上get請求亂碼

          問題描述

          前臺通過html頁面,發送請求到后臺查詢數據,在日志中打印的sql語句顯示傳入的參數亂碼:

           SELECT ... 
          [2018-05-11 09:15:00.582][http-bio-8280-exec-2][DEBUG][org.apache.ibatis.logging.jdbc.BaseJdbcLogger][159]:==> Parameters: 1(Integer), ???è′o(String)
          [2018-05-11 09:15:00.585][http-bio-8280-exec-2][DEBUG][org.apache.ibatis.logging.jdbc.BaseJdbcLogger][159]:<==Total: 1
          ...
          

          本地windows開發環境測試沒有亂碼問題;

          請求信息

          前端頁面發送get請求,瀏覽器默認對get請求路徑進行URL編碼處理。

          后臺Controller打印的日志

          分頁查詢用戶列表!搜索條件:userSearch:UserSearchDTO{page=1, limit=10, uname='??????', umobile='', insertTimeStart='', insertTimeEnd=''},page:1,每頁記錄數量limit:10,請求編碼:UTF-8
          

          Controller層在接收到這個uname參數時,已經是亂碼,ISO-8859-1解碼后的結果。

          請求參數編碼流程

          1. 前端頁面發送get請求,瀏覽器默認在中文的UTF-8后加上上%得到URL編碼,比如:%e8%b4%b9%e7...;
          2. get請求到tomcat應用服務器后,會以默認的ISO-8859-1進行解碼;
          3. 在controller中,接收到的是經過URL編碼和iso-8859-1解碼后的參數值。

          具體編碼細節:TODO

          解決方案

          項目編碼配置【可以不配置】

          開發前,默認必須統一編碼環境;正常都是設置為utf-8。

          spring boot 與spring mvc不同,在web應用中,spring boot默認的編碼格式為UTF-8,而spring mvc的默認編碼格式為iso-8859-1。

          spring boot項目中如果沒有特殊需求,該編碼不需要修改。如果要強制其他編碼格式,spring boot提供了設置方式:

          1. 通過application.properties配置文件設置:
          # 默認utf-8配置
          spring.http.encoding.force=true
          spring.http.encoding.charset=UTF-8
          spring.http.encoding.enabled=true
          server.tomcat.uri-encoding=UTF-8
          

          此時攔截器中返回的中文已經不亂碼了,但是controller中返回的數據可能會依舊亂碼。

          1. 參考spring MVC的方式,自定義實現WebMvcConfigurerAdapter類,處理響應數據亂碼問題:
          @Bean
          public HttpMessageConverter<String> responseBodyConverter() {
           StringHttpMessageConverter converter=new StringHttpMessageConverter(Charset.forName("UTF-8"));
           return converter;
          }
          @Override
          public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
           super.configureMessageConverters(converters);
           converters.add(responseBodyConverter());
          }
          

          也可以在controller方法@RequestMapping上添加:

          produces="text/plain;charset=UTF-8"
          

          這種方法的弊端是限定了數據類型。

          亂碼解決方案

          表單采用get方式提交,中文亂碼解決方案:

          1. 改為post請求;
          2. 手動編解碼:
          param=new String(param.getBytes("iso8859-1"), "utf-8");
          
          1. 修改tomcat配置server.xml文件:
          2. 找到如下代碼:
          <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
          

          在這里添加一個屬性:URIEncoding,將該屬性值設置為UTF-8,即可讓Tomcat(默認ISO-8859-1編碼)以UTF-8的編碼處理get請求。

          修改完成后:

          <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" />
          
          1. 發送get請求前,瀏覽器中兩次URL編碼:
          2. 兩次編碼兩次解碼的過程為:
          3. ==UTF-8編碼->UTF-8(iso-8859-1)編碼->iso-8859-1解碼->UTF-8解碼,編碼和解碼的過程是對稱的,所以不會出現亂碼。==
           //js代碼
           param=encodeURI(param);
           // alert("第一次URL編碼:" + param);
           param=encodeURI(param);
           // alert("第二次URL編碼:" + param);
          

          后臺代碼:

          //兩次解碼
          URLDecoder.decode(URLDecoder.decode(param,"utf-8"),"utf-8");
          

          總結

          以上四種解決方案,可結合具體情況進行使用。

          no session異常

          異常日志1:

          [2018-05-21 18:00:51.574][http-nio-8280-exec-6][DEBUG][org.apache.shiro.web.servlet.SimpleCookie][389]:Found 'SHRIOSESSIONID' cookie value [fc6b7b64-6c59-4f82-853b-e2ca20135b99]
          [2018-05-21 18:00:51.575][http-nio-8280-exec-6][DEBUG][org.apache.shiro.mgt.DefaultSecurityManager][447]:Resolved SubjectContext context session is invalid. Ignoring and creating an anonymous (session-less) Subject instance.
          org.apache.shiro.session.UnknownSessionException: There is no session with id [fc6b7b64-6c59-4f82-853b-e2ca20135b99]
           at org.apache.shiro.session.mgt.eis.AbstractSessionDAO.readSession(AbstractSessionDAO.java:170) ~[shiro-all-1.3.1.jar:1.3.1]
          

          異常日志2【偶爾出現】:

          Caused by: javax.crypto.BadPaddingException: Given final block not properly padded
           at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:811) ~[sunjce_provider.jar:1.7.0_85]
          

          UnknownSessionException

          UnknownSessionException: There is no session with id [...]

          原因

          結合項目配置,分析問題原因:

          1,用戶退出后,瀏覽器中的SHIROSESSIONID依然存在;

          2,再次發送請求時,攜帶SHIROSESSIONID,會在shiro的DefaultWebSecurityManager.getSessionKey(context)中,逐層跟蹤對應在sessionManager中session值,沒有的話,最終在AbstractSessionDAO.readSession(sessionID)中拋出異常。

          解決方案

          1. 在程序中退出的地方,清除cookie:
          //刪除cookie
          Cookie co=new Cookie("username", "");
          co.setMaxAge(0);// 設置立即過期
          co.setPath("/");// 根目錄,整個網站有效
          servletResponse.addCookie(co);
          
          1. 設置SimpleCookie的過期時間,和session、ehcache緩存時間保持一致;
          @Bean
          public SimpleCookie sessionIdCookie() {
           //DefaultSecurityManager
           SimpleCookie simpleCookie=new SimpleCookie();
           //如果在Cookie中設置了"HttpOnly"屬性,那么通過程序(JS腳本、Applet等)將無法讀取到Cookie信息,這樣能防止XSS×××。
           simpleCookie.setHttpOnly(true);
           simpleCookie.setName("SHRIOSESSIONID");
           simpleCookie.setMaxAge(86400000*3);
           return simpleCookie;
          }
          
          1. 手動實現shiro的logout方法,清除瀏覽器cookie;
          2. 重寫AbstractSessionDAO.readSession方法,如果session為null,清空瀏覽器cookie;
          3. 不做處理;實際項目運行中,不影響功能執行。

          源碼

          源碼已集成到項目中:

          github源碼: https://github.com/wyait/manage.git

          碼云:https://gitee.com/wyait/manage.git

          github對應項目源碼目錄:wyait-manage-1.2.0

          碼云對應項目源碼目錄:wyait-manage-1.2.0

          轉載:https://blog.51cto.com/wyait/2125708

          作者:wyait



          .基本信息。

          [查看開源項目](https://gitee.com/yadong.zhang/DBlog)

          | 工具 | 版本或描述 |

          | ----- | -------------------- |

          | OS | Windows 10 |

          | JDK | 1.8+ |

          | IDE | eclipse|

          | Maven | 3.3.1 |

          | MySQL | 5.6.4 |

          #### 模塊劃分

          | 模塊 | 釋義 |

          | ---------- | ----------------------- |

          | shiro-core | 核心業務類模塊,提供基本的數據操作、工具處理等 |

          | shiro-admin | 后臺管理模塊 |


          使用說明

          1. 使用IDE導入本項目

          2. 新建數據庫`CREATE DATABASE shiro;`

          3. 導入數據庫`docs/db/shiro.sql`

          4. 修改(`resources/application.yml`)配置文件

          1. 數據庫鏈接屬性(可搜索`datasource`或定位到L.19)

          2. redis配置(可搜索`redis`或定位到L.69)

          5. 運行項目(三種方式)

          1. 項目根目錄下執行`mvn -X clean package -Dmaven.test.skip=true`編譯打包,然后執行`java -jar shiro-admin/target/shiro-admin.jar`

          2. 項目根目錄下執行`mvn springboot:run`

          3. 直接運行`ShiroAdminApplication.java`

          6. 瀏覽器訪問`http://127.0.0.1:8080`

          **用戶密碼**

          _超級管理員_: 賬號:root 密碼:123456

          _普通管理員_: 賬號:admin 密碼:123456

          **Druid監控**

          _鏈接_: `http://127.0.0.1:8080/druid/index.html`

          用戶名:zyd-druid 密碼:zyd-druid

          2.安裝數據表:shiro.sql;

          1. sys_resources


          '

          2.sys_role


          .

          3.sys_role_resources



          .

          4.sys_user


          .

          5.sys_user_role


          .

          6.eclipse導入成功后頁面


          /

          7.類對象,mapper對象,xml關系如圖


          8.打開application.yml,注意。redis,mysql,啟動服務。注意端口;




          ……


          ……


          。


          搭建完成?。。。?/p>


          主站蜘蛛池模板: 国模私拍一区二区三区| 国产一区中文字幕在线观看| 天天躁日日躁狠狠躁一区| 国产精品区AV一区二区| 久久国产精品无码一区二区三区 | 色欲AV无码一区二区三区| 无码少妇一区二区| 久久综合精品国产一区二区三区| 日本一区二三区好的精华液| 91在线一区二区三区| 国产激情з∠视频一区二区| 精品亚洲一区二区三区在线播放| 日韩精品一区在线| 日韩a无吗一区二区三区| 国产SUV精品一区二区88| 亚洲熟妇AV一区二区三区浪潮 | 国产精品日本一区二区在线播放| 久99精品视频在线观看婷亚洲片国产一区一级在线 | 福利一区二区在线| 精品视频一区二区观看| 立川理惠在线播放一区 | 日日摸夜夜添一区| 国产91大片精品一区在线观看| 亚洲一区二区三区高清| 蜜臀Av午夜一区二区三区| 91大神在线精品视频一区| 色噜噜狠狠一区二区三区| 无码精品一区二区三区| 人妻夜夜爽天天爽一区| 在线观看中文字幕一区| 国产成人av一区二区三区在线观看| 国内精品一区二区三区最新| 精品国产亚洲第一区二区三区| 精品乱码一区二区三区在线| 亚洲AⅤ视频一区二区三区| 日本免费电影一区二区| 精品国产AV一区二区三区| 日韩高清一区二区| 久久婷婷色综合一区二区| 亚洲AV无码一区二区乱子伦| 中文字幕精品一区二区|