1、在pom中引入依賴jar包css
1 <properties> 2 <shiro.version>1.3.2</shiro.version> 3 </properties> 4 5 <!--shiro start--> 6 <dependency> 7 <groupId>org.apache.shiro</groupId> 8 <artifactId>shiro-core</artifactId> 9 <version>${shiro.version}</version> 10 </dependency> 11 <dependency> 12 <groupId>org.apache.shiro</groupId> 13 <artifactId>shiro-web</artifactId> 14 <version>${shiro.version}</version> 15 </dependency> 16 <dependency> 17 <groupId>org.apache.shiro</groupId> 18 <artifactId>shiro-ehcache</artifactId> 19 <version>${shiro.version}</version> 20 </dependency> 21 <dependency> 22 <groupId>org.apache.shiro</groupId> 23 <artifactId>shiro-spring</artifactId> 24 <version>${shiro.version}</version> 25 </dependency> 26 <!--shiro end-->
2、shiro配置類:java
ShiroConfiguration:web
1 package com.example.demo; 2 3 import org.apache.log4j.Logger; 4 import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; 5 import org.springframework.context.annotation.Bean; 6 import org.springframework.context.annotation.Configuration; 7 8 import java.util.LinkedHashMap; 9 import java.util.Map; 10 11 /** 12 * Shiro 配置 13 * Apache Shiro 核心經過 Filter 來實現,就好像SpringMvc 經過DispachServlet 來主控制同樣。 14 * 既然是使用 Filter 通常也就能猜到,是經過URL規則來進行過濾和權限校驗,因此咱們須要定義一系列關於URL的規則和訪問權限。 15 * Created by sun on 2017-4-2. 16 */ 17 @Configuration 18 @EnableTransactionManagement 19 public class ShiroConfiguration{ 20 21 private final Logger logger = Logger.getLogger(ShiroConfiguration.class); 22 23 /** 24 * ShiroFilterFactoryBean 處理攔截資源文件問題。 25 * 注意:單獨一個ShiroFilterFactoryBean配置是或報錯的,由於在 26 * 初始化ShiroFilterFactoryBean的時候須要注入:SecurityManager 27 * 28 Filter Chain定義說明 29 一、一個URL能夠配置多個Filter,使用逗號分隔 30 二、當設置多個過濾器時,所有驗證經過,才視爲經過 31 三、部分過濾器可指定參數,如perms,roles 32 * 33 */ 34 @Bean 35 public EhCacheManager getEhCacheManager(){ 36 EhCacheManager ehcacheManager = new EhCacheManager(); 37 ehcacheManager.setCacheManagerConfigFile("classpath:config/ehcache-shiro.xml"); 38 return ehcacheManager; 39 } 40 41 //配置shiro倉庫 42 @Bean(name = "myShiroRealm") 43 public MyShiroRealm myShiroRealm(EhCacheManager ehCacheManager){ 44 MyShiroRealm realm = new MyShiroRealm(); 45 realm.setCacheManager(ehCacheManager); 46 return realm; 47 } 48 49 //把shiro生命週期交給spring boot管理 50 @Bean(name = "lifecycleBeanPostProcessor") 51 public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){ 52 return new LifecycleBeanPostProcessor(); 53 } 54 55 //DefaultAdvisorAutoProxyCreator實現Spring自動代理 56 @Bean 57 public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){ 58 DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator(); 59 creator.setProxyTargetClass(true); 60 return creator; 61 } 62 63 //權限認證信息 64 @Bean(name = "securityManager") 65 public DefaultWebSecurityManager defaultWebSecurityManager(MyShiroRealm realm){ 66 System.out.println("shiro~~~~~~~~啓動"); 67 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); 68 //設置realm 69 securityManager.setRealm(realm); 70 securityManager.setCacheManager(getEhCacheManager()); 71 return securityManager; 72 } 73 74 @Bean 75 public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager){ 76 AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); 77 advisor.setSecurityManager(securityManager); 78 return advisor; 79 } 80 81 //shiro核心攔截器 82 @Bean(name = "shiroFilter") 83 public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){ 84 ShiroFilterFactoryBean factoryBean = new MyShiroFilterFactoryBean(); 85 factoryBean.setSecurityManager(securityManager); 86 // 若是不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面 87 factoryBean.setLoginUrl("/login"); 88 // 登陸成功後要跳轉的鏈接 89 factoryBean.setSuccessUrl("/welcome"); 90 factoryBean.setUnauthorizedUrl("/403"); 91 loadShiroFilterChain(factoryBean); 92 logger.info("shiro攔截器工廠類注入成功"); 93 return factoryBean; 94 } 95 96 //加載ShiroFilter權限控制規則 97 private void loadShiroFilterChain(ShiroFilterFactoryBean factoryBean) { 98 /**下面這些規則配置最好配置到配置文件中*/ 99 Map<String, String> filterChainMap = new LinkedHashMap<String, String>(); 100 // authc:該過濾器下的頁面必須驗證後才能訪問,它是Shiro內置的一個攔截器 101 // anon:它對應的過濾器裏面是空的,什麼都沒作,能夠理解爲不攔截 102 //authc:全部url都必須認證經過才能夠訪問; anon:全部url均可以匿名訪問 103 filterChainMap.put("/permission/userInsert", "anon"); 104 filterChainMap.put("/error", "anon"); 105 filterChainMap.put("/tUser/insert","anon"); 106 filterChainMap.put("/**", "authc"); 107 108 factoryBean.setFilterChainDefinitionMap(filterChainMap); 109 } 110 111 /* 112 1.LifecycleBeanPostProcessor,這是個DestructionAwareBeanPostProcessor的子類,負責org.apache.shiro.util.Initializable類型bean的生命週期的,初始化和銷燬。主要是AuthorizingRealm類的子類,以及EhCacheManager類。 113 2.HashedCredentialsMatcher,這個類是爲了對密碼進行編碼的,防止密碼在數據庫裏明碼保存,固然在登錄認證的生活,這個類也負責對form裏輸入的密碼進行編碼。 114 3.ShiroRealm,這是個自定義的認證類,繼承自AuthorizingRealm,負責用戶的認證和權限的處理,能夠參考JdbcRealm的實現。 115 4.EhCacheManager,緩存管理,用戶登錄成功後,把用戶信息和權限信息緩存起來,而後每次用戶請求時,放入用戶的session中,若是不設置這個bean,每一個請求都會查詢一次數據庫。 116 5.SecurityManager,權限管理,這個類組合了登錄,登出,權限,session的處理,是個比較重要的類。 117 6.ShiroFilterFactoryBean,是個factorybean,爲了生成ShiroFilter。它主要保持了三項數據,securityManager,filters,filterChainDefinitionManager。 118 7.DefaultAdvisorAutoProxyCreator,Spring的一個bean,由Advisor決定對哪些類的方法進行AOP代理。 119 8.AuthorizationAttributeSourceAdvisor,shiro裏實現的Advisor類,內部使用AopAllianceAnnotationsAuthorizingMethodInterceptor來攔截用如下註解的方法。 120 */ 121 }
MyShiroRealm :spring
package com.sun.configuration; import com.sun.permission.model.Role; import com.sun.permission.model.User; import com.sun.permission.service.PermissionService; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.log4j.Logger; import org.apache.shiro.authc.*; 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.springframework.beans.factory.annotation.Autowired; import java.util.List; /** * shiro的認證最終是交給了Realm進行執行 * 因此咱們須要本身從新實現一個Realm,此Realm繼承AuthorizingRealm * Created by sun on 2017-4-2. */ public class MyShiroRealm extends AuthorizingRealm { private static final Logger logger = Logger.getLogger(MyShiroRealm.class); @Autowired private PermissionService permissionService; /** * 登陸認證 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //UsernamePasswordToken用於存放提交的登陸信息 UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken; logger.info("登陸認證!"); logger.info("驗證當前Subject時獲取到token爲:" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE)); User user = permissionService.findByUserEmail(token.getUsername()); if (user != null){ logger.info("用戶: " + user.getEmail()); if(user.getStatus() == 0){ throw new DisabledAccountException(); } // 若存在,將此用戶存放到登陸認證info中,無需本身作密碼對比,Shiro會爲咱們進行密碼對比校驗 return new SimpleAuthenticationInfo(user.getEmail(), user.getPswd(), getName()); } return null; } /** * 權限認證(爲當前登陸的Subject授予角色和權限) * * 該方法的調用時機爲需受權資源被訪問時,而且每次訪問需受權資源都會執行該方法中的邏輯,這代表本例中並未啓用AuthorizationCache, * 若是連續訪問同一個URL(好比刷新),該方法不會被重複調用,Shiro有一個時間間隔(也就是cache時間,在ehcache-shiro.xml中配置), * 超過這個時間間隔再刷新頁面,該方法會被執行 * * doGetAuthorizationInfo()是權限控制, * 當訪問到頁面的時候,使用了相應的註解或者shiro標籤纔會執行此方法不然不會執行, * 因此若是隻是簡單的身份認證沒有權限的控制的話,那麼這個方法能夠不進行實現,直接返回null便可 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String loginName = (String) super.getAvailablePrincipal(principals); User user = permissionService.findByUserEmail(loginName); logger.info("權限認證!"); if (user != null){ // 權限信息對象info,用來存放查出的用戶的全部的角色及權限 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //用戶的角色集合 info.setRoles(permissionService.getRolesName(user.getId())); List<Role> roleList = permissionService.getRoleList(user.getId()); for (Role role : roleList){ //用戶的角色對應的全部權限 logger.info("角色: "+role.getName()); info.addStringPermissions(permissionService.getPermissionsName(role.getId())); } return info; } // 返回null將會致使用戶訪問任何被攔截的請求時都會自動跳轉到unauthorizedUrl指定的地址 return null; } }
MyShiroFilterFactoryBean:數據庫
1 package com.sun.configuration; 2 3 import org.apache.shiro.mgt.SecurityManager; 4 import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 5 import org.apache.shiro.web.filter.mgt.FilterChainManager; 6 import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver; 7 import org.apache.shiro.web.mgt.WebSecurityManager; 8 import org.apache.shiro.web.servlet.AbstractShiroFilter; 9 import org.springframework.beans.factory.BeanInitializationException; 10 11 import javax.servlet.FilterChain; 12 import javax.servlet.ServletException; 13 import javax.servlet.ServletRequest; 14 import javax.servlet.ServletResponse; 15 import javax.servlet.http.HttpServletRequest; 16 import java.io.IOException; 17 import java.util.HashSet; 18 import java.util.Set; 19 20 /** 21 * Created by sun on 2017-4-2. 22 */ 23 public class MyShiroFilterFactoryBean extends ShiroFilterFactoryBean { 24 // ShiroFilter將直接忽略的請求 25 private Set<String> ignoreExt; 26 27 public MyShiroFilterFactoryBean(){ 28 super(); 29 ignoreExt = new HashSet<String>(); 30 ignoreExt.add(".jpg"); 31 ignoreExt.add(".png"); 32 ignoreExt.add(".gif"); 33 ignoreExt.add(".bmp"); 34 ignoreExt.add(".js"); 35 ignoreExt.add(".css"); 36 } 37 /** 38 * 啓動時加載 39 */ 40 @Override 41 protected AbstractShiroFilter createInstance() throws Exception { 42 SecurityManager securityManager = getSecurityManager(); 43 if (securityManager == null){ 44 throw new BeanInitializationException("SecurityManager property must be set."); 45 } 46 47 if (!(securityManager instanceof WebSecurityManager)){ 48 throw new BeanInitializationException("The security manager does not implement the WebSecurityManager interface."); 49 } 50 51 PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver(); 52 FilterChainManager chainManager = createFilterChainManager(); 53 chainResolver.setFilterChainManager(chainManager); 54 return new MySpringShiroFilter((WebSecurityManager)securityManager, chainResolver); 55 } 56 57 /** 58 * 啓動時加載 59 */ 60 private class MySpringShiroFilter extends AbstractShiroFilter { 61 public MySpringShiroFilter( 62 WebSecurityManager securityManager, PathMatchingFilterChainResolver chainResolver) { 63 super(); 64 if (securityManager == null){ 65 throw new IllegalArgumentException("WebSecurityManager property cannot be null."); 66 } 67 setSecurityManager(securityManager); 68 if (chainResolver != null){ 69 setFilterChainResolver(chainResolver); 70 } 71 } 72 /** 73 * 頁面上傳輸的url先進入此方法驗證 74 */ 75 @Override 76 protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, 77 FilterChain chain) 78 throws ServletException, IOException { 79 HttpServletRequest request = (HttpServletRequest)servletRequest; 80 String str = request.getRequestURI().toLowerCase(); 81 boolean flag = true; 82 int idx = 0; 83 if ((idx = str.lastIndexOf(".")) > 0){ 84 str = str.substring(idx); 85 if (ignoreExt.contains(str.toLowerCase())){ 86 flag = false; 87 } 88 } 89 if (flag){ 90 super.doFilterInternal(servletRequest, servletResponse, chain); 91 } else { 92 chain.doFilter(servletRequest, servletResponse); 93 } 94 } 95 } 96 }
ehcache-shiro.xml:apache
1 <?xml version="1.0" encoding="UTF-8"?> 2 <ehcache updateCheck="false" name="shiroCache"> 3 <!-- 4 name:緩存名稱。 5 maxElementsInMemory:緩存最大數目 6 maxElementsOnDisk:硬盤最大緩存個數。 7 eternal:對象是否永久有效,一但設置了,timeout將不起做用。 8 overflowToDisk:是否保存到磁盤,當系統當機時 9 timeToIdleSeconds:設置對象在失效前的容許閒置時間(單位:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,默認值是0,也就是可閒置時間無窮大。 10 timeToLiveSeconds:設置對象在失效前容許存活時間(單位:秒)。最大時間介於建立時間和失效時間之間。僅當eternal=false對象不是永久有效時使用,默認是0.,也就是對象存活時間無窮大。 11 diskPersistent:是否緩存虛擬機重啓期數據 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. 12 diskSpoolBufferSizeMB:這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每一個Cache都應該有本身的一個緩衝區。 13 diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認是120秒。 14 memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理內存。默認策略是LRU(最近最少使用)。你能夠設置爲FIFO(先進先出)或是LFU(較少使用)。 15 clearOnFlush:內存數量最大時是否清除。 16 memoryStoreEvictionPolicy: 17 Ehcache的三種清空策略; 18 FIFO,first in first out,這個是你們最熟的,先進先出。 19 LFU, Less Frequently Used,就是上面例子中使用的策略,直白一點就是講一直以來最少被使用的。如上面所講,緩存的元素有一個hit屬性,hit值最小的將會被清出緩存。 20 LRU,Least Recently Used,最近最少使用的,緩存的元素有一個時間戳,當緩存容量滿了,而又須要騰出地方來緩存新的元素的時候,那麼現有緩存元素中時間戳離當前時間最遠的元素將被清出緩存。 21 --> 22 <defaultCache 23 maxElementsInMemory="10000" 24 eternal="false" 25 timeToIdleSeconds="120" 26 timeToLiveSeconds="120" 27 overflowToDisk="false" 28 diskPersistent="false" 29 diskExpiryThreadIntervalSeconds="120" 30 /> 31 <!-- 登陸記錄緩存鎖定10分鐘 --> 32 <cache name="passwordRetryCache" 33 maxEntriesLocalHeap="2000" 34 eternal="false" 35 timeToIdleSeconds="3600" 36 timeToLiveSeconds="0" 37 overflowToDisk="false" 38 statistics="true"> 39 </cache> 40 </ehcache>
登陸的controller類:緩存
1 package com.sun.permission.controller; 2 3 import com.sun.permission.model.User; 4 import com.sun.permission.service.PermissionService; 5 import com.sun.util.CommonUtils; 6 import org.apache.commons.lang3.StringUtils; 7 import org.apache.log4j.Logger; 8 import org.apache.shiro.SecurityUtils; 9 import org.apache.shiro.authc.*; 10 import org.apache.shiro.session.Session; 11 import org.apache.shiro.subject.Subject; 12 import org.springframework.beans.factory.annotation.Autowired; 13 import org.springframework.stereotype.Controller; 14 import org.springframework.validation.BindingResult; 15 import org.springframework.web.bind.annotation.RequestMapping; 16 import org.springframework.web.bind.annotation.RequestMethod; 17 import org.springframework.web.servlet.ModelAndView; 18 import org.springframework.web.servlet.mvc.support.RedirectAttributes; 19 20 import javax.validation.Valid; 21 22 /** 23 * Created by sun on 2017-4-2. 24 */ 25 @Controller 26 public class LoginController { 27 private static final Logger logger = Logger.getLogger(LoginController.class); 28 @Autowired 29 private PermissionService permissionService; 30 31 @RequestMapping(value="/login",method= RequestMethod.GET) 32 public ModelAndView loginForm(){ 33 ModelAndView model = new ModelAndView(); 34 model.addObject("user", new User()); 35 model.setViewName("login"); 36 return model; 37 } 38 39 @RequestMapping(value="/login",method=RequestMethod.POST) 40 public String login(@Valid User user, BindingResult bindingResult, RedirectAttributes redirectAttributes){ 41 if(bindingResult.hasErrors()){ 42 return "redirect:login"; 43 } 44 String email = user.getEmail(); 45 if(StringUtils.isBlank(user.getEmail()) || StringUtils.isBlank(user.getPswd())){ 46 logger.info("用戶名或密碼爲空! "); 47 redirectAttributes.addFlashAttribute("message", "用戶名或密碼爲空!"); 48 return "redirect:login"; 49 } 50 //驗證 51 UsernamePasswordToken token = new UsernamePasswordToken(user.getEmail(), user.getPswd()); 52 //獲取當前的Subject 53 Subject currentUser = SecurityUtils.getSubject(); 54 try { 55 //在調用了login方法後,SecurityManager會收到AuthenticationToken,並將其發送給已配置的Realm執行必須的認證檢查 56 //每一個Realm都能在必要時對提交的AuthenticationTokens做出反應 57 //因此這一步在調用login(token)方法時,它會走到MyRealm.doGetAuthenticationInfo()方法中,具體驗證方式詳見此方法 58 logger.info("對用戶[" + email + "]進行登陸驗證..驗證開始"); 59 currentUser.login(token); 60 logger.info("對用戶[" + email + "]進行登陸驗證..驗證經過"); 61 }catch(UnknownAccountException uae){ 62 logger.info("對用戶[" + email + "]進行登陸驗證..驗證未經過,未知帳戶"); 63 redirectAttributes.addFlashAttribute("message", "未知帳戶"); 64 }catch(IncorrectCredentialsException ice){ 65 logger.info("對用戶[" + email + "]進行登陸驗證..驗證未經過,錯誤的憑證"); 66 redirectAttributes.addFlashAttribute("message", "密碼不正確"); 67 }catch(LockedAccountException lae){ 68 logger.info("對用戶[" + email + "]進行登陸驗證..驗證未經過,帳戶已鎖定"); 69 redirectAttributes.addFlashAttribute("message", "帳戶已鎖定"); 70 }catch(ExcessiveAttemptsException eae){ 71 logger.info("對用戶[" + email + "]進行登陸驗證..驗證未經過,錯誤次數大於5次,帳戶已鎖定"); 72 redirectAttributes.addFlashAttribute("message", "用戶名或密碼錯誤次數大於5次,帳戶已鎖定"); 73 }catch (DisabledAccountException sae){ 74 logger.info("對用戶[" + email + "]進行登陸驗證..驗證未經過,賬號已經禁止登陸"); 75 redirectAttributes.addFlashAttribute("message", "賬號已經禁止登陸"); 76 }catch(AuthenticationException ae){ 77 //經過處理Shiro的運行時AuthenticationException就能夠控制用戶登陸失敗或密碼錯誤時的情景 78 logger.info("對用戶[" + email + "]進行登陸驗證..驗證未經過,堆棧軌跡以下"); 79 ae.printStackTrace(); 80 redirectAttributes.addFlashAttribute("message", "用戶名或密碼不正確"); 81 } 82 //驗證是否登陸成功 83 if(currentUser.isAuthenticated()){ 84 logger.info("用戶[" + email + "]登陸認證經過(這裏能夠進行一些認證經過後的一些系統參數初始化操做)"); 85 //把當前用戶放入session 86 Session session = currentUser.getSession(); 87 User tUser = permissionService.findByUserEmail(email); 88 session.setAttribute("currentUser",tUser); 89 return "/welcome"; 90 }else{ 91 token.clear(); 92 return "redirect:login"; 93 } 94 } 95 96 @RequestMapping(value="/logout",method=RequestMethod.GET) 97 public String logout(RedirectAttributes redirectAttributes ){ 98 //使用權限管理工具進行用戶的退出,跳出登陸,給出提示信息 99 SecurityUtils.getSubject().logout(); 100 redirectAttributes.addFlashAttribute("message", "您已安全退出"); 101 return "redirect:login"; 102 } 103 104 @RequestMapping("/403") 105 public String unauthorizedRole(){ 106 logger.info("------沒有權限-------"); 107 return "errorPermission"; 108 } 109 110 }