使用Shiro實現權限驗證

使用Shiro實現權限驗證

《使用Shiro實現權限驗證》

1. Shiro入門

ApacheShiro是一個功能強大且易於使用的Java安全框架,提供了認證,授權,加密,和會話管理。

Shiro有三大核心組件:

Subject:即當前用戶,在權限管理的應用程序裏往往需要知道誰能夠操作什麼,誰擁有操作該程序的權利,shiro中則需要通過Subject來提供基礎的當前用戶信息,Subject 不僅僅代表某個用戶,與當前應用交互的任何東西都是Subject,如網絡爬蟲等。所有的Subject都要綁定到SecurityManager上,與Subject的交互實際上是被轉換爲與SecurityManager的交互。

SecurityManager:即所有Subject的管理者,這是Shiro框架的核心組件,可以把他看做是一個Shiro框架的全局管理組件,用於調度各種Shiro框架的服務。作用類似於SpringMVC中的DispatcherServlet,用於攔截所有請求並進行處理。

Realm:Realm是用戶的信息認證器和用戶的權限人證器,我們需要自己來實現Realm來自定義的管理我們自己系統內部的權限規則。SecurityManager要驗證用戶,需要從Realm中獲取用戶。可以把Realm看做是數據源。

1.1  搭建測試工程

新建一個普通的java工程,簡單瞭解一下Shiro的API。

完整的工程目錄如下:

 

 

 

1.2  導入jar包

在pom.xml中加入jar包的配置:

<dependencies>
    <!--shiro核心類庫-->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.2.3</version>
    </dependency>
    <!--日誌工具包-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.6.1</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.6.1</version>
    </dependency>
</dependencies>

使用log4j查看日誌,需要在resources目錄下創建log4j.properties配置文件:

log4j.rootLogger=INFO, stdout, 

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n

1.3  編寫shiro配置文件

在resources目錄下編寫shiro配置文件,shiro.ini:

#用戶名=密碼,角色1,角色2...,角色n
[users]
root = secret, admin
guest = guest, guest
test = 123456, role1, role2
# -----------------------------------------------------------------------------
# Roles with assigned permissions
# roleName = perm1, perm2, ..., permN
# 角色名=權限1,權限2...權限n
# -----------------------------------------------------------------------------
[roles]
admin = *
guest = guest
role1=perm1,perm2
role2=perm3

配置文件中包含兩個部分用戶[users]和角色[roles]

用戶配置的格式是:

用戶名=密碼,角色1,角色2,...角色n

如:

test=123456,role1,role2

用戶名是test,密碼是123456,擁有兩個角色role1和role2

一個用戶可以具有多個角色。注意逗號是英文的。

角色配置的格式是:

角色名=權限1,權限2…權限n

如:

role1=perm1,perm2

角色名是role1,擁有perm1和perm2兩個權限。

