標籤:技術分享 static subject 機器 set 資料 ons man logout
1、架構
hiro是一個強大的簡單易用的Java安全架構,主要用來更便捷的認證,授權,加密,會話管理。Shiro首要的和最重要的目標就是容易使用並且容易理解。
Shiro是一個有許多特性的全面的安全架構,下面這幅圖可以瞭解Shiro的特性:
可以看出shiro除了基本的認證,授權,會話管理,加密之外,還有許多額外的特性。
從大的角度來看,Shiro有三個主要的概念:Subject,SecurityManager,Realms,下面這幅圖可以看到這些原件之間的互動。
Subject:主體,代表了當前“使用者”,這個使用者不一定是一個具體的人,與當前應用互動的任何東西都是Subject,如網路爬蟲,機器人等;即一個抽象概念;所有Subject都綁定到SecurityManager,與Subject的所有互動都會委託給SecurityManager;可以把Subject認為是一個門面;SecurityManager才是實際的執行者;
SecurityManager:安全管理器;即所有與安全有關的操作都會與SecurityManager互動;且它管理著所有Subject;可以看出它是Shiro的核心,它負責與後邊介紹的其他組件進行互動,如果學習過SpringMVC,你可以把它看成DispatcherServlet前端控制器;
Realm:域,Shiro從從Realm擷取安全資料(如使用者、角色、許可權),就是說SecurityManager要驗證使用者身份,那麼它需要從Realm擷取相應的使用者進行比較以確定使用者身份是否合法;也需要從Realm得到使用者相應的角色/許可權進行驗證使用者是否能進行操作;可以把Realm看成DataSource,即安全資料來源。
2、名詞解釋:
Authentication:身份認證/登入,驗證使用者是不是擁有相應的身份;
Authorization:授權,即許可權驗證,驗證某個已認證的使用者是否擁有某個許可權;即判斷使用者是否能做事情,常見的如:驗證某個使用者是否擁有某個角色。或者細粒度的驗證某個使用者對某個資源是否具有某個許可權;
Session Manager:會話管理,即使用者登入後就是一次會話,在沒有退出之前,它的所有資訊都在會話中;會話可以是普通JavaSE環境的,也可以是如Web環境的;
Cryptography:加密,保護資料的安全性,如密碼加密儲存到資料庫,而不是明文儲存;
Web Support:Web支援,可以非常容易的整合到Web環境;
Caching:緩衝,比如使用者登入後,其使用者資訊、擁有的角色/許可權不必每次去查,這樣可以提高效率;
Concurrency:shiro支援多線程應用的並發驗證,即如在一個線程中開啟另一個線程,能把許可權自動傳播過去;
Testing:提供測試支援;
Run As:允許一個使用者假裝為另一個使用者(如果他們允許)的身份進行訪問;
Remember Me:記住我,這個是非常常見的功能,即一次登入後,下次再來的話不用登入了。
記住一點,Shiro不會去維護使用者、維護許可權;這些需要我們自己去設計/提供;然後通過相應的介面注入給Shiro即可。
3、主要代碼
public class ShiroDbRealm extends AuthorizingRealm { private static final Logger LOGGER = LogManager.getLogger(ShiroDbRealm.class); @Autowired private UserService userService; @Autowired private IRoleService roleService; public ShiroDbRealm(CacheManager cacheManager, CredentialsMatcher matcher) { super(cacheManager, matcher); } /** * Shiro登入認證 * 使用者登陸時執行 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException { LOGGER.info("Shiro開始登入認證"); UsernamePasswordToken token = (UsernamePasswordToken) authcToken; UserVO user = userService.queryByLoginName(token.getUsername()); // 帳號不存在 if (user == null) { return null; } // 帳號未啟用 if (user.getStatus() == 1) { return null; } // 讀取使用者的url和角色 Map<String, Set<String>> resourceMap = roleService.selectResourceMapByUserId(user.getId()); Set<String> urls = resourceMap.get("urls"); Set<String> roles = resourceMap.get("roles"); ShiroUser shiroUser = new ShiroUser(user.getId(), user.getLoginName(), user.getName(), urls); shiroUser.setRoles(roles); // 認證緩衝資訊 return new SimpleAuthenticationInfo(shiroUser, user.getPassword().toCharArray(), getName()); } /** * Shiro許可權認證 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setRoles(shiroUser.getRoles()); info.addStringPermissions(shiroUser.getUrlSet()); return info; } @Override public void onLogout(PrincipalCollection principals) { super.clearCachedAuthorizationInfo(principals); ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal(); removeUserCache(shiroUser); } /** * 清除使用者緩衝 * @param shiroUser */ public void removeUserCache(ShiroUser shiroUser){ removeUserCache(shiroUser.getLoginName()); } /** * 清除使用者緩衝 * @param loginName */ public void removeUserCache(String loginName){ SimplePrincipalCollection principals = new SimplePrincipalCollection(); principals.add(loginName, super.getName()); super.clearCachedAuthenticationInfo(principals); }}
登陸的代碼為:
@PostMapping("/login") @ResponseBody public Object loginPost(String loginName, String password) throws IOException { if (StringUtils.isBlank(loginName)) { throw new RuntimeException("使用者名稱不可為空"); } if (StringUtils.isBlank(password)) { throw new RuntimeException("密碼不可為空"); } Subject user = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(loginName, password); try { logger.info("開始登入"); user.login(token); return renderSuccess("登陸成功"); } catch (UnknownAccountException e) { throw new RuntimeException("帳號不存在!", e); } catch (DisabledAccountException e) { throw new RuntimeException("帳號未啟用!", e); } catch (IncorrectCredentialsException e) { throw new RuntimeException("密碼錯誤!", e); } catch (Throwable e) { throw new RuntimeException(e.getMessage(), e); } }
4、執行流程
使用者提交使用者名稱和密碼 --> shiro 封裝令牌 --> realm 通過使用者名稱將密碼查詢返回 --> shiro 自動去比較查詢出密碼和使用者輸入密碼是否一致--> 進行登陸控制。
login方法中調用user.login(token)後就會觸發protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)方法的執行。
代碼中使用SecurityUtils.getSubject().isPermitted("/public/getdatalist")時,會觸發protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
方法進行許可權認證。
java使用shiro小記