shiro註解使用

shiro註解使用

 

前言

相比有作過企業級開發的童鞋應該都有作過權限安全之類的功能吧,最早開始我採用的是建用戶表,角色表,權限表,以後在攔截器中對每個請求進行攔截,再到數據庫中進行查詢看當前用戶是否有該權限,這樣的設計能知足大多數中小型系統的需求。不過這篇所介紹的Shiro能知足以前的全部需求,而且使用簡單,安全性高,並且如今愈來愈的多企業都在使用Shiro,這應該是一個收入的你的技能庫。html

建立自定義MyRealm

有關Shiro的基礎知識我這裏就不過多介紹了,直接來乾貨,到最後會整合Spring來進行權限驗證。
首先在使用Shiro的時候咱們要考慮在什麼樣的環境下使用:前端

  • 登陸的驗證
  • 對指定角色的驗證
  • 對URL的驗證

基本上咱們也就這三個需求,因此同時咱們也須要三個方法:java

  1. findUserByUserName(String username)根據username查詢用戶,以後Shiro會根據查詢出來的User的密碼來和提交上來的密碼進行比對。
  2. findRoles(String username)根據username查詢該用戶的全部角色,用於角色驗證。
  3. findPermissions(String username)根據username查詢他所擁有的權限信息,用於權限判斷。

下面我貼一下個人mapper代碼(PS:該項目依然是基於以前的SSM,不太清楚整合的請看SSM一)。git

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.crossoverJie.dao.T_userDao" >
    <resultMap id="BaseResultMap" type="com.crossoverJie.pojo.T_user" >
        <result property="id" column="id"/>
        <result property="userName" column="userName"/>
        <result property="password" column="password"/>
        <result property="roleId" column="roleId"/>
    </resultMap>
    <sql id="Base_Column_List" >
        id, username, password,roleId
    </sql>

    <select id="findUserByUsername" parameterType="String" resultMap="BaseResultMap">
        select <include refid="Base_Column_List"/>
        from t_user where userName=#{userName}
    </select>

    <select id="findRoles" parameterType="String" resultType="String">
        select r.roleName from t_user u,t_role r where u.roleId=r.id and u.userName=#{userName}
    </select>

    <select id="findPermissions" parameterType="String" resultType="String">
        select p.permissionName from t_user u,t_role r,t_permission p
        where u.roleId=r.id and p.roleId=r.id and u.userName=#{userName}
    </select>
</mapper>

很簡單隻有三個方法,分別對應上面所說的三個方法。對sql稍微熟悉點的童鞋應該都能看懂,不太清楚就拷到數據庫中執行一下就好了,數據庫的Sql也在個人github上。實體類就比較簡單了,就只有四個字段以及get,set方法。我就這裏就不貼了,具體能夠去githubfork個人源碼。github

如今就須要建立自定義的MyRealm類,這個仍是比較重要的。繼承至ShiroAuthorizingRealm類,用於處理本身的驗證邏輯,下面貼一下個人代碼:web

package com.crossoverJie.shiro;

import com.crossoverJie.pojo.T_user;
import com.crossoverJie.service.T_userService;
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 javax.annotation.Resource;
import java.util.Set;

/**
 * Created with IDEA
 * Created by ${jie.chen} on 2016/7/14.
 * Shiro自定義域
 */
public class MyRealm extends AuthorizingRealm {

    @Resource
    private T_userService t_userService;