1.4  測試代碼

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class ShiroTest {
    private static final transient Logger log =
            LoggerFactory.getLogger(ShiroTest.class);

    public static void main(String[] args) {
        //1. 這裏的SecurityManager是org.apache.shiro.mgt.SecurityManager
        // 而不是java.lang.SecurityManager
        // 加載配置文件
        Factory<SecurityManager> factory =
                new IniSecurityManagerFactory("classpath:shiro.ini");
        //2.解析配置文件,並且返回一些SecurityManger實例
        SecurityManager securityManager = factory.getInstance();
        //3.將SecurityManager綁定給SecurityUtils
        SecurityUtils.setSecurityManager(securityManager);
        // 安全操作,Subject是當前登錄的用戶
        Subject currentUser = SecurityUtils.getSubject();

        // 測試在應用的當前會話中設置屬性
        Session session = currentUser.getSession();
        //放進去一個key和一個value
        session.setAttribute("someKey", "aValue");
        //根據key拿到value
        String value = (String) session.getAttribute("someKey");
        if ("aValue".equals(value)) {//比較拿到的值和原來的值是否一致
            log.info("檢索到正確的值[" + value + "]");
        }
        //嘗試進行登錄用戶,如果登錄失敗了,我們進行一些處理
        if (!currentUser.isAuthenticated()) {//如果用戶沒有登錄過
            //new UsernamePasswordToken(用戶名,密碼)
            UsernamePasswordToken token =
                    new UsernamePasswordToken("test", "123456");
            token.setRememberMe(true);//是否記住用戶
            try {
                currentUser.login(token);
                //當我們獲登錄用戶之後
                log.info("用戶 [" + currentUser.getPrincipal() + "] 登陸成功");
                // 查看用戶是否有指定的角色
                if (currentUser.hasRole("admin")) {
                    log.info("您有admin角色");
                } else {
                    log.info("您沒有admin角色");
                }
                if (currentUser.hasRole("role1")) {
                    log.info("您有role1角色");
                } else {
                    log.info("您沒有role1角色");
                }

                // 查看用戶是否有某個權限
                if (currentUser.isPermitted("perm1")) {
                    log.info("您有perm1權限");
                } else {
                    log.info("您沒有perm1權限");
                }
                if (currentUser.isPermitted("guest")) {
                    log.info("您有guest權限");
                } else {
                    log.info("您沒有guest權限");
                }
                //登出
                currentUser.logout();
            } catch (UnknownAccountException uae) {
                log.info(token.getPrincipal() + "賬戶不存在");
            } catch (IncorrectCredentialsException ice) {
                log.info(token.getPrincipal() + "密碼不正確");
            } catch (LockedAccountException lae) {
                log.info(token.getPrincipal() + "用戶被鎖定了 ");
            } catch (AuthenticationException ae) {
                //無法判斷是什麼錯了
                log.info(ae.getMessage());
            }
        }
    }
}

運行結果:

 

注:rememberMe只能記住你登錄過,但不會記住你是誰以及你的權限信息。

2. Shiro+MySQL動態權限驗證

2.1  數據庫設計

在實際開發中,用戶名密碼、角色、權限需要存在數據庫中動態管理。一個簡單的Shiro+MySQL的項目需要三張表:

用戶表SHIRO_USER:

 

測試數據:

 

用戶角色表SHIRO_USER_ROLE:

 

測試數據:

 

角色權限表SHIRO_ROLE_PERMISSION:

 

測試數據:

 

2.2  工程搭建

工程的搭建與第1.1節中介紹的一樣。需要在1.2節的基礎上加上數據源的jar包和mysql的驅動包,數據源我們選用spring-jdbc:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.32</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>4.3.11.RELEASE</version>
</dependency>

2.3  配置文件

配置文件與1.3中的不同,在使用jdbc數據源的時候,不需要指定user和roles,而是在配置文件中指定數據庫連接信息和要執行的sql語句。

在resources文件夾下創建配置文件shiro-mysql.ini:

[main]
dataSource=org.springframework.jdbc.datasource.DriverManagerDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://127.0.0.1:3306/數據庫名
dataSource.username=用戶名
#如果數據庫沒有密碼,就不要寫這行
dataSource.password=你的密碼
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
#是否檢查權限
jdbcRealm.permissionsLookupEnabled = true
jdbcRealm.dataSource=$dataSource
#重寫sql語句
#根據用戶名查詢出密碼
jdbcRealm.authenticationQuery = select PASSWORD from SHIRO_USER where USER_NAME = ?
#根據用戶名查詢出角色
jdbcRealm.userRolesQuery = select ROLE_NAME from SHIRO_USER_ROLE where USER_NAME = ?
#根據角色名查詢出權限
jdbcRealm.permissionsQuery = select PERM_NAME from SHIRO_ROLE_PERMISSION WHERE ROLE_NAME = ?
securityManager.realms=$jdbcRealm

注意sql語句,每次只查詢一個shiro要求查詢的字段,如果寫select *就會報錯了。

