shiro入門筆記

關於 Apache Shiro 概念基本都粘自官網 http://shiro.apache.org/
詳細中文博客 http://wiki.jikexueyuan.com/p...
與SpringBoot整合 https://segmentfault.com/a/11...

Shiro 簡介

Apache Shiro是一個功能強大且靈活的開源安全框架,能夠清晰地處理身份驗證,受權,企業會話管理和加密。html

如下是Apache Shiro能夠作的一些事情:前端

  • 驗證用戶以驗證其身份
  • 爲用戶執行訪問控制
  • 在任何環境中使用Session API,即便沒有Web容器或EJB容器也是如此。
  • ......

功能簡介

clipboard.png

  • Authentication:身份認證/登陸,驗證用戶是否是擁有相應的身份;
  • Authorization:受權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限;即判斷用戶是否能進行什麼操做,如:驗證某個用戶是否擁有某個角色。或者細粒度的驗證某個用戶對某個資源是否具備某個權限;
  • Session Manager:會話管理,即用戶登陸後就是一次會話,在沒有退出以前,它的全部信息都在會話中;會話能夠是普通 JavaSE 環境,也能夠是 Web 環境的;
  • Cryptography:加密,保護數據的安全性,如密碼加密存儲到數據庫,而不是明文存儲;
  • Web Support:Web 支持,能夠很是容易的集成到Web 環境;
  • Caching:緩存,好比用戶登陸後,其用戶信息、擁有的角色/權限沒必要每次去查,這樣能夠提升效率;
  • Concurrency:Shiro 支持多線程應用的併發驗證,即如在一個線程中開啓另外一個線程,能
  • 把權限自動傳播過去;
  • Testing:提供測試支持;
  • Run As:容許一個用戶僞裝爲另外一個用戶(若是他們容許)的身份進行訪問;
  • Remember Me:記住我,這個是很是常見的功能,即一次登陸後,下次再來的話不用登陸了

Shiro 詳細的架構

能夠參考官方文檔:http://shiro.apache.org/archi...web

Shiro web工程搭建

1.Maven 架包依賴

緩存架包先用 ehcachespring

<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-all</artifactId>
  <version>1.3.2</version>
</dependency>

<dependency>
  <groupId>net.sf.ehcache</groupId>
  <artifactId>ehcache</artifactId>
  <version>2.10.6</version>
</dependency>

2.ehcache 緩存xml文件配置

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <!-- mac 電腦, 跟 win 設置路徑有點不同 示例: path="d:/ehcache/" -->
    <diskStore path="${user.home}/Downloads/ehcache" />

    <!-- 默認緩存配置 沒有特別指定就用這個配置 -->
    <defaultCache maxElementsInMemory="10000"
                  eternal="false"
                  timeToIdleSeconds="3600" 
                  timeToLiveSeconds="3600" 
                  overflowToDisk="true" 
                  maxElementsOnDisk="10000000" 
                  diskPersistent="false" 
                  memoryStoreEvictionPolicy="LRU" 
                  diskExpiryThreadIntervalSeconds="120" />
</ehcache>

3.web.xml中shiro攔截器配置

<!-- 1. 配置 shiro 攔截器 -->
<filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
        <param-name>targetFilterLifecycle</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

4.spring文件配置

4.1基本配置

<!-- 2. 配置shiro的核心組件 SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <!-- 配置緩存 -->
    <property name="cacheManager" ref="cacheManager" />

    <!-- 自定義的 realm -->
    <property name="realm">
        <bean class="com.ogemray.shiro.MyShiroRealm">
            <!-- 自定義 realm 裏面的的密碼匹對器 -->
            <property name="credentialsMatcher">
                <bean class="com.ogemray.shiro.MyCredentialsMatcher"/>
            </property>
        </bean>
    </property>
</bean>

<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
    <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"></property>
</bean>

