10、 Spring Boot Shiro 權限管理

使用Shiro以前用在spring MVC中,是經過XML文件進行配置。 javascript

將Shiro應用到Spring Boot中,本地已經完成了SpringBoot使用Shiro的實例,將配置方法共享一下。css

先簡單介紹一下Shiro,對於沒有用過Shiro的朋友,也算是作個簡介吧。 
Shiro是Apache下的一個開源項目,咱們稱之爲Apache Shiro。它是一個很易用與Java項目的的安全框架,提供了認證、受權、加密、會話管理,與 Spring Security 同樣都是作一個權限的安全框架,可是與Spring Security 相比,在於 Shiro 使用了比較簡單易懂易於使用的受權方式。html

Apache Shiro 的三大核心組件 
這裏寫圖片描述 
- Subject 當前用戶操做 
- SecurityManager 用於管理全部的Subject 
- Realms 用於進行權限信息的驗證,也是咱們須要本身實現的。java

咱們須要實現Realms的Authentication 和 Authorization。其中 Authentication 是用來驗證用戶身份,Authorization 是受權訪問控制,用於對用戶進行的操做受權,證實該用戶是否容許進行當前操做,如訪問某個連接,某個資源文件等。jquery

Apache Shiro 核心經過 Filter 來實現,就好像SpringMvc 經過DispachServlet 來主控制同樣。 
既然是使用 Filter 通常也就能猜到,是經過URL規則來進行過濾和權限校驗,因此咱們須要定義一系列關於URL的規則和訪問權限。web

另外咱們能夠經過Shiro 提供的會話管理來獲取Session中的信息。Shiro 也提供了緩存支持,使用 CacheManager 來管理。spring

官方網站:http://shiro.apache.org/ 
完整架構圖: 
這裏寫圖片描述sql

下面咱們經過代碼實戰來看下Spring Boot 中應用Shiro: 
一、建立數據庫表 
這裏寫圖片描述數據庫

表(t_permission) id permissionname role_id ------ -------------- --------- 1 add 2 2 del 1 3 update 2 4 query 3 5 user:query 1 6 user:edit 2 表(t_role) id rolename ------ ---------- 1 admin 2 manager 3 normal 表(t_user) id username password ------ -------- ---------- 1 tom 123456 2 jack 123456 3 rose 123456 表(t_user_role) user_id role_id ------- --------- 1 1 1 3 2 2 2 3 3 3

上面3張表是我測試別的用的,能夠忽略。apache

下面是,數據庫腳本和測試數據。