ini配置文件要求必須是key=value的形式,如果有些人沒有設置數據庫的密碼,就不要寫對應的配置。只寫”dataSource.password=”等號右面沒有值會報錯。

2.4  測試代碼

測試代碼與1.4中類似,只是讀取的配置文件不同:

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ShiroMySqlTest {
    private static final transient Logger log =
            LoggerFactory.getLogger(ShiroMySqlTest.class);
    public static void main(String[] args) {
        Factory<SecurityManager> factory =
                new IniSecurityManagerFactory("classpath:shiro-mysql.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        Subject currentUser = SecurityUtils.getSubject();
        UsernamePasswordToken token =
                new UsernamePasswordToken("[email protected]", "123456");
        token.setRememberMe(true);//是否記住用戶
        try {
            currentUser.login(token);
            //當我們獲登錄用戶之後
            log.info("用戶 [" + currentUser.getPrincipal() + "] 登陸成功");
            //查看用戶是否有角色
            if (currentUser.hasRole("admin")) {
                log.info("您有admin角色");
            } else {
                log.info("您沒有admin角色");
            }
            if (currentUser.hasRole("test")) {
                log.info("您有test角色");
            } else {
                log.info("您沒有test角色");
            }
            // 查看用戶是否有某個權限
            if (currentUser.isPermitted("perm1")) {
                log.info("您有perm1權限");
            } else {
                log.info("您沒有perm1權限");
            }
            if (currentUser.isPermitted("guest")) {
                log.info("您有guest權限");
            } else {
                log.info("您沒有guest權限");
            }
            //登出
            currentUser.logout();
        } catch (UnknownAccountException uae) {
            log.info(token.getPrincipal() + "賬戶不存在");
        } catch (IncorrectCredentialsException ice) {
            log.info(token.getPrincipal() + "密碼不正確");
        } catch (LockedAccountException lae) {
            log.info(token.getPrincipal() + "用戶被鎖定了 ");
        } catch (AuthenticationException ae) {
            //無法判斷是什麼錯了
            log.info(ae.getMessage());
        }
    }
}

運行結果:

 

3. Web中使用Shiro

本小節使用SpringJDBC、SpringMVC、Shiro實現一個簡單的權限驗證。實際開發中數據層可以使用Hibernate、Mybatis等數據層框架。本節的示例只是介紹Web工程中使用Shiro驗證的基本流程,完整的權限系統會比這個示例複雜的多。爲了簡化代碼,這個示例中沒有寫接口。

這個測試項目用第2小節中的數據進行測試。

工程中定義幾個地址:

/gologin.html 不需要權限驗證就可以訪問

/login.html 不需要驗證就可以訪問

/doadd.html 要有perm1和perm2權限纔可以訪問,訪問成功後頁面顯示add

/doget.html 要有admin權限纔可以訪問,訪問成功後頁面顯示get

/doupdate.html 要有perm1權限才能訪問,訪問成功後頁面顯示update

/dodel.html 要有perm2權限纔可以訪問,訪問成功後頁面顯示del

第2小節的測試數據中,[email protected]這個用戶由perm1和perm2兩個權限,沒有admin權限,所以/doget.html訪問不到,會顯示error。

3.1  創建web工程

 

 

完整的工程目錄如下:

 

3.2  導入jar包

<!--shiro核心類庫-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.2.3</version>
</dependency>
<!--日誌工具包-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.6.1</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.6.1</version>
</dependency>
<!--mysql驅動-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.32</version>
</dependency>
<!--spring相關包-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>4.3.11.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.11.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>4.3.11.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>

3.3  DAO層

package dao;

import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

public class ShiroDAO {
    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    /**
     * 根據用戶名查詢密碼
     */
    public String getPasswordByUserName(String username) {
        String sql = "select PASSWORD from SHIRO_USER where USER_NAME = ?";
        String password = jdbcTemplate.queryForObject(sql, String.class, username);
        return password;
    }

    /**
     * 查詢當前用戶對應的權限
     */
    public List<String> getPermissionByUserName(String username) {
        String sql = "select P.PERM_NAME from SHIRO_ROLE_PERMISSION P inner join SHIRO_USER_ROLE R on P.ROLE_NAME=R.ROLE_NAME where R.USER_NAME = ?";
        List<String> perms = jdbcTemplate.queryForList(sql, String.class, username);
        return perms;
    }
}

3.4  Service層

package service;

import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import dao.ShiroDAO;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;

import java.util.List;

public class ShiroService {
    private ShiroDAO shiroDAO;

    public void setShiroDAO(ShiroDAO shiroDAO) {
        this.shiroDAO = shiroDAO;
    }

    /**
     * 登錄
     */
    public void doLogin(String username, String password) throws Exception {
        Subject currentUser = SecurityUtils.getSubject();
        if (!currentUser.isAuthenticated()) {
            UsernamePasswordToken token =
                    new UsernamePasswordToken(username, password);
            token.setRememberMe(true);//是否記住用戶
            try {
                currentUser.login(token);//執行登錄
            } catch (UnknownAccountException uae) {
                throw new Exception("賬戶不存在");
            } catch (IncorrectCredentialsException ice) {
                throw new Exception("密碼不正確");
            } catch (LockedAccountException lae) {
                throw new Exception("用戶被鎖定了 ");
            } catch (AuthenticationException ae) {
                ae.printStackTrace();
                throw new Exception("未知錯誤");
            }
        }
    }

    /**
     * 根據用戶名查詢密碼
     */
    public String getPasswordByUserName(String username) {
        return shiroDAO.getPasswordByUserName(username);
    }

    /**
     * 查詢用戶所有權限
     */
    public List<String> getPermissionByUserName(String username) {
        return shiroDAO.getPermissionByUserName(username);
    }
}

注:rememberMe後瀏覽器裏會生成一個cookie:

 

如果訪問的路徑,要求權限是user,所有使用過rememberMe的用戶就都可以訪問。但是它只是記錄你登錄過,不會記住你是誰以及你的權限信息。

3.5  Controller

package controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import service.ShiroService;

@Controller
public class ShiroController {
    @Autowired
    private ShiroService shiroService;

    @RequestMapping("/gologin.html")
    public String goLogin() {
        return "/login.jsp";
    }

    @RequestMapping("/login.html")
    public ModelAndView login(String username, String password) {
        try {
            shiroService.doLogin(username, password);
        } catch (Exception e) {
            return new ModelAndView("/error.jsp", "msg", e.getMessage());
        }
        return new ModelAndView("/index.jsp");
    }
    @RequestMapping("/logout.html")
    public String logout() {
        Subject currentUser = SecurityUtils.getSubject();
        currentUser.logout();
        return "/login.jsp";
    }
    /**
     * 模擬不同的請求,在配置文件中對請求進行權限攔截
     */
    @RequestMapping("/do{act}.html")
    public ModelAndView get(@PathVariable String act) {
        //簡化代碼,省略數據庫操作
        //通過頁面上顯示的信息查看請求是否被攔截
        return new ModelAndView("/page.jsp", "page", act);
    }
}

3.6  自定義Realm

package util;

import org.apache.shiro.authc.*;
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 service.ShiroService;

import java.util.List;

public class MyShiroRealm extends AuthorizingRealm {
    private ShiroService shiroService;

    public void setShiroService(ShiroService shiroService) {
        this.shiroService = shiroService;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo
                 (PrincipalCollection principalCollection) {
        //根據自己的需求編寫獲取授權信息,這裏簡化代碼獲取了用戶對應的所有權限
        String username = 
        (String) principalCollection.fromRealm(getName()).iterator().next();
        if (username != null) {
            List<String> perms = shiroService.getPermissionByUserName(username);
            if (perms != null && !perms.isEmpty()) {
                SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
                for (String each : perms) {
                    //將權限資源添加到用戶信息中
                    info.addStringPermission(each);
                }
                return info;
            }
        }
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo
        (AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        // 通過表單接收的用戶名,調用currentUser.login的時候執行
        String username = token.getUsername();
        if (username != null && !"".equals(username)) {
            //查詢密碼
            String password = shiroService.getPasswordByUserName(username);
            if (password != null) {
                return new SimpleAuthenticationInfo(username, password, getName());
            }
        }
        return null;
    }
}

3.7  配置文件

resources目錄下的db.properties用於存放數據庫配置信息:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/數據庫?characterEncoding=utf-8
jdbc.username=用戶名
jdbc.password=密碼

log4j.properties

log4j.rootLogger=INFO, stdout, 

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n

WEB-INF目錄下的applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">

    <!--讀取配置文件-->
    <context:property-placeholder location="classpath:db.properties" ignore-unresolvable="true"/>
    <!--從配置文件中獲取數據源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <!-- 配置Jdbc模板 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <bean id="shiroDAO" class="dao.ShiroDAO">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
    <bean id="shiroService" class="service.ShiroService">
        <property name="shiroDAO" ref="shiroDAO"/>
    </bean>
    <bean id="myShiroRealm" class="util.MyShiroRealm">
        <property name="shiroService" ref="shiroService"/>
    </bean>

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="myShiroRealm"/>
    </bean>
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <!--去登錄的地址-->
        <property name="loginUrl" value="/gologin.html"/>
        <!--登錄成功的跳轉地址-->
        <property name="successUrl" value="/index.html"/>
        <!--驗證失敗的跳轉地址-->
        <property name="unauthorizedUrl" value="/error.jsp"/>
        <!--定義過濾的規則-->
        <!--複雜的系統中,url和權限都可以從數據庫中讀取-->
        <!--anon是不需要驗證,authc時需要驗證,perms[admin]代表要admin權限-->
        <property name="filterChainDefinitions">
            <value>
                /gologin.html = anon
                /login.html = anon
                /doadd.html = authc, perms[perm1,perm2]
                /doget.html = authc, perms[admin]
                /doupdate.html = authc, perms[perm1]
                /dodel.html = authc, perms[perm2]
            </value>
        </property>
    </bean>
</beans>

shiro過濾器過濾屬性含義:

securityManager:這個屬性是必須的。

loginUrl :沒有登錄的用戶請求需要登錄的頁面時自動跳轉到登錄頁面,不是必須的屬性,不輸入地址的話會自動尋找項目web項目的根目錄下的”/login.jsp”頁面。

successUrl :登錄成功默認跳轉頁面,不配置則跳轉至”/”。如果登陸前點擊的一個需要登錄的頁面,則在登錄自動跳轉到那個需要登錄的頁面。不跳轉到此。

unauthorizedUrl :沒有權限默認跳轉的頁面

 

其權限過濾器及配置釋義:

anon:

例子/admins/**=anon 沒有參數,表示可以匿名使用。

authc:

例如/admins/user/**=authc表示需要認證(登錄)才能使用,沒有參數

roles(角色):

例子/admins/user/**=roles[admin],參數可以寫多個,參數之間用逗號分割,當有多個參數時,例如admins/user/**=roles["admin,guest"],每個參數通過纔算通過,相當於hasAllRoles()方法。

perms(權限)

例子/admins/user/**=perms[add],參數可以寫多個,例如/admins/user/**=perms["add, modify"],當有多個參數時必須每個參數都通過才通過,想當於isPermitedAll()方法。

rest:

例子/admins/user/**=rest[user],根據請求的方法,相當於/admins/user/**=perms[user:method] ,其中method爲post,get,delete等。

port:

例子/admins/user/**=port[8081],當請求的url的端口不是8081是跳轉到schemal://serverName:8081?queryString,其中schmal是協議http或https等,serverName是你訪問的host,8081是url配置裏port的端口,queryString

是你訪問的url裏的?後面的參數。

authcBasic:

例如/admins/user/**=authcBasic沒有參數.表示httpBasic認證

ssl:

例子/admins/user/**=ssl沒有參數,表示安全的url請求,協議爲https

user:

例如/admins/user/**=user沒有參數表示必須存在用戶,當登入操作時不做檢查

 

springMVC-servlet.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">
    <context:annotation-config />
    <!-- 啓動自動掃描 -->
    <context:component-scan base-package="controller">
        <!-- 制定掃包規則 ,只掃描使用@Controller註解的JAVA類 -->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
</beans>

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
          http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <display-name>Archetype Created Web Application</display-name>
    <!--配置listener,在啓動Web容器的時候加載Spring的配置-->
    <listener>
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!--歡迎頁面-->
    <welcome-file-list>
        <welcome-file>gologin.html</welcome-file>
    </welcome-file-list>

    <!--配置DispatcherServlet-->
    <servlet>
        <servlet-name>springMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>springMVC</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

    <!-- 配置shiro的核心攔截器 -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>*.html</url-pattern>
    </filter-mapping>
</web-app>

3.8  頁面

頁面沒有添加樣式,將這些代碼寫在body中即可。

login.jsp

<form action="/login.html" method="post">
    username:<input type="text" name="username"/><br/>
    password:<input type="password" name="password"/><br/>
    <input type="submit" value="login"/>
</form>

index.jsp

<a href="/doadd.html" target="_blank">add</a><br/>
<a href="/dodel.html" target="_blank">del</a><br/>
<a href="/doupdate.html" target="_blank">update</a><br/>
<a href="/doget.html" target="_blank">get</a><br/>

page.jsp

<h2>${page}</h2>

error.jsp

<h2>ERROR:${msg}</h2>

測試結果:

 

登錄成功:

 

點擊add、del、update可以看到對應的頁面:

 

 

 

點擊get會報錯,並且直接跳轉到error.jsp,因爲沒有權限:

 

3.9  動態配置過濾規則

在實際開發中,url和對應的訪問權限可能需要從數據庫中讀取,我們可以定義一個工具類從數據庫中讀取訪問權限並傳遞給Shiro。

package util;

import org.apache.shiro.config.Ini;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.config.IniFilterChainResolverFactory;
import org.springframework.beans.factory.FactoryBean;

import java.text.MessageFormat;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

public class MyChainDefinitions implements FactoryBean<Ini.Section> {
    public static final String PREMISSION_STRING = "perms[{0}]";
    public static final String ROLE_STRING = "roles[{0}]";
    private String filterChainDefinitions;

    public void setFilterChainDefinitions(String filterChainDefinitions) {
        this.filterChainDefinitions = filterChainDefinitions;
    }


    @Override
    public Ini.Section getObject() throws Exception {
        //urls可以從數據庫查出來,此處省略代碼,直接模擬幾條數據
        Set<String> urls = new LinkedHashSet<>();
        urls.add("/dotest1.html");
        urls.add("/dotest2.html");
        //每個url對應的權限也可以從數據庫中查出來,這裏模擬幾條數據
        Map<String, String[]> permsMap = new HashMap<>();
        permsMap.put("/dotest1.html", new String[]{"perm1", "admin"});
        permsMap.put("/dotest2.html", new String[]{"perm1"});

        //加載配置默認的過濾鏈
        Ini ini = new Ini();
        ini.load(filterChainDefinitions);
        Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS);
        if (CollectionUtils.isEmpty(section)) {
            section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
        }
        for (String url : urls) {
            String[] perms = permsMap.get(url);
            StringBuilder permFilters = new StringBuilder();
            for (int i = 0; i < perms.length; i++) {
                permFilters.append(perms[i]).append(",");
            }
            //去掉末尾的逗號
            String str = permFilters.substring(0, permFilters.length() - 1);
            //生成結果如:/dotest1.html = authc, perms[admin]
            section.put(url, MessageFormat.format(PREMISSION_STRING, str));
        }
        return section;
    }

    @Override
    public Class<?> getObjectType() {
        return this.getClass();
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}

注意section中是以Map存放的數據,所以放入相同的key,後放的會覆蓋先放的數據。

修改spring的配置:

<!--聲明自定義規則-->
<bean id="myChainDefinitions" class="util.MyChainDefinitions">
    <!--靜態的條件-->
    <property name="filterChainDefinitions">
        <value>
            /gologin.html = anon
            /login.html = anon
            /doadd.html = authc, perms[perm1,perm2]
            /doget.html = authc, perms[admin]
            /doupdate.html = authc, perms[perm1]
            /dodel.html = authc, perms[perm2]
            /logout.html=user
        </value>
    </property>
</bean>

將shiroFilter中的filterChainDefinitions替換掉:

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <property name="loginUrl" value="/gologin.html"/>
    <property name="successUrl" value="/index.html"/>
    <property name="unauthorizedUrl" value="/error.jsp"/>
    <!--定義過濾的規則-->
    <property name="filterChainDefinitionMap" ref="myChainDefinitions"/>
</bean>

可以訪問/dotest1.html和/dotest2.html查看攔截效果。

3.10     重寫過濾器

/doadd.html = authc,perms[perm1,perm2]

shiro默認的攔截是要滿足所有的條件,但有時我們只要滿足其中的一個,用於擁有perm1或perm2任何一個條件就可以訪問/doadd.html。這時我們可以重寫過濾器,將and變成or

package util;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;

public class MyShiroPermFilter extends AuthorizationFilter {
    @Override
    protected boolean isAccessAllowed
            (ServletRequest req, ServletResponse resp, Object mappedValue) 
                 throws Exception {
        Subject subject = getSubject(req, resp);
        String[] permsArray = (String[]) mappedValue;

        if (permsArray == null || permsArray.length == 0) { //沒有權限限制
            return true;
        }
        for (int i = 0; i < permsArray.length; i++) {
            //如果是角色,就是subject.hasRole()
            //若當前用戶是permsArray中的任何一個,則有權限訪問
            if (subject.isPermitted(permsArray[i])) {
                return true;
            }
        }
        return false;
    }
}

此處需要引入servlet的jar包:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
</dependency>

在spring中的配置,在id=”shiroFilter”的bean中加入過濾攔截:

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <property name="loginUrl" value="/gologin.html"/>
    <property name="successUrl" value="/index.html"/>
    <property name="unauthorizedUrl" value="/error.jsp"/>
    <property name="filterChainDefinitionMap" ref="myChainDefinitions"/>
    <!--修改後的過濾規則,從and變成or-->
    <property name="filters">
        <map>
            <entry key="perms">
                <bean class="util.MyShiroPermFilter"/>
            </entry>
        </map>
    </property>
</bean>

 

3.11     rememberMe屬性

rememberMe可以在瀏覽器中設置cookie,在spring配置中可以設置cookie的屬性,如過期時間、cookie名字、加密的祕鑰等:

<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
    <constructor-arg value="rememberMeShiro"/><!-- 瀏覽器中cookie的名字 -->
    <property name="httpOnly" value="true"/><!--document對象中就看不到cookie了-->
    <property name="maxAge" value="2592000"/><!-- 30天 -->
</bean>

<!-- rememberMe管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
    <!--祕鑰要16位,24位或32位的Base64。這個解密後是1234567890abcdef-->
    <property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('MTIzNDU2Nzg5MGFiY2RlZg==')}"/>
    <property name="cookie" ref="rememberMeCookie"/>
</bean>

在securityManager中加入rememberMe中加入配置:

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="myShiroRealm"/>
    <!--加入rememberMe的設置-->
    <property name="rememberMeManager" ref="rememberMeManager"/>
</bean>

 

 

HttpOnly屬性:

瀏覽器中通過document.cookie可以獲取cookie屬性,設置了HttpOnly=true,在腳本中就不能的到cookie了。可以避免cookie被盜用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章