<!-- 3. 配置shiroFilter,配置shiro的一些基本規則信息,id必須和web.xml中配置的攔截器名字同樣(DelegatingFilterProxy 經過名字去spring容器中找注入的攔截器) -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <property name="loginUrl" value="/login.html"/> <!-- loginUrl 表示登陸頁面地址 -->
    <property name="successUrl" value="/admin/main.html"/> <!-- successUrl 表示登陸成功後跳轉的頁面 -->
    <property name="unauthorizedUrl" value="/unauthorized.html"/> <!-- 配置沒有受權跳轉頁面 -->
    <property name="filterChainDefinitions">
        <value>
            <!--/logout.action=logout --> <!-- logout 表示登出, 清空session, 這裏不須要了, 由於已經在登出對象的方法裏手動清空了 [ SecurityUtils.getSubject().logout() ] -->
            /admin/userlist*=roles[user]
            /admin/adduser*=roles[user,admin] <!-- 表示擁有 user 角色 而且 擁有 admin 角色 -->
            /admin/editRPRelation*=roles[admin],perms[user:insert,user:update,user:select,user:delete]
            /admin/editURRelation*=perms[user:select]
            /admin/**=authc
            /**=anon
        </value>
    </property>
</bean>

4.2攔截規則附解

攔截器 shiroFilter 的基本數據數據庫

  • securityManager:這個屬性是必須的
  • loginUrl:表示登陸成功後跳轉的頁面,不是必須的屬性,不輸入地址的話會自動尋找項目web項目的根目錄下的」/login.jsp」頁面
  • successUrl:登陸成功默認跳轉頁面,不配置則跳轉至」/」
  • unauthorizedUrl:沒有權限默認跳轉的頁面
  • filterChainDefinitions:指定過濾規則

clipboard.png

關於過濾器能夠參考shiro提供的枚舉類 org.apache.shiro.web.filter.mgt.DefaultFilterapache

url 模式使用 Ant 風格模式
Ant 路徑通配符支持?、、,注意通配符匹配不包括目錄分隔符 「/」:
?:匹配一個字符,如」/admin?」 將匹配 / admin1,但不匹配 / admin 或 / admin2;
:匹配零個或多個字符串,如 / admin * 將匹配 / admin、/admin123,但不匹配 / admin/1;
:匹配路徑中的零個或多個路徑,如 / admin/ 將匹配 / admin/a 或 / admin/a/b。segmentfault

5.自定義realm

public class MyShiroRealm extends AuthorizingRealm {

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("********************* 進行登陸認證 *********************");

        String username = (String) authenticationToken.getPrincipal(); //獲取提交的用戶名
        User user = userRepository.findByUsername(username);

        if (user == null) throw new UnknownAccountException("用戶不存在, 請先註冊而後再來登陸");
        if (user.getState() == 1) throw new LockedAccountException("該用戶已經被管理員禁用, 請換個帳號登陸");

        //接下來進行密碼的比對邏輯
        //參數 principal 做爲下面受權部分參數集合裏面的一部分 
        //參數 credentials 做爲後面與token裏面密碼比對基礎
        //返回值 info 做爲下面自定義密碼匹類裏面比對方法的參數
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
        return info;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("********************* 進行受權認證 *********************");

        User user = (User) principalCollection.asList().get(0);

        //獲得該用戶的全部角色和權限
        Set<String> roles = new HashSet<>();
        Set<String> permissions = new HashSet<>();

        user.getRoles().forEach(role -> {
            roles.add(role.getRoleName());
            role.getPermissions().forEach(permission -> {
                permissions.add(permission.getPermissionName());
            });
        });

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(roles);
        info.setStringPermissions(permissions);
        return info;
    }

    @Autowired
    private UserRepository userRepository;
}

6.自定義密碼匹對方案

前端密碼加密規則:ciphertext_pwd = AES.encrypt(MD5(password))
後端解密密碼規則:md5_password = AES.desEncrypt(ciphertext_pwd)
後端匹對密碼規則:(md5_password + 用戶名作鹽值) 進行 1024 次 MD5 轉換,而後與數據庫取出密碼作比對後端

public class MyCredentialsMatcher implements CredentialsMatcher {

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {

        String tokenCredentials = new String((char[])token.getCredentials()); //前端傳過來的密碼
        String accountCredentials = (String) info.getCredentials(); //數據庫查詢到的密碼

        //首先對前端傳過來的密碼進行AES解密 -> 清空 session 裏面的key
        Session session = SecurityUtils.getSubject().getSession();
        String key = (String) session.getAttribute("AESKey");

        try {
            tokenCredentials = AesEncryptUtil.desEncrypt(tokenCredentials, key, key);
        } catch (Exception e) {
            throw new IncorrectCredentialsException("可能受到重放攻擊, AES 解密失敗");
        }
        session.removeAttribute("AESKey");

        //加密方式 待加密數據 加密鹽值 加密次數
        SimpleHash simpleHash = new SimpleHash("MD5", tokenCredentials, token.getPrincipal(), 1024);
        tokenCredentials = simpleHash.toString();
        return accountCredentials.equals(tokenCredentials);
    }
}

7.登陸和註冊接口的調用

@Service("userService")
public class UserServiceImpl implements UserService {

    @Override
    public void registerUser(User user) {
        if (userRepository.existsByUsername(user.getUsername())) {
            throw new RuntimeException("用戶名已經被註冊, 請換個用戶名");
        }
        user.setState((byte) 0);
        //密碼進行加密
        SimpleHash simpleHash = new SimpleHash("MD5", user.getPassword(), user.getUsername(), 1024);
        user.setPassword(simpleHash.toString());
        userRepository.save(user);
    }

    @Override
    public void login(User user) {
        Subject currentUser = SecurityUtils.getSubject();
        if (currentUser.isAuthenticated() == false) { //沒有登陸過須要進行登陸驗證
            UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword(), false);
            currentUser.login(token);
        }
    }

    @Autowired
    private UserRepository userRepository;
}

8.多角色/權限匹對規則

從上面咱們能夠看到url地址規則匹配能夠配置多角色和多權限,當對應多個角色和權限時中間用 「,」 隔開。spring-mvc

以對應多角色爲例緩存

/admin/userlist*=roles[user]
/admin/adduser*=roles[user,admin]

當訪問第二個 /admin/adduser* 時須要同時擁有 useradmin 角色,可是有時咱們須要他們之間是或者的關係,這個時候咱們就須要自定義對應的過濾器。

以自定角色過濾器爲例

<!-- 配置shiroFilter,配置shiro的一些基本規則信息 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <property name="loginUrl" value="/login.html"/> 
    <property name="successUrl" value="/admin/main.html"/>
    <property name="unauthorizedUrl" value="/unauthorized.html"/>
    <property name="filterChainDefinitions">
        <value>
            ...
            /admin/userlist*=roles[user]
            /admin/adduser*=roles[user,admin]
            ...
        </value>
    </property>
    <property name="filters">
        <map>
            <entry key="perms">
                <bean class="com.ogemray.shiro.MyRolesAuthorizationFilter"></bean>
            </entry>
        </map>
    </property>
</bean>
public class MyRolesAuthorizationFilter extends AuthorizationFilter {

    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
        Subject subject = this.getSubject(request, response);
        String[] rolesArray = (String[])((String[])mappedValue);
        if (rolesArray != null && rolesArray.length != 0) {
            for (String role : rolesArray) {
                if (subject.hasRole(role)) return true;
            }
        }
        return false;
    }
}

啓動web項目看下自定義過濾器有沒有加進去

clipboard.png

9.shiro註解實現權限控制

在有些方法多,可是權限分的細的地方用註解要比用配置的方案來的方便

使用註解首先要在spring-mvc.xml配置文件中加入如下配置

<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean>
<!-- 開啓shiro的註解支持 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
    <!-- 必須改成true,即便用cglib方式爲Action建立代理對象。默認值爲false,使用JDK建立代理對象,會形成問題 -->
    <property name="proxyTargetClass" value="true"/>
</bean>
<!-- 使用shiro框架提供的切面類,用於建立代理對象 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager"></property>
</bean>

在spring.xml配置文件對於過濾器的配置就簡單多了
沒有那些繁雜的配置規則和跳轉頁面

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
</bean>

5個權限註解

  • RequiresAuthentication:當前Subject必須在當前session中已通過認證。
  • RequiresGuest:當前Subject能夠是「gust」身份,不須要通過認證或者在原先的session中存在記錄。
  • RequiresPermissions:當前Subject須要擁有某些特定的權限時,才能執行被該註解標註的方法。
  • RequiresRoles:當前Subject必須擁有全部指定的角色時,才能訪問被該註解標註的方法。
  • RequiresUser:當前Subject必須是應用的用戶,才能訪問或調用被該註解標註的類,實例,方法。

上面全部要求的權限和認證沒有時就會拋出對應的異常,只需在SpringMVC中寫好對應的異常截獲方法便可

示例

//表示須要認證
@RequiresAuthentication

//表示在須要認證的基礎上要同時擁有 user 和 admin 角色
@RequiresRoles(value = {"user", "admin"})

//同上
@RequiresRoles(value = {"user", "admin"}, logical = Logical.AND)

//表示在須要認證的基礎上擁有 user 或 admin 角色
@RequiresRoles(value = {"user", "admin"}, logical = Logical.OR)
相關文章
相關標籤/搜索