Shiro和Spring MVC、Mybatis整合教程

前言

Apache Shiro 是Java的安全框架,提供了認證(Authentication)、受權(Authorization)、會話(Session)管理、加密(Cryptography)等功能,且Shiro與Spring Security等安全框架相比具備簡單性、靈活性、支持細粒度鑑權、支持一級緩存等,還有Shiro不跟任何容器(Tomcat等)和框架(Sping等)捆綁,能夠獨立運行,這也造就了Shiro不只僅是能夠用在Java EE上還能夠用在Java SE上html

Shiro四大功能

在開始以前,首先了解一下Shiro的四大功能,俗話說「知己知彼百戰不殆」。 前端

image

認證

認證就是用戶訪問系統的時候,系統要驗證用戶身份的合法性,好比咱們一般所說的「登陸」就是認證的一種方式,只有登陸成功了以後咱們才能訪問相應的資源。在Shiro中,咱們能夠將用戶理解爲Subject主體,在用戶身份認證的時候,用戶須要提供能證實他身份的信息,如用戶名、密碼等,用戶所提供的這些用戶名、密碼則對應Shiro中的Principal、 Credentials,即在Subject進行身份認證的時候,須要提供相應的Principal、 Credentials,對應的代碼以下:java

UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
subject.login(token); //提交認證
複製代碼

咱們知道Http協議是無狀態的,因此用戶認證成功後怎麼才能保持認證成功的狀態呢?若是是咱們開發的話通常都是登陸成功後將Session儲存在服務器,而後再將Session返回給用戶,以後的請求用戶都將這個Session帶上,而後服務器根據用戶請求攜帶的Session和服務器儲存的Session進行比較來判斷用戶是否已認證。可是使用Shiro後,Shiro已經幫咱們作好這個了(下面介紹的會話管理),是否是feel爽~mysql

受權

受權能夠理解爲訪問控制,在用戶認證(登陸)成功以後,系統對用戶訪問資源的權限進行控制,即肯定什麼用戶能訪問什麼資源,如普通用戶不能訪問後臺,可是管理員能夠。在這裏咱們還須要認識幾個概念,資源(Resource)、角色(Role)、權限(Permission),上面提到的Subject主體能夠有多個角色,每一個角色又對應多個資源的多個權限,這種基於資源的訪問控制能夠實現細粒度的權限。對主體設置角色、權限的代碼以下:git

SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//  添加用戶的角色
authorizationInfo.addRoles(roleIdList);
//  添加用戶的權限
authorizationInfo.addStringPermissions(resourceIdList);
複製代碼

若是要實現這樣的受權功能,咱們一定須要設計一個用戶組、權限,給每一個方法或者URL加上判斷,是否當前登陸的用戶知足條件。可是使用Shiro後,Shiro也幫咱們幫這些都作好了github

會話管理

會話管理的會話即Session,所謂會話,即用戶訪問應用時保持的鏈接關係,在屢次交互中應用可以識別出當前訪問的用戶是誰,且能夠在屢次交互中保存一些數據。如訪問一些網站時登陸成功後,網站能夠記住用戶,且在退出以前均可以識別當前用戶是誰。在Shiro中,與用戶有關的一切信息均可以經過Shiro的接口得到,和用戶的會話Session也都由Shiro管理。如實現「記住我」或者「下次自動登陸」的功能,若是要本身去開發的話,估計又得話很多時間。可是使用Shiro後,Shiro也幫咱們幫這些都作好了web

加密

用戶密碼明文保存是否是安全,應不該該MD5加密,是否是應該加鹽,又要寫密碼加密的代碼。 這些Shiro已經幫你作好了spring

Shiro三大核心概念

從總體概念上理解,Shiro的體系架構有三個主要的概念,Subject(主體),Security Manager (安全管理器)和 Realms (域)。 sql

image

Subject主體

主體是當前正在操做的用戶的特定數據集合。主體能夠是一我的,也能夠表明第三方服務,守護進程,定時任務或相似的東西,也就是幾乎全部與該應用進行交互的事物。全部Subject都綁定到SecurityManager,與Subject的全部交互都會委託給 SecurityManager,能夠把 Subject 認爲是一個門面,SecurityManager 纔是實際的執行者。數據庫

