shiro

概念

Apache Shiro 是java的一個安全框架,Shiro能夠很是容易的開發出足夠好的應用,其不只能夠用在javaSE環境,也能夠用在javaEE環境。Shiro能夠幫助咱們完成:認證,受權,加密,會話,管理,與Web集合,緩存等。
Java領域中spring security也是一個開源的權限管理框架,可是spring security依賴spring運行,而shiro就相對獨立,最主要是由於shiro使用簡單,靈活,因此如今愈來愈多的用戶選擇shiro。html

基本功能

image-20191203112031777.png

Authentication

身份認證/登陸,驗證用戶是否是擁有相應的身份

Authorization

受權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限,判斷用戶是否能作事情,常見的如:驗證某個用是否擁有某個角色。或者細粒度的驗證某個用戶對某個資源是否具備某個權限

Session Manager

會話管理,即用戶登陸後就是一次會話,在沒有退出以前,它的全部信息都在會話中,會話能夠是普通javaSE環境的,也能夠是如Web環境的。

Cryptography

加密,保護數據的安全性,如密碼加密儲存到數據庫,而不是明文存儲。
  • Web Support:Web 支持,能夠很是容易的集成到web環境。
  • Caching:緩存,好比用戶登陸後,其用戶信息,用戶的角色/權限沒必要每次去查,這樣能夠提升效率。
  • Concurrency:shiro支持多線程應用的併發驗證,即如在一個線程中開啓另外一個線程,能把權限自動傳播過去。
  • Testing:提供測試支持。
  • Run As:容許一個用戶僞裝爲另外一個用戶的身份進行訪問。
  • Remember Me:記住我,這個是很是常見的功能,即一次登陸後,下次再來的話不用登陸了。
  • Shiro:不會去維護用戶,維護權限,這些須要咱們本身去設計/提供而後經過相應的接口注入給Shiro便可。

架構

image-20191204153257793.png

subject

Subject即主體,外部應用與subject進行交互,subject記錄了當前操做用戶,將用戶的概念理解爲當前操做的主體,多是一個經過瀏覽器請求的用戶,也多是一個運行的程序。  Subject在shiro中是一個接口,接口中定義了不少認證授相關的方法,外部程序經過subject進行認證授,而subject是經過SecurityManager安全管理器進行認證受權

SecurityManager

SecurityManager即安全管理器,對所有的subject進行安全管理,它是shiro的核心,負責對全部的subject進行安全管理。
經過SecurityManager能夠完成subject的認證、受權等,實質上SecurityManager是經過Authenticator進行認證,
經過Authorizer進行受權,經過SessionManager進行會話管理等。
SecurityManager是一個接口,繼承了Authenticator, Authorizer, SessionManager這三個接口。

Authenticator

Authenticator即認證器,對用戶身份進行認證,Authenticator是一個接口,shiro提供ModularRealmAuthenticator實現類,
經過ModularRealmAuthenticator基本上能夠知足大多數需求,也能夠自定義認證器。

Authorizer

Authorizer即受權器,用戶經過認證器認證經過,在訪問功能時須要經過受權器判斷用戶是否有此功能的操做權限。

realm

Realm即領域,至關於datasource數據源,securityManager進行安全認證須要經過Realm獲取用戶權限數據,好比:若是用戶身份數據在數據庫那麼realm就須要從數據庫獲取用戶身份信息。
注意:不要把realm理解成只是從數據源取數據,在realm中還有認證受權校驗的相關的代碼

sessionManager

sessionManager即會話管理,shiro框架定義了一套會話管理,它不依賴web容器的session,因此shiro可使用在非web應用上,也能夠將分佈式應用的會話集中在一點管理,此特性可以使它實現單點登陸。

SessionDAO

SessionDAO即會話dao,是對session會話操做的一套接口,好比要將session存儲到數據庫,能夠經過jdbc將會話存儲到數據庫。

CacheManager

CacheManager即緩存管理,將用戶權限數據存儲在緩存,這樣能夠提升性能。

Cryptography

Cryptography即密碼管理,shiro提供了一套加密/解密的組件,方便開發。好比提供經常使用的散列、加/解密等功能。

默認過濾器

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
logout org.apache.shiro.web.filter.authc.LogoutFilter
noSessionCreation org.apache.shiro.web.filter.session.NoSessionCreationFilter
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

shiro.ini

shiro.ini配置文件共有[main],[users],[roles],[urls]共4部分組成

1.  [main] 用於定義全局變量

2.  [users] 用於定義用戶名及密碼

3.  [roles] 用於定義角色

4.  [urls] 用於定義訪問url及攔截驗證方式

[main]

#提供了對根對象 securityManager 及其依賴的配置
securityManager=org.apache.shiro.mgt.DefaultSecurityManager
jdbcRealm = xxxxx
securityManager.realms=$jdbcRealm

[users]

#提供了對用戶/密碼及其角色的配置,用戶名=密碼,角色 1,角色 2 username=password,role1,role2
#定義用戶信息
[users]
#用戶名=密碼
admin=123456
test=123

[roles]

#提供了角色及權限之間關係的配置,角色=權限 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

[urls]

#用於 web,提供了對 web url 攔截相關的配置,url=攔截器[參數],攔截器

Realm域

Realm:域,Shiro 從 Realm 獲取安全數據(如用戶、角色、權限),就是說 SecurityManager要驗證用戶身份,那麼它須要從 Realm 獲取相應的用戶進行比較以肯定用戶身份是否合法;也須要從 Realm 獲得用戶相應的角色/權限進行驗證用戶是否能進行操做;能夠把 Realm 當作 DataSource ,即安全數據源。如咱們以前的ini配置方式 將使用org.apache.shiro.realm.text.IniRealm。
Shiro默認使用自帶的IniRealm,IniRealm從ini配置文件中讀取用戶的信息,大部分狀況下須要從系統的數據庫中讀取用戶信息,
**因此須要自定義realm**

jar包

<!-- shiro核心依賴 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.4.2</version>
</dependency>

Realm

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


  • 配置 application.properties
#加載驅動
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
  • ShiroConfiguration 類
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();
    }
}
  • Realm 類
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;
    }


}
相關文章
相關標籤/搜索