    /**
     * 用於的權限的認證。
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String username = principalCollection.getPrimaryPrincipal().toString() ;
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo() ;
        Set<String> roleName = t_userService.findRoles(username) ;
        Set<String> permissions = t_userService.findPermissions(username) ;
        info.setRoles(roleName);
        info.setStringPermissions(permissions);
        return info;
    }

    /**
     * 首先執行這個登陸驗證
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        //獲取用戶帳號
        String username = token.getPrincipal().toString() ;
        T_user user = t_userService.findUserByUsername(username) ;
        if (user != null){
            //將查詢到的用戶帳號和密碼存放到 authenticationInfo用於後面的權限判斷。第三個參數傳入realName。
            AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUserName(),user.getPassword(),
                    "a") ;
            return authenticationInfo ;
        }else{
            return  null ;
        }
    }
}

繼承AuthorizingRealm類以後就須要覆寫它的兩個方法,doGetAuthorizationInfo,doGetAuthenticationInfo,這兩個方法的做用我都有寫註釋,邏輯也比較簡單。
doGetAuthenticationInfo是用於登陸驗證的,在登陸的時候須要將數據封裝到Shiro的一個token中,執行shiro的login()方法,以後只要咱們將MyRealm這個類配置到Spring中,登陸的時候Shiro就會自動的調用doGetAuthenticationInfo()方法進行驗證。
哦對了,忘了貼下登陸的Controller了:spring

package com.crossoverJie.controller;

import com.crossoverJie.pojo.T_user;
import com.crossoverJie.service.T_userService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;

/**
 * Created with IDEA
 * Created by ${jie.chen} on 2016/7/14.
 * 後臺Controller
 */
@Controller
@RequestMapping("/")
public class T_userController {

    @Resource
    private T_userService t_userService ;

    @RequestMapping("/loginAdmin")
    public String login(T_user user, Model model){
        Subject subject = SecurityUtils.getSubject() ;
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(),user.getPassword()) ;
        try {
            subject.login(token);
            return "admin" ;
        }catch (Exception e){
            //這裏將異常打印關閉是由於若是登陸失敗的話會自動拋異常
//            e.printStackTrace();
            model.addAttribute("error","用戶名或密碼錯誤") ;
            return "../../login" ;
        }
    }

    @RequestMapping("/admin")
    public String admin(){
        return "admin";
    }

    @RequestMapping("/student")
    public String student(){
        return "admin" ;
    }

    @RequestMapping("/teacher")
    public String teacher(){
        return "admin" ;
    }
}

主要就是login()方法。邏輯比較簡單,只是登陸驗證的時候不是像以前那樣直接查詢數據庫而後返回是否有用戶了,而是調用subjectlogin()方法,就是我上面提到的,調用login()方法時Shiro會自動調用咱們自定義的MyRealm類中的doGetAuthenticationInfo()方法進行驗證的,驗證邏輯是先根據用戶名查詢用戶,若是查詢到的話再將查詢到的用戶名和密碼放到SimpleAuthenticationInfo對象中,Shiro會自動根據用戶輸入的密碼和查詢到的密碼進行匹配,若是匹配不上就會拋出異常,匹配上以後就會執行doGetAuthorizationInfo()進行相應的權限驗證。
doGetAuthorizationInfo()方法的處理邏輯也比較簡單,根據用戶名獲取到他所擁有的角色以及權限,而後賦值到SimpleAuthorizationInfo對象中便可,Shiro就會按照咱們配置的XX角色對應XX權限來進行判斷,這個配置在下面的整合中會講到。sql

整合Spring

接下來應該是你們比較關係的一步:整合Spring
我是在以前的Spring SpringMVC Mybatis的基礎上進行整合的。數據庫

web.xml配置

首先咱們須要在web.xml進行配置Shiro的過濾器。
我只貼Shiro部分的,其他的和以前配置是同樣的。apache

<!-- shiro過濾器定義 -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <!-- 該值缺省爲false,表示生命週期由SpringApplicationContext管理,設置爲true則表示由ServletContainer管理 -->
            <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>

配置仍是比較簡單的,這樣會過濾全部的請求。
以後咱們還須要在Spring中配置一個shiroFilter的bean。