/* SQLyog Ultimate v10.00 Beta1 MySQL - 5.5.28 : Database - test ********************************************************************* */ /*!40101 SET NAMES utf8 */; /*!40101 SET SQL_MODE=''*/; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; CREATE DATABASE /*!32312 IF NOT EXISTS*/`test` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `test`; /*Table structure for table `t_permission` */ DROP TABLE IF EXISTS `t_permission`; CREATE TABLE `t_permission` ( `id` int(11) NOT NULL AUTO_INCREMENT, `permissionname` varchar(32) DEFAULT NULL, `role_id` int(11) DEFAULT NULL, KEY `id` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; /*Data for the table `t_permission` */ insert into `t_permission`(`id`,`permissionname`,`role_id`) values (1,'add',2),(2,'del',1),(3,'update',2),(4,'query',3),(5,'user:query',1),(6,'user:edit',2); /*Table structure for table `t_role` */ DROP TABLE IF EXISTS `t_role`; CREATE TABLE `t_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `rolename` varchar(32) DEFAULT NULL, KEY `id` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; /*Data for the table `t_role` */ insert into `t_role`(`id`,`rolename`) values (1,'admin'),(2,'manager'),(3,'normal'); /*Table structure for table `t_user` */ DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(32) DEFAULT NULL, `password` varchar(32) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; /*Data for the table `t_user` */ insert into `t_user`(`id`,`username`,`password`) values (1,'tom','123456'),(2,'jack','123456'),(3,'rose','123456'); /*Table structure for table `t_user_role` */ DROP TABLE IF EXISTS `t_user_role`; CREATE TABLE `t_user_role` ( `user_id` int(11) DEFAULT NULL, `role_id` int(11) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*Data for the table `t_user_role` */ insert into `t_user_role`(`user_id`,`role_id`) values (1,1),(1,3),(2,2),(2,3),(3,3); /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

二、建立對應實體類 
User.java



package org.springboot.sample.entity; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.Table; import javax.persistence.Transient; import org.hibernate.validator.constraints.NotEmpty; /** * 用戶 * */ @Entity @Table(name = "t_user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @NotEmpty(message = "用戶名不能爲空") private String username; @NotEmpty(message = "密碼不能爲空") private String password; @ManyToMany(fetch=FetchType.EAGER) @JoinTable(name = "t_user_role", joinColumns = { @JoinColumn(name = "user_id") }, inverseJoinColumns = { @JoinColumn(name = "role_id") }) private List<Role> roleList;// 一個用戶具備多個角色 public User() { super(); } public User(String username, String password) { super(); this.username = username; this.password = password; } // 省略 get set 方法 @Transient public Set<String> getRolesName() { List<Role> roles = getRoleList(); Set<String> set = new HashSet<String>(); for (Role role : roles) { set.add(role.getRolename()); } return set; } }

Role.java

package org.springboot.sample.entity; import java.util.ArrayList; import java.util.List; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Transient; /** * 角色(管理員,普通用戶等) * */ @Entity @Table(name = "t_role") public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String rolename; @OneToMany(mappedBy = "role", fetch=FetchType.EAGER) private List<Permission> permissionList;// 一個角色對應多個權限 @ManyToMany @JoinTable(name = "t_user_role", joinColumns = { @JoinColumn(name = "role_id") }, inverseJoinColumns = { @JoinColumn(name = "user_id") }) private List<User> userList;// 一個角色對應多個用戶 // 省略 get set 方法 @Transient public List<String> getPermissionsName() { List<String> list = new ArrayList<String>(); List<Permission> perlist = getPermissionList(); for (Permission per : perlist) { list.add(per.getPermissionname()); } return list; } }

Permission.java

package org.springboot.sample.entity; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; /** * 權限(增刪改查等) * */ @Entity @Table(name = "t_permission") public class Permission { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String permissionname; @ManyToOne @JoinColumn(name = "role_id") private Role role;// 一個權限對應一個角色 // 省略 get set } 

三、Shiro 配置,至關於SpringMVC 中的XML配置 
ShiroConfiguration.java

package org.springboot.sample.config; import java.util.LinkedHashMap; import java.util.Map; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springboot.sample.dao.IScoreDao; import org.springboot.sample.security.MyShiroRealm; import org.springboot.sample.service.StudentService; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.boot.context.embedded.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.filter.DelegatingFilterProxy; /** * Shiro 配置 * */ @Configuration public class ShiroConfiguration { private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class); @Bean public EhCacheManager getEhCacheManager() { EhCacheManager em = new EhCacheManager(); em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml"); return em; } @Bean(name = "myShiroRealm") public MyShiroRealm myShiroRealm(EhCacheManager cacheManager) { MyShiroRealm realm = new MyShiroRealm(); realm.setCacheManager(cacheManager); return realm; } /** * 註冊DelegatingFilterProxy(Shiro) * 集成Shiro有2種方法: * 1. 按這個方法本身組裝一個FilterRegistrationBean(這種方法更爲靈活,能夠本身定義UrlPattern, * 在項目使用中你可能會由於一些很但疼的問題最後採用它, 想使用它你可能須要看官網或者已經很瞭解Shiro的處理原理了) * 2. 直接使用ShiroFilterFactoryBean(這種方法比較簡單,其內部對ShiroFilter作了組裝工做,沒法本身定義UrlPattern, * 默認攔截 /*) * * @param dispatcherServlet * @return */ // @Bean // public FilterRegistrationBean filterRegistrationBean() { // FilterRegistrationBean filterRegistration = new FilterRegistrationBean(); // filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter")); // // 該值缺省爲false,表示生命週期由SpringApplicationContext管理,設置爲true則表示由ServletContainer管理 // filterRegistration.addInitParameter("targetFilterLifecycle", "true"); // filterRegistration.setEnabled(true); // filterRegistration.addUrlPatterns("/*");// 能夠本身靈活的定義不少,避免一些根本不須要被Shiro處理的請求被包含進來 // return filterRegistration; // } @Bean(name = "lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator(); daap.setProxyTargetClass(true); return daap; } @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(MyShiroRealm myShiroRealm) { DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager(); dwsm.setRealm(myShiroRealm); // <!-- 用戶受權/認證信息Cache, 採用EhCache 緩存 --> dwsm.setCacheManager(getEhCacheManager()); return dwsm; } @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor(); aasa.setSecurityManager(securityManager); return aasa; } /** * 加載shiroFilter權限控制規則(從數據庫讀取而後配置) * */ private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean, StudentService stuService, IScoreDao scoreDao){ /////////////////////// 下面這些規則配置最好配置到配置文件中 /////////////////////// Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // authc:該過濾器下的頁面必須驗證後才能訪問,它是Shiro內置的一個攔截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter filterChainDefinitionMap.put("/user", "authc");// 這裏爲了測試,只限制/user,實際開發中請修改成具體攔截的請求規則 // anon:它對應的過濾器裏面是空的,什麼都沒作 logger.info("##################從數據庫讀取權限規則,加載到shiroFilter中##################"); filterChainDefinitionMap.put("/user/edit/**", "authc,perms[user:edit]");// 這裏爲了測試,固定寫死的值,也能夠從數據庫或其餘配置中讀取 filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/**", "anon");//anon 能夠理解爲不攔截 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); } /** * ShiroFilter<br/> * 注意這裏參數中的 StudentService 和 IScoreDao 只是一個例子,由於咱們在這裏能夠用這樣的方式獲取到相關訪問數據庫的對象, * 而後讀取數據庫相關配置,配置到 shiroFilterFactoryBean 的訪問規則中。實際項目中,請使用本身的Service來處理業務邏輯。 * * @param myShiroRealm * @param stuService * @param scoreDao * @return */ @Bean(name = "shiroFilter") public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager, StudentService stuService, IScoreDao scoreDao) { ShiroFilterFactoryBean shiroFilterFactoryBean = new MShiroFilterFactoryBean(); // 必須設置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 若是不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面 shiroFilterFactoryBean.setLoginUrl("/login"); // 登陸成功後要跳轉的鏈接 shiroFilterFactoryBean.setSuccessUrl("/user"); shiroFilterFactoryBean.setUnauthorizedUrl("/403"); loadShiroFilterChain(shiroFilterFactoryBean, stuService, scoreDao); return shiroFilterFactoryBean; } } 
/** * 繼承 ShiroFilterFactoryBean 處理攔截資源文件問題。 * */ public class MShiroFilterFactoryBean extends ShiroFilterFactoryBean { // 對ShiroFilter來講,須要直接忽略的請求 private Set<String> ignoreExt; public MShiroFilterFactoryBean() { super(); ignoreExt = new HashSet<>(); ignoreExt.add(".jpg"); ignoreExt.add(".png"); ignoreExt.add(".gif"); ignoreExt.add(".bmp"); ignoreExt.add(".js"); ignoreExt.add(".css"); } @Override protected AbstractShiroFilter createInstance() throws Exception { SecurityManager securityManager = getSecurityManager(); if (securityManager == null) { String msg = "SecurityManager property must be set."; throw new BeanInitializationException(msg); } if (!(securityManager instanceof WebSecurityManager)) { String msg = "The security manager does not implement the WebSecurityManager interface."; throw new BeanInitializationException(msg); } FilterChainManager manager = createFilterChainManager(); PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver(); chainResolver.setFilterChainManager(manager); return new MSpringShiroFilter((WebSecurityManager) securityManager, chainResolver); } private final class MSpringShiroFilter extends AbstractShiroFilter { protected MSpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) { super(); if (webSecurityManager == null) { throw new IllegalArgumentException("WebSecurityManager property cannot be null."); } setSecurityManager(webSecurityManager); if (resolver != null) { setFilterChainResolver(resolver); } } @Override protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws ServletException, IOException { HttpServletRequest request = (HttpServletRequest)servletRequest; String str = request.getRequestURI().toLowerCase(); // 由於ShiroFilter 攔截全部請求(在上面咱們配置了urlPattern 爲 * ,固然你也能夠在那裏精確的添加要處理的路徑,這樣就不須要這個類了),而在每次請求裏面都作了session的讀取和更新訪問時間等操做,這樣在集羣部署session共享的狀況下,數量級的加大了處理量負載。 // 因此咱們這裏將一些能忽略的請求忽略掉。 // 固然若是你的集羣系統使用了動靜分離處理,靜態資料的請求不會到Filter這個層面,即可以忽略。 boolean flag = true; int idx = 0; if(( idx = str.indexOf(".")) > 0){ str = str.substring(idx); if(ignoreExt.contains(str.toLowerCase())) flag = false; } if(flag){ super.doFilterInternal(servletRequest, servletResponse, chain); }else{ chain.doFilter(servletRequest, servletResponse); } } } }

其中的 ehcache-shiro.xml 在 src/main/resources 下面,內容爲:



<?xml version="1.0" encoding="UTF-8"?> <ehcache updateCheck="false" name="shiroCache"> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="false" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" /> </ehcache>

四、繼承 AuthorizingRealm 實現認證和受權2個方法 
MyShiroRealm.java

package org.springboot.sample.security; import java.util.List; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springboot.sample.dao.IUserDao; import org.springboot.sample.entity.Role; import org.springboot.sample.entity.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * MyShiroRealm * */ public class MyShiroRealm extends AuthorizingRealm{ private static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class); @Autowired private IUserDao userDao; /** * 權限認證,爲當前登陸的Subject授予角色和權限 * @see 經測試:本例中該方法的調用時機爲需受權資源被訪問時 * @see 經測試:而且每次訪問需受權資源時都會執行該方法中的邏輯,這代表本例中默認並未啓用AuthorizationCache * @see 經測試:若是連續訪問同一個URL(好比刷新),該方法不會被重複調用,Shiro有一個時間間隔(也就是cache時間,在ehcache-shiro.xml中配置),超過這個時間間隔再刷新頁面,該方法會被執行 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { logger.info("##################執行Shiro權限認證##################"); //獲取當前登陸輸入的用戶名,等價於(String) principalCollection.fromRealm(getName()).iterator().next(); String loginName = (String)super.getAvailablePrincipal(principalCollection); //到數據庫查是否有此對象 User user=userDao.findByName(loginName);// 實際項目中,這裏能夠根據實際狀況作緩存,若是不作,Shiro本身也是有時間間隔機制,2分鐘內不會重複執行該方法 if(user!=null){ //權限信息對象info,用來存放查出的用戶的全部的角色(role)及權限(permission) SimpleAuthorizationInfo info=new SimpleAuthorizationInfo(); //用戶的角色集合 info.setRoles(user.getRolesName()); //用戶的角色對應的全部權限,若是隻使用角色定義訪問權限,下面的四行能夠不要 List<Role> roleList=user.getRoleList(); for (Role role : roleList) { info.addStringPermissions(role.getPermissionsName()); } // 或者按下面這樣添加 //添加一個角色,不是配置意義上的添加,而是證實該用戶擁有admin角色 // simpleAuthorInfo.addRole("admin"); //添加權限 // simpleAuthorInfo.addStringPermission("admin:manage"); // logger.info("已爲用戶[mike]賦予了[admin]角色和[admin:manage]權限"); return info; } // 返回null的話,就會致使任何用戶訪問被攔截的請求時,都會自動跳轉到unauthorizedUrl指定的地址 return null; } /** * 登陸認證 */ @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken authenticationToken) throws AuthenticationException { //UsernamePasswordToken對象用來存放提交的登陸信息 UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken; logger.info("驗證當前Subject時獲取到token爲:" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE)); //查出是否有此用戶 User user=userDao.findByName(token.getUsername()); if(user!=null){ // 若存在,將此用戶存放到登陸認證info中,無需本身作密碼對比,Shiro會爲咱們進行密碼對比校驗 return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName()); } return null; } }

注意:其中 userDao.findByName 這個代碼就不貼上了,也沒啥可貼的,根據姓名查詢一個對象而已。

五、編寫測試的 Controller 和測試 jsp 頁面 
ShiroController.java

package org.springboot.sample.controller; import java.util.Map; import javax.validation.Valid; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.ExcessiveAttemptsException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springboot.sample.dao.IUserDao; import org.springboot.sample.entity.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.mvc.support.RedirectAttributes; /** * Shiro測試Controller * */ @Controller public class ShiroController { private static final Logger logger = LoggerFactory.getLogger(ShiroController.class); @Autowired private IUserDao userDao; @RequestMapping(value="/login",method=RequestMethod.GET) public String loginForm(Model model){ model.addAttribute("user", new User()); return "login"; } @RequestMapping(value="/login",method=RequestMethod.POST) public String login(@Valid User user,BindingResult bindingResult,RedirectAttributes redirectAttributes){ if(bindingResult.hasErrors()){ return "login"; } String username = user.getUsername(); UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword()); //獲取當前的Subject Subject currentUser = SecurityUtils.getSubject(); try { //在調用了login方法後,SecurityManager會收到AuthenticationToken,並將其發送給已配置的Realm執行必須的認證檢查 //每一個Realm都能在必要時對提交的AuthenticationTokens做出反應 //因此這一步在調用login(token)方法時,它會走到MyRealm.doGetAuthenticationInfo()方法中,具體驗證方式詳見此方法 logger.info("對用戶[" + username + "]進行登陸驗證..驗證開始"); currentUser.login(token); logger.info("對用戶[" + username + "]進行登陸驗證..驗證經過"); }catch(UnknownAccountException uae){ logger.info("對用戶[" + username + "]進行登陸驗證..驗證未經過,未知帳戶"); redirectAttributes.addFlashAttribute("message", "未知帳戶"); }catch(IncorrectCredentialsException ice){ logger.info("對用戶[" + username + "]進行登陸驗證..驗證未經過,錯誤的憑證"); redirectAttributes.addFlashAttribute("message", "密碼不正確"); }catch(LockedAccountException lae){ logger.info("對用戶[" + username + "]進行登陸驗證..驗證未經過,帳戶已鎖定"); redirectAttributes.addFlashAttribute("message", "帳戶已鎖定"); }catch(ExcessiveAttemptsException eae){ logger.info("對用戶[" + username + "]進行登陸驗證..驗證未經過,錯誤次數過多"); redirectAttributes.addFlashAttribute("message", "用戶名或密碼錯誤次數過多"); }catch(AuthenticationException ae){ //經過處理Shiro的運行時AuthenticationException就能夠控制用戶登陸失敗或密碼錯誤時的情景 logger.info("對用戶[" + username + "]進行登陸驗證..驗證未經過,堆棧軌跡以下"); ae.printStackTrace(); redirectAttributes.addFlashAttribute("message", "用戶名或密碼不正確"); } //驗證是否登陸成功 if(currentUser.isAuthenticated()){ logger.info("用戶[" + username + "]登陸認證經過(這裏能夠進行一些認證經過後的一些系統參數初始化操做)"); return "redirect:/user"; }else{ token.clear(); return "redirect:/login"; } } @RequestMapping(value="/logout",method=RequestMethod.GET) public String logout(RedirectAttributes redirectAttributes ){ //使用權限管理工具進行用戶的退出,跳出登陸,給出提示信息 SecurityUtils.getSubject().logout(); redirectAttributes.addFlashAttribute("message", "您已安全退出"); return "redirect:/login"; } @RequestMapping("/403") public String unauthorizedRole(){ logger.info("------沒有權限-------"); return "403"; } @RequestMapping("/user") public String getUserList(Map<String, Object> model){ model.put("userList", userDao.getList()); return "user"; } @RequestMapping("/user/edit/{userid}") public String getUserList(@PathVariable int userid){ logger.info("------進入用戶信息修改-------"); return "user_edit"; } }

login.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>Login</title> </head> <body> <h1>登陸頁面----${message }</h1> <img alt="" src="${pageContext.request.contextPath }/pic.jpg"> <form:form action="${pageContext.request.contextPath }/login" commandName="user" method="post"> 用戶名:<form:input path="username" /> <form:errors path="username" cssClass="error" /> <br /> 密碼:<form:password path="password" /> <form:errors path="password" cssClass="error" /> <br /> <form:button name="button">提交</form:button> </form:form> </body> </html>

user.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>用戶列表</title> </head> <body> <h1>${message }</h1> <h1>用戶列表--<a href="${pageContext.request.contextPath }/logout">退出登陸</a> </h1> <h2>權限列表</h2> <shiro:authenticated>用戶已經登陸顯示此內容<br/></shiro:authenticated><br/> <shiro:hasRole name="manager">manager角色登陸顯示此內容<br/></shiro:hasRole> <shiro:hasRole name="admin">admin角色登陸顯示此內容<br/></shiro:hasRole> <shiro:hasRole name="normal">normal角色登陸顯示此內容<br/></shiro:hasRole><br/> <shiro:hasAnyRoles name="manager,admin">manager or admin 角色用戶登陸顯示此內容<br/></shiro:hasAnyRoles><br/> <shiro:principal/>-顯示當前登陸用戶名<br/><br/> <shiro:hasPermission name="add">add權限用戶顯示此內容<br/></shiro:hasPermission> <shiro:hasPermission name="user:query">user:query權限用戶顯示此內容<br/></shiro:hasPermission> <shiro:lacksPermission name="user:query">不具備user:query權限的用戶顯示此內容 <br/></shiro:lacksPermission> <br/>全部用戶列表:<br/> <ul> <c:forEach items="${userList }" var="user"> <li>用戶名:${user.username }----密碼:${user.password }----<a href="${pageContext.request.contextPath }/user/edit/${user.id}">修改用戶(測試根據不一樣用戶可訪問權限不一樣,本例tom無權限,jack有權限)</a></li> </c:forEach> </ul> <img alt="" src="${pageContext.request.contextPath }/pic.jpg"> <script type="text/javascript" src="${pageContext.request.contextPath }/webjarslocator/jquery/jquery.js"></script> </body> </html>

user.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>用戶列表</title> </head> <body> <h1>${message }</h1> <h1>用戶列表--<a href="${pageContext.request.contextPath }/logout">退出登陸</a> </h1> <h2>權限列表</h2> <shiro:authenticated>用戶已經登陸顯示此內容<br/></shiro:authenticated><br/> <shiro:hasRole name="manager">manager角色登陸顯示此內容<br/></shiro:hasRole> <shiro:hasRole name="admin">admin角色登陸顯示此內容<br/></shiro:hasRole> <shiro:hasRole name="normal">normal角色登陸顯示此內容<br/></shiro:hasRole><br/> <shiro:hasAnyRoles name="manager,admin">manager or admin 角色用戶登陸顯示此內容<br/></shiro:hasAnyRoles><br/> <shiro:principal/>-顯示當前登陸用戶名<br/><br/> <shiro:hasPermission name="add">add權限用戶顯示此內容<br/></shiro:hasPermission> <shiro:hasPermission name="user:query">user:query權限用戶顯示此內容<br/></shiro:hasPermission> <shiro:lacksPermission name="user:query">不具備user:query權限的用戶顯示此內容 <br/></shiro:lacksPermission> <br/>全部用戶列表:<br/> <ul> <c:forEach items="${userList }" var="user"> <li>用戶名:${user.username }----密碼:${user.password }----<a href="${pageContext.request.contextPath }/user/edit/${user.id}">修改用戶(測試根據不一樣用戶可訪問權限不一樣,本例tom無權限,jack有權限)</a></li> </c:forEach> </ul> <img alt="" src="${pageContext.request.contextPath }/pic.jpg"> <script type="text/javascript" src="${pageContext.request.contextPath }/webjarslocator/jquery/jquery.js"></script> </body> </html>

403.jsp



<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>權限錯誤</title> </head> <body> <h1>對不起,您沒有權限請求此鏈接!</h1> <img alt="" src="${pageContext.request.contextPath }/pic.jpg"> </body> </html>

其中的pic.jpg 是測試代碼遺留的,沒有任何用處。關於 Controller 和 JSP 頁面本文不作介紹,關於Spring Boot 使用Controller 和 JSP ,前面已經有文章介紹。

啓動服務後訪問 http://localhost:8080/myspringboot/user 會自動跳到 login 頁面。 
登陸成功後,會打開 user 頁面(關於默認登陸頁、成功成功URL、沒有權限URL,在 ShiroConfiguration 中已經配置)。 
在 user 頁面上,不一樣用戶會根據權限不一樣顯示不一樣的內容,下面的修改操做也已經有文字說明,更換帳號測試便知。

而後咱們在實際項目中:不但要在頁面上控制不一樣權限隱藏或將某些操做設置爲不可用狀態,還要在實際上控制那個操做背後的請求是真的不可使用的。(例如:頁面上的修改按鈕已經灰化了,而我知道了修改按鈕正常狀況下點擊會觸發的請求,此時我直接模擬這個修改請求,應當是沒有權限的纔對,這樣纔算是真正的控制了權限。)


附: 
Filter Chain定義說明 
一、一個URL能夠配置多個Filter,使用逗號分隔 
二、當設置多個過濾器時,所有驗證經過,才視爲經過 
三、部分過濾器可指定參數,如perms,roles

Shiro內置的FilterChain

 

Filter Name Class
anon org.apache.shiro.web.filter.authc.AnonymousFilter
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
user org.apache.shiro.web.filter.authc.UserFilter
相關文章
相關標籤/搜索