Security Manager安全管理器

安全管理器,即全部與安全有關的操做都會與SecurityManager交互,且它管理着全部Subject能夠看出它是Shiro的核心,它負責與後邊介紹的其餘組件進行交互,若是學習過 SpringMVC,你能夠把它當作DispatcherServlet前端控制器,通常來講,一個應用只會存在一個SecurityManager實例

Realms域

域,Shiro從Realm獲取安全數據(如用戶、角色、權限),就是說SecurityManager要驗證用戶身份,那麼它須要從Realm獲取相應的用戶進行比較以肯定用戶身份是否合法,也須要從Realm獲得用戶相應的角色 / 權限進行驗證用戶是否能進行操做,即Realms做爲Shiro與應用程序安全數據之間的「橋樑」。從這個意義上講,Realm實質上是一個安全相關的DAO,它封裝了數據源的鏈接細節,並在須要時將相關數據提供給Shiro。其中Realm有2個方法,doGetAuthenticationInfo用來認證,doGetAuthorizationInfo用來受權。

Spring、Spring MVC、Mybatis、Shiro集成

項目目錄

image

添加依賴包

pox.xml:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>shiro</groupId>
  <artifactId>shiro</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>shiro Maven Webapp</name>
  <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!--Sping核心依賴--> <!-- https://mvnrepository.com/artifact/org.springframework/spring-core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.1.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-web --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.1.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context-support --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>5.1.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.1.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-test --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.1.3.RELEASE</version> <scope>test</scope> </dependency> <!--Mybatis依賴--> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.2</version> </dependency> <!--MySQL鏈接驅動--> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.13</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-web --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.4.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> </dependencies> <build> <finalName>shiro</finalName> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </pluginManagement> </build> </project> 複製代碼

建立數據庫和實體類

爲了減小篇幅,只作簡單介紹,詳情能夠查看源碼,數據庫文件在本項目根目錄。

image

  • resource表:資源表,有idname兩個字段,分別對應資源id和權限。
  • role表:角色表,有idname兩個字段,分別對應角色id和角色名。
  • role_resource表:角色資源權限表,有idroleidresid三個字段,分別對應自增id、角色id和資源id。
  • user表:用戶表,有idusernamepassword三個字段,分別對應自增id、用戶名和密碼。
  • user_role表:有iduidrid三個字段,分別對應自增id、用戶id、和角色id。

Dao層

AccountDao.java:

public interface AccountDao {
    User findUserByUsername(String username);
    List<Role> findRoleByUserId(int id);
    List<Resource> findResourceByUserId(int id);
}
複製代碼

service層

AccountService.java:

public interface AccountService {
    User findUserByUsername(String username);
    List<Role> findRoleByUserId(int id);
    List<Resource> findResourceByUserId(int id);
    boolean login(User user);
}
複製代碼

AccountServiceImpl.java:

package com.shiro.service.impl;

import com.shiro.dao.AccountDao;
import com.shiro.entity.Role;
import com.shiro.entity.User;
import com.shiro.service.AccountService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @program: shiro
 * @description:
 * @author: Xue 8
 * @create: 2019-02-01 15:37
 **/
@Service
public class AccountServiceImpl implements AccountService {
    @Resource
    AccountDao accountDao;

    /**
     * @description: 根據用戶名查找用戶信息
     * @param: [username]
     * @return: com.shiro.entity.User
     * @author: Xue 8
     * @date: 2019/2/1
     */
    @Override
    public User findUserByUsername(String username) {
        return accountDao.findUserByUsername(username);
    }

    @Override
    public List<Role> findRoleByUserId(int id) {
        return accountDao.findRoleByUserId(id);
    }

    @Override
    public List<com.shiro.entity.Resource> findResourceByUserId(int id) {
        return accountDao.findResourceByUserId(id);
    }

    public boolean login(User user){
//        獲取當前用戶對象subject
        Subject subject = SecurityUtils.getSubject();
        System.out.println("subject:" + subject.toString());
//        建立用戶名/密碼身份證驗證Token
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
        System.out.println("token" + token);
        try {
            subject.login(token);
            System.out.println("登陸成功");
            return true;
        } catch (Exception e) {
            System.out.println("登陸失敗" + e);
            return false;
        }
    }
}