spring-mybatis.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-3.1.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-3.1.xsd">
    <!-- 自動掃描 -->
    <context:component-scan base-package="com.crossoverJie" />
    <!-- 引入配置文件 -->
    <bean id="propertyConfigurer"
          class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:jdbc.properties" />
    </bean>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <!-- 指定鏈接數據庫的驅動 -->
        <property name="driverClassName" value="${jdbc.driverClass}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.user}" />
        <property name="password" value="${jdbc.password}" />

        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="3" />
        <property name="minIdle" value="3" />
        <property name="maxActive" value="20" />

        <!-- 配置獲取鏈接等待超時的時間 -->
        <property name="maxWait" value="60000" />

        <!-- 配置間隔多久才進行一次檢測,檢測須要關閉的空閒鏈接,單位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000" />

        <!-- 配置一個鏈接在池中最小生存的時間,單位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000" />

        <property name="validationQuery" value="SELECT 'x'" />
        <property name="testWhileIdle" value="true" />
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="false" />

        <!-- 打開PSCache,而且指定每一個鏈接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="true" />
        <property name="maxPoolPreparedStatementPerConnectionSize"
                  value="20" />

        <!-- 配置監控統計攔截的filters,去掉後監控界面sql沒法統計 -->
        <property name="filters" value="stat" />
    </bean>

    <!-- spring和MyBatis完美整合,不須要mybatis的配置映射文件 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!-- 自動掃描mapping.xml文件 -->
        <property name="mapperLocations" value="classpath:mapping/*.xml"></property>
    </bean>

    <!-- DAO接口所在包名,Spring會自動查找其下的類 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.crossoverJie.dao" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
    </bean>

    <!-- (事務管理)transaction manager, use JtaTransactionManager for global tx -->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>


    <!-- 配置自定義Realm -->
    <bean id="myRealm" class="com.crossoverJie.shiro.MyRealm"/>

    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="myRealm"/>
    </bean>

    <!-- Shiro過濾器 核心-->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- Shiro的核心安全接口,這個屬性是必須的 -->
        <property name="securityManager" ref="securityManager"/>
        <!-- 身份認證失敗,則跳轉到登陸頁面的配置 -->
        <property name="loginUrl" value="/login.jsp"/>
        <!-- 權限認證失敗,則跳轉到指定頁面 -->
        <property name="unauthorizedUrl" value="/nopower.jsp"/>
        <!-- Shiro鏈接約束配置,即過濾鏈的定義 -->
        <property name="filterChainDefinitions">
            <value>
                <!--anon 表示匿名訪問,不須要認證以及受權-->
                /loginAdmin=anon

                <!--authc表示須要認證 沒有進行身份認證是不能進行訪問的-->
                /admin*=authc


                /student=roles[teacher]
                /teacher=perms["user:create"]
            </value>
        </property>
    </bean>

    <!-- 保證明現了Shiro內部lifecycle函數的bean執行 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- 開啓Shiro註解 -->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor"/>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>
</beans>

在這裏咱們配置了上文中所提到的自定義myRealm,這樣Shiro就能夠按照咱們自定義的邏輯來進行權限驗證了。其他的都比較簡單,看註釋應該都能明白。
着重講解一下:

<property name="filterChainDefinitions">
            <value>
                <!--anon 表示匿名訪問,不須要認證以及受權-->
                /loginAdmin=anon

                <!--authc表示須要認證 沒有進行身份認證是不能進行訪問的-->
                /admin*=authc


                /student=roles[teacher]
                /teacher=perms["user:create"]
            </value>
        </property>
  • /loginAdmin=anon的意思的意思是,發起/loginAdmin這個請求是不須要進行身份認證的,這個請求在此次項目中是一個登陸請求,通常對於這樣的請求都是不須要身份認證的。
  • /admin*=authc表示 /admin,/admin1,/admin2這樣的請求都是須要進行身份認證的,否則是不能訪問的。
  • /student=roles[teacher]表示訪問/student請求的用戶必須是teacher角色,否則是不能進行訪問的。
  • /teacher=perms["user:create"]表示訪問/teacher請求是須要當前用戶具備user:create權限才能進行訪問的。
    更多相關權限過濾的資料能夠訪問shiro的官方介紹:傳送門

