在閱讀這篇文章以前假設你已經對Apache Shiro(後面統一用Shiro做爲代指)有了必定的瞭解,若是你還對Shiro不熟悉的話在這篇文章的結尾附有相關的學習資料,關於Shiro是用來作什麼的這裏有個不錯的介紹,在後面的文章中就不在對其進行描述了。後面的文章將圍繞着 Spring Boot 集成Shiro 來進行展開。html
<!-- 使用Shiro認證 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.5</version> </dependency>
因爲篇幅緣由這裏不進行展開 提供一個參考前端
AbstractUserRealm繼承AuthorizingRealm,並重寫doGetAuthorizationInfo(用於獲取認證成功後的角色、權限等信息) 和 doGetAuthenticationInfo(驗證當前登陸的Subject)方法:web
public abstract class AbstractUserRealm extends AuthorizingRealm { private static final Logger logger = LoggerFactory.getLogger(AbstractUserRealm.class); @Autowired private UserRepository userRepository; //獲取用戶組的權限信息 public abstract UserRolesAndPermissions doGetGroupAuthorizationInfo(User userInfo); //獲取用戶角色的權限信息 public abstract UserRolesAndPermissions doGetRoleAuthorizationInfo(User userInfo); /** * 獲取受權信息 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String currentLoginName = (String) principals.getPrimaryPrincipal(); Set<String> userRoles = new HashSet<>(); Set<String> userPermissions = new HashSet<>(); //從數據庫中獲取當前登陸用戶的詳細信息 User userInfo = userRepository.findByLoginName(currentLoginName); if (null != userInfo) { UserRolesAndPermissions groupContainer = doGetGroupAuthorizationInfo(userInfo); UserRolesAndPermissions roleContainer = doGetGroupAuthorizationInfo(userInfo); userRoles.addAll(groupContainer.getUserRoles()); userRoles.addAll(roleContainer.getUserRoles()); userPermissions.addAll(groupContainer.getUserPermissions()); userPermissions.addAll(roleContainer.getUserPermissions()); } else { throw new AuthorizationException(); } //爲當前用戶設置角色和權限 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.addRoles(userRoles); authorizationInfo.addStringPermissions(userPermissions); logger.info("###【獲取角色成功】[SessionId] => {}", SecurityUtils.getSubject().getSession().getId()); return authorizationInfo; } /** * 登陸認證 */ @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken authenticationToken) throws AuthenticationException { //UsernamePasswordToken對象用來存放提交的登陸信息 UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; //查出是否有此用戶 User user = userRepository.findByLoginName(token.getUsername()); if (user != null) { // 若存在,將此用戶存放到登陸認證info中,無需本身作密碼對比,Shiro會爲咱們進行密碼對比校驗 return new SimpleAuthenticationInfo(user.getLoginName(), user.getPassword(), getName()); } return null; } protected class UserRolesAndPermissions { Set<String> userRoles; Set<String> userPermissions; public UserRolesAndPermissions(Set<String> userRoles, Set<String> userPermissions) { this.userRoles = userRoles; this.userPermissions = userPermissions; } public Set<String> getUserRoles() { return userRoles; } public Set<String> getUserPermissions() { return userPermissions; } }
@Component public class UserRealm extends AbstractUserRealm { @Override public UserRolesAndPermissions doGetGroupAuthorizationInfo(User userInfo) { Set<String> userRoles = new HashSet<>(); Set<String> userPermissions = new HashSet<>(); //TODO 獲取當前用戶下擁有的全部角色列表,及權限 return new UserRolesAndPermissions(userRoles, userPermissions); } @Override public UserRolesAndPermissions doGetRoleAuthorizationInfo(User userInfo) { Set<String> userRoles = new HashSet<>(); Set<String> userPermissions = new HashSet<>(); //TODO 獲取當前用戶下擁有的全部角色列表,及權限 return new UserRolesAndPermissions(userRoles, userPermissions); } }
這是最重要的一步等價於常規的Spring web應用的配置文件,將相關的配置託管給Spring 管理。spring
@Configuration public class ShiroConfiguration { private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class); /** * Shiro的Web過濾器Factory 命名:shiroFilter<br /> * * @param securityManager * @return */ @Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { logger.info("注入Shiro的Web過濾器-->shiroFilter", ShiroFilterFactoryBean.class); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //Shiro的核心安全接口,這個屬性是必須的 shiroFilterFactoryBean.setSecurityManager(securityManager); //要求登陸時的連接(可根據項目的URL進行替換),非必須的屬性,默認會自動尋找Web工程根目錄下的"/login.jsp"頁面 shiroFilterFactoryBean.setLoginUrl("/login"); //登陸成功後要跳轉的鏈接,邏輯也能夠自定義,例如返回上次請求的頁面 shiroFilterFactoryBean.setSuccessUrl("/index"); //用戶訪問未對其受權的資源時,所顯示的鏈接 shiroFilterFactoryBean.setUnauthorizedUrl("/403"); /*定義shiro過濾器,例如實現自定義的FormAuthenticationFilter,須要繼承FormAuthenticationFilter **本例中暫不自定義實現,在下一節實現驗證碼的例子中體現 */ /*定義shiro過濾鏈 Map結構 * Map中key(xml中是指value值)的第一個'/'表明的路徑是相對於HttpServletRequest.getContextPath()的值來的 * anon:它對應的過濾器裏面是空的,什麼都沒作,這裏.do和.jsp後面的*表示參數,比方說login.jsp?main這種 * authc:該過濾器下的頁面必須驗證後才能訪問,它是Shiro內置的一個攔截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter */ Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // 配置退出過濾器,其中的具體的退出代碼Shiro已經替咱們實現了 filterChainDefinitionMap.put("/logout", "logout"); // <!-- 過濾鏈定義,從上向下順序執行,通常將 /**放在最爲下邊 -->:這是一個坑呢,一不當心代碼就很差使了; // <!-- authc:全部url都必須認證經過才能夠訪問; anon:全部url都均可以匿名訪問--> filterChainDefinitionMap.put("/login", "anon");//anon 能夠理解爲不攔截 filterChainDefinitionMap.put("/reg", "anon"); filterChainDefinitionMap.put("/plugins/**", "anon"); filterChainDefinitionMap.put("/pages/**", "anon"); filterChainDefinitionMap.put("/api/**", "anon"); filterChainDefinitionMap.put("/dists/img/*", "anon"); filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public EhCacheManager ehCacheManager() { EhCacheManager cacheManager = new EhCacheManager(); return cacheManager; } /** * 不指定名字的話,自動建立一個方法名第一個字母小寫的bean * @Bean(name = "securityManager") * @return */ @Bean public SecurityManager securityManager(UserRealm userRealm) { logger.info("注入Shiro的Web過濾器-->securityManager", ShiroFilterFactoryBean.class); DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(userRealm); //注入緩存管理器; securityManager.setCacheManager(ehCacheManager());//這個若是執行屢次,也是一樣的一個對象; return securityManager; } /** * Shiro生命週期處理器 * @return */ @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * 開啓Shiro的註解(如@RequiresRoles,@RequiresPermissions),需藉助SpringAOP掃描使用Shiro註解的類,並在必要時進行安全邏輯驗證 * 配置如下兩個bean(DefaultAdvisorAutoProxyCreator(可選)和AuthorizationAttributeSourceAdvisor)便可實現此功能 * @return */ @Bean @DependsOn({"lifecycleBeanPostProcessor"}) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }
@Controller public class SecurityController { private static final Logger logger = LoggerFactory.getLogger(SecurityController.class); @Autowired private UserService userService; @GetMapping("/login") public String loginForm() { return "login"; } @PostMapping("/login") public String login(@Valid User user, BindingResult bindingResult, RedirectAttributes redirectAttributes) { if (bindingResult.hasErrors()) { return "login"; } String loginName = user.getLoginName(); logger.info("準備登錄用戶 => {}", loginName); UsernamePasswordToken token = new UsernamePasswordToken(loginName,user.getPassword()); //獲取當前的Subject Subject currentUser = SecurityUtils.getSubject(); try { //在調用了login方法後,SecurityManager會收到AuthenticationToken,並將其發送給已配置的Realm執行必須的認證檢查 //每一個Realm都能在必要時對提交的AuthenticationTokens做出反應 //因此這一步在調用login(token)方法時,它會走到MyRealm.doGetAuthenticationInfo()方法中,具體驗證方式詳見此方法 logger.info("對用戶[" + loginName + "]進行登陸驗證..驗證開始"); currentUser.login(token); logger.info("對用戶[" + loginName + "]進行登陸驗證..驗證經過"); } catch (UnknownAccountException uae) { logger.info("對用戶[" + loginName + "]進行登陸驗證..驗證未經過,未知帳戶"); redirectAttributes.addFlashAttribute("message", "未知帳戶"); } catch (IncorrectCredentialsException ice) { logger.info("對用戶[" + loginName + "]進行登陸驗證..驗證未經過,錯誤的憑證"); redirectAttributes.addFlashAttribute("message", "密碼不正確"); } catch (LockedAccountException lae) { logger.info("對用戶[" + loginName + "]進行登陸驗證..驗證未經過,帳戶已鎖定"); redirectAttributes.addFlashAttribute("message", "帳戶已鎖定"); } catch (ExcessiveAttemptsException eae) { logger.info("對用戶[" + loginName + "]進行登陸驗證..驗證未經過,錯誤次數過多"); redirectAttributes.addFlashAttribute("message", "用戶名或密碼錯誤次數過多"); } catch (AuthenticationException ae) { //經過處理Shiro的運行時AuthenticationException就能夠控制用戶登陸失敗或密碼錯誤時的情景 logger.info("對用戶[" + loginName + "]進行登陸驗證..驗證未經過,堆棧軌跡以下"); ae.printStackTrace(); redirectAttributes.addFlashAttribute("message", "用戶名或密碼不正確"); } //驗證是否登陸成功 if (currentUser.isAuthenticated()) { logger.info("用戶[" + loginName + "]登陸認證經過(這裏能夠進行一些認證經過後的一些系統參數初始化操做)"); return "redirect:/index"; } else { token.clear(); return "redirect:/login"; } } @GetMapping("/logout") public String logout(RedirectAttributes redirectAttributes) { //使用權限管理工具進行用戶的退出,跳出登陸,給出提示信息 SecurityUtils.getSubject().logout(); redirectAttributes.addFlashAttribute("message", "您已安全退出"); return "redirect:/login"; } @GetMapping("/reg") @ResponseBody public Result<String> reg(@Valid User user, BindingResult bindingResult) { if (bindingResult.hasErrors()) { return Result.error("用戶信息填寫不完整"); } userService.save(user); return Result.ok(); } }
一個簡單 form表單提交的demo數據庫
<form action="login" method="POST"> <div class="form-group has-feedback"> <input name="loginName" type="text" class="form-control" placeholder="用戶名"/> <span class="glyphicon glyphicon-user form-control-feedback"></span> </div> <div class="form-group has-feedback"> <input name="password" type="password" class="form-control"/> <span class="glyphicon glyphicon-lock form-control-feedback"></span> </div> <div class="row"> <!-- /.col --> <div class="col-xs-12"> <button type="submit" class="btn btn-primary btn-block btn-flat">登 錄</button> </div> <!-- /.col --> </div> </form>
權限註解:apache
@RequiresAuthentication 表示當前Subject已經經過login進行了身份驗證;即Subject. isAuthenticated()返回true。 @RequiresUser 表示當前Subject已經身份驗證或者經過記住我登陸的。 @RequiresGuest 表示當前Subject沒有身份驗證或經過記住我登陸過,便是遊客身份。 @RequiresRoles(value={「admin」, 「user」}, logical= Logical.AND) 表示當前Subject須要角色admin和user。 @RequiresPermissions (value={「user:a」, 「user:b」}, logical= Logical.OR) 表示當前Subject須要權限user:a或user:b。
標籤
代碼驗證:
(暫時忽略)留待補充api
Shiro 做爲一款安全框架爲咱們提供了經常使用的功能,已經足夠應對絕大多數的業務須要,在下一篇文章中將介紹一款更增強大的安全框架 Spring Security。緩存
Spring Boot系列(十五) 安全框架Apache Shiro(一)基本功能
Spring Boot Shiro 權限管理安全
Apache Shiro 使用手冊
《跟開濤學Shiro》 - 博客版
跟開濤學 Shiro - wiki版
官方文檔app