複製代碼

MyRealm.java

package com.shiro.service.impl;

import com.shiro.entity.Role;
import com.shiro.entity.User;
import com.shiro.service.AccountService;
import org.apache.shiro.SecurityUtils;
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.session.Session;
import org.apache.shiro.subject.PrincipalCollection;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * @program: shiro
 * @description:
 * @author: Xue 8
 * @create: 2019-02-01 15:16
 **/

public class MyRealm extends AuthorizingRealm {
    @Resource
    AccountService accountService;

    /**
      * 身份認證的方法 認證成功獲取身份驗證信息
     * 這裏最主要的是user.login(token);這裏有一個參數token,這個token就是用戶輸入的用戶密碼,
     * 咱們平時可能會用一個對象user來封裝用戶名和密碼,shiro用的是token,這個是控制層的代碼,還沒到shiro,
     * 當調用user.login(token)後,就交給shiro去處理了,接下shiro應該是去token中取出用戶名,而後根據用戶去查數據庫,
     * 把數據庫中的密碼查出來。這部分代碼通常都是要求咱們自定義實現,自定義一個realm,重寫doGetAuthenticationInfo方法
    **/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//        獲取用戶輸入的用戶名和密碼
//        實際上這個token是從UserResource面currentUser.login(token)傳過來的
//        兩個token的引用都是同樣的
        String username = (String) authenticationToken.getPrincipal();
//        密碼要用字符數組來接受 由於UsernamePasswordToken(username, password) 儲存密碼的時候是將字符串類型轉成字符數組的 查看源碼能夠看出
        String password = new String((char[]) authenticationToken.getCredentials());
//        調用service 根據用戶名查詢用戶信息
        User user = accountService.findUserByUsername(username);
//        String password = user.getPassword();
//        判斷用戶是否存在 不存在則拋出異常
        if (user != null) {
//            判斷用戶密碼是否匹配 匹配則不匹配則拋出異常
            if (user.getPassword().equals(password)) {
//                登陸成功 把用戶信息儲存在Session中
                Session session = SecurityUtils.getSubject().getSession();
                session.setAttribute("userSession", user);
                session.setAttribute("userSessionId", user.getId());
//                認證成功 返回一個AuthenticationInfo的實現
                return new SimpleAuthenticationInfo(username, password, getName());
            } else {
                System.out.println("密碼不正確");
                throw new IncorrectCredentialsException();
            }
        } else {
            System.out.println("帳號不存在");
            throw new UnknownAccountException();
        }
    }

    /**
      * 受權的方法
     * 一、subject.hasRole(「admin」) 或 subject.isPermitted(「admin」):本身去調用這個是否有什麼角色或者是否有什麼權限的時候;
     *
     * 二、@RequiresRoles("admin") :在方法上加註解的時候;
     *
     * 三、[@shiro.hasPermission name = "admin"][/@shiro.hasPermission]:在頁面上加shiro標籤的時候,即進這個頁面的時候掃描到有這個標籤的時候。
     * 四、xml配置權限的時候也會走
    **/
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("受權");
//        從principalCollection獲取用戶信息
//        若是doGetAuthenticationInfo(user,password,getName()); 傳入的是user類型的數據 那這裏getPrimaryPrincipal獲取到的也是user類型的數據
        String username = (String) principalCollection.getPrimaryPrincipal();
        User user = accountService.findUserByUsername(username);
//        獲取該用戶的全部角色
        List<Role> roleList = accountService.findRoleByUserId(user.getId());
//        將角色的id放到一個String列表中 由於authorizationInfo.addRoles()方法只支持角色的String列表或者單個角色String
        List<String> roleIdList = new ArrayList<String>();
        for (Role role:roleList) {
            roleIdList.add(role.getName());
        }
//        獲取該用戶的全部權限
        List<com.shiro.entity.Resource> resourceList = accountService.findResourceByUserId(user.getId());
        List<String> resourceIdList = new ArrayList<String>();
