Apache Shiro 是java的一個安全框架,Shiro能夠很是容易的開發出足夠好的應用,其不只能夠用在javaSE環境,也能夠用在javaEE環境。Shiro能夠幫助咱們完成:認證,受權,加密,會話,管理,與Web集合,緩存等。
Java領域中spring security也是一個開源的權限管理框架,可是spring security依賴spring運行,而shiro就相對獨立,最主要是由於shiro使用簡單,靈活,因此如今愈來愈多的用戶選擇shiro。html
身份認證/登陸,驗證用戶是否是擁有相應的身份
受權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限,判斷用戶是否能作事情,常見的如:驗證某個用是否擁有某個角色。或者細粒度的驗證某個用戶對某個資源是否具備某個權限
會話管理,即用戶登陸後就是一次會話,在沒有退出以前,它的全部信息都在會話中,會話能夠是普通javaSE環境的,也能夠是如Web環境的。
加密,保護數據的安全性,如密碼加密儲存到數據庫,而不是明文存儲。
Subject即主體,外部應用與subject進行交互,subject記錄了當前操做用戶,將用戶的概念理解爲當前操做的主體,多是一個經過瀏覽器請求的用戶,也多是一個運行的程序。 Subject在shiro中是一個接口,接口中定義了不少認證授相關的方法,外部程序經過subject進行認證授,而subject是經過SecurityManager安全管理器進行認證受權
SecurityManager即安全管理器,對所有的subject進行安全管理,它是shiro的核心,負責對全部的subject進行安全管理。 經過SecurityManager能夠完成subject的認證、受權等,實質上SecurityManager是經過Authenticator進行認證, 經過Authorizer進行受權,經過SessionManager進行會話管理等。 SecurityManager是一個接口,繼承了Authenticator, Authorizer, SessionManager這三個接口。
Authenticator即認證器,對用戶身份進行認證,Authenticator是一個接口,shiro提供ModularRealmAuthenticator實現類, 經過ModularRealmAuthenticator基本上能夠知足大多數需求,也能夠自定義認證器。
Authorizer即受權器,用戶經過認證器認證經過,在訪問功能時須要經過受權器判斷用戶是否有此功能的操做權限。
Realm即領域,至關於datasource數據源,securityManager進行安全認證須要經過Realm獲取用戶權限數據,好比:若是用戶身份數據在數據庫那麼realm就須要從數據庫獲取用戶身份信息。 注意:不要把realm理解成只是從數據源取數據,在realm中還有認證受權校驗的相關的代碼
sessionManager即會話管理,shiro框架定義了一套會話管理,它不依賴web容器的session,因此shiro可使用在非web應用上,也能夠將分佈式應用的會話集中在一點管理,此特性可以使它實現單點登陸。
SessionDAO即會話dao,是對session會話操做的一套接口,好比要將session存儲到數據庫,能夠經過jdbc將會話存儲到數據庫。
CacheManager即緩存管理,將用戶權限數據存儲在緩存,這樣能夠提升性能。
Cryptography即密碼管理,shiro提供了一套加密/解密的組件,方便開發。好比提供經常使用的散列、加/解密等功能。
shiro.ini配置文件共有[main],[users],[roles],[urls]共4部分組成 1. [main] 用於定義全局變量 2. [users] 用於定義用戶名及密碼 3. [roles] 用於定義角色 4. [urls] 用於定義訪問url及攔截驗證方式
#提供了對根對象 securityManager 及其依賴的配置 securityManager=org.apache.shiro.mgt.DefaultSecurityManager jdbcRealm = xxxxx securityManager.realms=$jdbcRealm
#提供了對用戶/密碼及其角色的配置,用戶名=密碼,角色 1,角色 2 username=password,role1,role2 #定義用戶信息 [users] #用戶名=密碼 admin=123456 test=123
#提供了角色及權限之間關係的配置,角色=權限 1,權限 2 role1=permission1,permission2 [roles] role1=admin:query,admin:add,admin:update,admin:delete,admin:export role2=user:query,user:add role3=test:query,test:export
#用於 web,提供了對 web url 攔截相關的配置,url=攔截器[參數],攔截器
Realm:域,Shiro 從 Realm 獲取安全數據(如用戶、角色、權限),就是說 SecurityManager要驗證用戶身份,那麼它須要從 Realm 獲取相應的用戶進行比較以肯定用戶身份是否合法;也須要從 Realm 獲得用戶相應的角色/權限進行驗證用戶是否能進行操做;能夠把 Realm 當作 DataSource ,即安全數據源。如咱們以前的ini配置方式 將使用org.apache.shiro.realm.text.IniRealm。 Shiro默認使用自帶的IniRealm,IniRealm從ini配置文件中讀取用戶的信息,大部分狀況下須要從系統的數據庫中讀取用戶信息, **因此須要自定義realm**
<!-- shiro核心依賴 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.2</version> </dependency>
package com.bdqn.shiro; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.realm.jdbc.JdbcRealm; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; public class JDBCRealmTest { public static void main(String[] args) { //1.讀取配置文件,初始化工廠對象 Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); //2.獲取SecurityManager實例 SecurityManager manager = factory.getInstance(); //3.將SecurityManager綁定到工具類 SecurityUtils.setSecurityManager(manager); //4.經過SecurityUtils獲得當前登陸的用戶 Subject currentUser = SecurityUtils.getSubject(); //5.窗口登陸令牌 UsernamePasswordToken token = new UsernamePasswordToken("test","123"); try { //6.登陸並傳入令牌 currentUser.login(token); System.out.println("身份信息驗證成功!"); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); System.out.println("身份信息驗證失敗!"); } //7.退出 currentUser.logout(); } }
Spring Boot框架
shiro
mysqljava
#加載驅動 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #數據庫鏈接路徑 spring.datasource.url=jdbc:mysql://localhost:3306/erp?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai #數據庫用戶名 spring.datasource.username=root #數據源類型(阿里巴巴) spring.datasource.type=com.alibaba.druid.pool.DruidDataSource #thymeleaf spring.thymeleaf.cache=false #日期格式 spring.jackson.date-format=yyyy-MM-dd HH:mm:ss spring.jackson.time-zone=GMT+8 #mybatis-plus #mybatis-plus.mapper-locations=classpath:mapper/*Mapper.xml,classpath:mapper/*/*Mapper.xml #日誌 logging.level.com.bdqn=debug
package com.bdqn.sys.config; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import com.bdqn.sys.realm.UserRealm; import org.apache.shiro.authc.credential.CredentialsMatcher; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.filter.DelegatingFilterProxy; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfiguration { private static final String SHIRO_DIALECT = "shiroDialect"; private static final String SHIRO_FILTER = "shiroFilter"; private String hashAlgorithmName = "md5";// 加密方式 private int hashIterations = 2;// 散列次數 /** * 聲明憑證匹配器 */ @Bean("credentialsMatcher") public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); credentialsMatcher.setHashAlgorithmName(hashAlgorithmName); credentialsMatcher.setHashIterations(hashIterations); return credentialsMatcher; } /** * 注入自定義的UserRealm * @return */ @Bean public UserRealm getUserRealm(CredentialsMatcher credentialsMatcher){ UserRealm userRealm = new UserRealm(); //注入憑證匹配器 userRealm.setCredentialsMatcher(credentialsMatcher); return userRealm; } /** * 建立DefaultWebSecurityManager對象,關聯自定義的UserRealm對象 * @param userRealm * @return */ @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm userRealm){ //建立DefaultWebSecurityManager對象 DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); //關聯自定義realm defaultWebSecurityManager.setRealm(userRealm); //返回DefaultWebSecurityManager對象 return defaultWebSecurityManager; } /** * 建立ShiroFilterFactoryBean對象,設置安全管理器 * @param securityManager * @return */ @Bean(SHIRO_FILTER) public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){ //建立ShiroFilterFactoryBean對象 ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); //設置安全管理器 factoryBean.setSecurityManager(securityManager); //設置過濾器鏈 Map<String,String> filterChainDefinitionsMap = new LinkedHashMap<String,String>(); //放行路徑(匿名訪問) filterChainDefinitionsMap.put("/resources/**","anon");//靜態資源 filterChainDefinitionsMap.put("/sys/user/login","anon");//登陸請求 filterChainDefinitionsMap.put("/sys/login","anon");//去到登陸頁面 filterChainDefinitionsMap.put("/","anon");//去到登陸頁面 filterChainDefinitionsMap.put("/login.html","anon");//去到登陸頁面 filterChainDefinitionsMap.put("/favicon.ico","anon");//小圖標 //退出 filterChainDefinitionsMap.put("/logout","logout"); //攔截請求 filterChainDefinitionsMap.put("/**","authc"); //將過濾器鏈設置到shiroFilterFactoryBean對象中 factoryBean.setFilterChainDefinitionMap(filterChainDefinitionsMap); //身份驗證失敗要去到登陸頁面 //若是不設置loginUrl,則默認找login.jsp頁面 factoryBean.setLoginUrl("/sys/login"); return factoryBean; } /** * 註冊shiro的委託過濾器,至關於以前在web.xml裏面配置的 * * @return */ @Bean public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterProxy() { FilterRegistrationBean<DelegatingFilterProxy> filterRegistrationBean = new FilterRegistrationBean<DelegatingFilterProxy>(); DelegatingFilterProxy proxy = new DelegatingFilterProxy(); proxy.setTargetFilterLifecycle(true); proxy.setTargetBeanName(SHIRO_FILTER); filterRegistrationBean.setFilter(proxy); return filterRegistrationBean; } /* 加入註解的使用,不加入這個註解不生效--開始 */ /** * * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } /* 加入註解的使用,不加入這個註解不生效--結束 */ /** * 這裏是爲了能在html頁面引用shiro標籤,上面兩個函數必須添加,否則會報錯 * * @return */ @Bean(name = SHIRO_DIALECT) public ShiroDialect shiroDialect() { return new ShiroDialect(); } }
package com.bdqn.sys.realm; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.bdqn.sys.entity.Permission; import com.bdqn.sys.entity.User; import com.bdqn.sys.service.PermissionService; import com.bdqn.sys.service.RoleService; import com.bdqn.sys.service.UserService; import com.bdqn.sys.utils.SystemConstant; import com.bdqn.sys.vo.LoginUserVo; 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.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import javax.annotation.Resource; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * 自定義Realm */ public class UserRealm extends AuthorizingRealm { @Resource private UserService userService; @Resource private RoleService roleService; @Resource private PermissionService permissionService; /** * 受權 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //建立受權對象 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //1.獲取當前登陸主體 LoginUserVo loginUserVo = (LoginUserVo) principalCollection.getPrimaryPrincipal(); //2.獲取當前用戶擁有的權限列表 Set<String> permissions = loginUserVo.getPermissions(); //3.判斷當前登陸用戶是不是超級管理員 if(loginUserVo.getUser().getType()== SystemConstant.SUPERUSER){ //超級管理員擁有全部操做權限 simpleAuthorizationInfo.addStringPermission("*:*"); }else{//普通用戶 //判斷權限集合是否有數據 if(permissions!=null && permissions.size()>0){ //非超級管理員須要根據本身擁有的角色進行受權 simpleAuthorizationInfo.addStringPermissions(permissions); } } return simpleAuthorizationInfo; } /** * 身份驗證 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //獲取當前登陸主體 String userName = (String) authenticationToken.getPrincipal(); try { //根據用戶名查詢用戶信息的方法 User user = userService.findUserByName(userName); //對象不爲空 if(user!=null){ //建立當前登陸用戶對象 //建立登陸用戶對象,傳入用戶信息,角色列表,權限列表 LoginUserVo loginUserVo = new LoginUserVo(user,null,null); /***************************關聯權限代碼開始***************************************/ //建立條件構造器對象 QueryWrapper<Permission> queryWrapper = new QueryWrapper<Permission>(); queryWrapper.eq("type",SystemConstant.TYPE_PERMISSION);//只查權限不查菜單 //根據當前登陸用戶ID查詢該用戶擁有的角色列表 Set<Integer> currentUserRoleIds = userService.findUserRoleByUserId(user.getId()); //建立集合保存每一個角色下擁有的權限菜單ID Set<Integer> pids = new HashSet<Integer>(); //循環遍歷當前用戶擁有的角色列表 for (Integer roleId : currentUserRoleIds) { //4.根據角色ID查詢每一個角色下擁有的權限菜單 Set<Integer> permissionIds = roleService.findRolePermissionByRoleId(roleId); //5.將查詢出來的權限id放到集合中 pids.addAll(permissionIds); } //建立集合保存權限 List<Permission> list = new ArrayList<Permission>(); //判斷pids集合是否有值 if(pids!=null && pids.size()>0){ queryWrapper.in("id",pids); //執行查詢 list = permissionService.list(queryWrapper); } //查詢權限編碼 Set<String> perCodes = new HashSet<String>(); //循環每個菜單 for (Permission permission : list) { perCodes.add(permission.getPercode()); } //給權限集合賦值 loginUserVo.setPermissions(perCodes); /***************************關聯權限代碼結束***************************************/ //建立鹽值(以用戶名做爲鹽值) ByteSource salt = ByteSource.Util.bytes(user.getSalt()); //建立身份驗證對象 //參數1:當前登陸對象 參數2:密碼 參數3:鹽值 參數4:域名 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(loginUserVo,user.getLoginpwd(),salt,""); return info; } } catch (Exception e) { e.printStackTrace(); } //驗證失敗 return null; } }