使用Shiro標籤庫

Shiro還有着強大標籤庫,能夠在前端幫我獲取信息和作判斷。
我貼一下我這裏登陸完成以後顯示的界面:

<%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2016/7/14
  Time: 13:17
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<html>
<head>
    <title>後臺</title>
</head>
<body>
<shiro:hasRole name="admin">
    這是admin角色登陸:<shiro:principal></shiro:principal>
</shiro:hasRole>

<shiro:hasPermission name="user:create">
    有user:create權限信息
</shiro:hasPermission>
<br>
登陸成功
</body>
</html>

要想使用Shiro標籤,只須要引入一下標籤便可:
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
其實英語稍微好點的童鞋應該都能看懂。下面我大概介紹下一些標籤的用法:

  • <shiro:hasRole name="admin">具備admin角色纔會顯示標籤內的信息。
  • <shiro:principal></shiro:principal>獲取用戶信息。默認調用Subject.getPrincipal()獲取,即 Primary Principal。
  • <shiro:hasPermission name="user:create"> 用戶擁有user:create這個權限纔回顯示標籤內的信息。
    更多的標籤能夠查看官網:傳送門

總體測試

 

 

 


能夠看到我登陸的用戶是crossoverJie他是有admin的角色,而且擁有user:*(ps:系統數據詳見上面的數據庫截圖)的權限,因此在這裏:

 

<shiro:hasRole name="admin">   
    這是admin角色登陸:<shiro:principal></shiro:principal>
</shiro:hasRole>
<shiro:hasPermission name="user:create">
    有user:create權限信息
</shiro:hasPermission>

是能顯示出標籤內的信息,並把用戶信息也顯示出來了。
接着咱們來訪問一下/student這個請求,由於在Spring的配置文件中:

<property name="filterChainDefinitions">
            <value>
                <!--anon 表示匿名訪問,不須要認證以及受權-->
                /loginAdmin=anon

                <!--authc表示須要認證 沒有進行身份認證是不能進行訪問的-->
                /admin*=authc


                /student=roles[teacher]
                /teacher=perms["user:create"]
            </value>
        </property>

只有teacher角色才能訪問/student這個請求的:

@RequestMapping("/teacher")
    public String teacher(){
        return "admin" ;
    }

而且沒有顯示以前Shiro標籤內的內容。
其餘的我就不測了,你們能夠本身在數據庫里加一些數據,或者是改下攔截的權限多試試,這樣對Shiro的理解就會更加深入。

MD5加密

Shiro還封裝了一個我認爲很是不錯的功能,那就是MD5加密,代碼以下:

package com.crossoverJie.shiro;

import org.apache.shiro.crypto.hash.Md5Hash;

/**
 * Created with IDEA
 * 基於Shiro的MD5加密
 * Created by ${jie.chen} on 2016/7/13.
 */
public class MD5Util {

    public static String md5(String str,String salt){
        return new Md5Hash(str,salt).toString() ;
    }

    public static void main(String[] args) {
        String md5 = md5("abc123","crossoverjie") ;
        System.out.println(md5);
    }
}

代碼很是簡單,只須要調用Md5Hash(str,salt)方法便可,這裏多了一個參數,第一個參數不用多解釋,是須要加密的字符串。第二個參數salt中文翻譯叫鹽,加密的時候咱們傳一個字符串進去,只要這個salt不被泄露出去,那原則上加密以後是沒法被解密的,在存用戶密碼的時候可使用,感受仍是很是屌的。

總結

以上就是Shiro實際使用的案例,將的比較初略,可是關於Shiro的核心東西都在裏面了。你們能夠去個人github上下載源碼,只要按照我給的數據庫就沒有問題,項目跑起來以後試着改下里面的東西能夠加深對Shiro的理解。

做者:crossoverJie 連接:http://www.jianshu.com/p/6786ddf54582/ 來源:簡書 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

相關文章
相關標籤/搜索