//        將權限id放到一個String列表中 由於authorizationInfo.addRoles()方法只支持角色的String列表或者單個角色String
        for (com.shiro.entity.Resource resource:resourceList) {
            resourceIdList.add(resource.getName());
        }
        System.out.println("受權11");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//        添加用戶的角色
        authorizationInfo.addRoles(roleIdList);
//        添加用戶的權限
        authorizationInfo.addStringPermissions(resourceIdList);
        return authorizationInfo;
    }
}

複製代碼

controller層

AccountController.java

package com.shiro.controller;

import com.shiro.entity.User;
import com.shiro.service.AccountService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

/**
 * @program: shiro
 * @description:
 * @author: Xue 8
 * @create: 2019-02-01 13:14
 **/
@Controller
public class AccountController {
    @Resource
    AccountService accountService;
    @Resource
    HttpServletRequest servletRequest;

    @RequestMapping(value = "/home")
    public String  home(){
        return "home";
    }

    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String  getLogin(){
        return "login";
    }

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String doLogin(@RequestParam(value = "username") String username,
                        @RequestParam(value = "password") String password){
        User user = new User();
        user.setUsername(username);
        user.setPassword(password);
        if (accountService.login(user)) {
            return "/home";
        }
        return "/login";
    }
}

複製代碼

GET方法訪問/login的時候,會出現登陸頁面,輸入帳號密碼點擊登陸數據將以POST方式提交給/login,若是帳號密碼匹配返回/home的頁面,不然返回/login的頁面。/home頁面只有在登陸且有權限的狀況下才能夠訪問,未登陸狀況下訪問會轉跳/login頁面,這個在Shiro的配置文件裏面配置。

配置文件

applicationContext.xml:配置Spring

<?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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--開啓掃描註冊-->
    <context:component-scan base-package="com.shiro"></context:component-scan>

    <!--讀取properties配置-->
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:jdbcConfig.properties"></property>
    </bean>

    <!--配置數據源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${driverClassName}"></property>
        <property name="username" value="${username}"></property>
        <property name="password" value="${password}"></property>
        <property name="url" value="${url}"></property>
    </bean>

    <!--配置session工廠-->
    <bean id="sessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
        <property name="mapperLocations" value="classpath:mapping/*.xml"></property>
    </bean>

    <!--配置掃描mapping-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.shiro.dao"></property>
        <property name="sqlSessionFactoryBeanName" value="sessionFactoryBean"></property>
    </bean>
</beans>
複製代碼

spring-shiro.xml:配置Shiro

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

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="myRealm"></property>
    </bean>

    <bean id="myRealm" class="com.shiro.service.impl.MyRealm">
        <!--關閉權限緩存 否則doGetAuthorizationInfo受權方法不執行-->
        <property name="authorizationCachingEnabled" value="false"/>
    </bean>

    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"></property>
        <property name="successUrl" value="/success"></property>
        <!--登陸頁面-->
        <property name="loginUrl" value="/login"></property> 
        <property name="filterChainDefinitions">
            <value>
                <!--配置`/home`只有擁有`admin`角色的用戶才能夠訪問-->
                /home = authc,roles[admin]
            </value>
        </property>
    </bean>

</beans>
複製代碼

這裏須要注意的是 在配置Realm的時候,若是沒用上緩存功能的話,須要將緩存關掉,否則進不到doGetAuthorizationInfo受權方法。

測試

打開http://localhost:8080/login登陸頁面,填寫正確用戶名和密碼登陸

image
登陸成功 轉跳成功頁面
image
清除瀏覽器cookie以後(未登陸狀態),打開 http://localhost:8080/home頁面,自動轉跳到了 /login登陸頁面(即沒有權限訪問),登陸帳戶,再次打開 http://localhost:8080/home頁面便可正常訪問。

總結

這是我學習Shiro時候根據本身的狀況記錄下來的,但願對你們有所幫助,若是你們想對Shiro進一步研究的話,推薦你們看張開濤老師的《跟我學Shiro》,最後附上本項目的Github地址:github.com/xue8/Java-D…

原文地址:ddnd.cn/2019/02/02/…

相關文章
相關標籤/搜索