1. 程式人生 > >Springboot整合Shiro框架(包含mybatis、Durid、Html5、Thymeleaf)

Springboot整合Shiro框架(包含mybatis、Durid、Html5、Thymeleaf)

在Springboot中使用Shrio,實現賬號的登入限制、賬號的角色的授權、資源的授權。

以下是我個人從零開始在springboot專案中去整合Shiro框架,僅供參考,但是按照我這個整合是絕對可行的!

我以專案結構的方式去介紹了:

先貼個專案結構目錄圖:

首先是pom檔案:

<?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>com</groupId>
   <artifactId>shirodemo</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>

   <name>shirodemo</name>
   <description>Demo project for Spring Boot</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.0.4.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>

   </properties>

   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-jdbc</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
         <groupId>org.mybatis.spring.boot</groupId>
         <artifactId>mybatis-spring-boot-starter</artifactId>
         <version>1.3.2</version>
      </dependency>

      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-thymeleaf</artifactId>
      </dependency>
      <!--Spring框架基本的核心工具-->
      <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-context-support</artifactId>
      </dependency>
      <!-- shiro-spring -->
      <dependency>
         <groupId>org.apache.shiro</groupId>
         <artifactId>shiro-spring</artifactId>
         <version>1.3.2</version>
      </dependency>
      <!-- shiro ehcache -->
      <dependency>
         <groupId>org.apache.shiro</groupId>
         <artifactId>shiro-ehcache</artifactId>
         <version>1.3.2</version>
      </dependency>
      <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
      <dependency>
         <groupId>org.apache.shiro</groupId>
         <artifactId>shiro-core</artifactId>
         <version>1.3.2</version>
      </dependency>
      <!--配置shiro標籤 必須使用,不然找不到AbstractTextChildModifierAttrProcessor-->
      <dependency>
         <groupId>com.github.theborakompanioni</groupId>
         <artifactId>thymeleaf-extras-shiro</artifactId>
         <version>2.0.0</version>
      </dependency>

      <!-- ehchache -->
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-cache</artifactId>
      </dependency>
      <dependency>
         <groupId>net.sf.ehcache</groupId>
         <artifactId>ehcache</artifactId>
      </dependency>

        <!--mysql連線包-->
      <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <scope>runtime</scope>
      </dependency>
      <!--mybatis-->
      <dependency>
         <groupId>org.mybatis.spring.boot</groupId>
         <artifactId>mybatis-spring-boot-starter</artifactId>
         <version>1.3.1</version>
      </dependency>
      <!-- druid資料來源驅動 1.1.10解決springboot從1.0——2.0版本問題-->
      <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>druid-spring-boot-starter</artifactId>
         <version>1.1.10</version>
      </dependency>
      <!-- springboot-log4j -->
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-log4j</artifactId>
         <version>1.3.8.RELEASE</version>
      </dependency>

      <dependency>
         <groupId>commons-lang</groupId>
         <artifactId>commons-lang</artifactId>
         <version>2.6</version>
      </dependency>
    </dependencies>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>


</project>

這個pom檔案,改說明的我都在註釋上做了說明,請認真閱讀。

配置完所需要用的jar包後,我們整合Shiro框架需要做的第一步是什麼呢?

就是建一個shiro資料夾,在這個資料夾裡面,我們需要建2個類:

一個是config配置類(顧名思義,就是各自配置),一個是自定義的Realm域類(這麼說吧,就是個做類似連線效果的東西,shiro裡面很多東西都得經過這個圈兒)

ShiroConfig類:

package com.shirodemo.shiro;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;


/**
 * @Author: JCccc
 * @CreateTime: 2018-10-25
 * @Description:
 */
    @Configuration
    public class ShiroConfig {
        /**
         * Subject: 使用者主體(把操作交給SecurityManager)
         * SecurityManager: 安全管理器(關聯Realm)
         * Realm: Shiro連線資料的橋樑
         */

        //一. 建立ShiroFilterFactoryBean
        @Bean
        public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
            ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
        //設定安全管理器
            shiroFilterFactoryBean.setSecurityManager(securityManager);

            //新增Shiro內建過濾器
        /**
         * Shiro內建過濾器,可以實現許可權相關的攔截器
         * 常用的過濾器:
         *  anon:無需認證(登入)可以訪問
         *  authc:必須認證才可以訪問
         *  user:如果使用rememberMe的功能可以直接訪問
         *  perms:該資源必須得到資源許可權才可以訪問
         *  role:該資源必須得到角色許可權才可以訪問
         *
         *
         *
         */
        Map<String,String> filterMap = new LinkedHashMap<String, String>();
        /*filterMap.put("/add","authc");
        filterMap.put("/update","authc");*/
         //使用通配方式統一配置攔截的URL

         // 注意:*******************下面的是按順序執行的**********************


            filterMap.put("/testThymeleaf","anon");
            //放行login.html
            filterMap.put("/login","anon");


            //授權過濾器 ,以下為例 加上了授權perms 後,那麼自動會跳轉到 執行認證授權AuthenticationInfo doGetAuthenticationInfo
            // 當授權攔截時,會自動跳轉到未授權提示頁面
            filterMap.put("/add","perms[user-add]");
            filterMap.put("/update","perms[user-update]");
            filterMap.put("/*","authc");

            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
         //修改攔截後返回的登入頁面
            shiroFilterFactoryBean.setLoginUrl("/toLogin");
         //設定未授權提示頁面
            shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");
            return shiroFilterFactoryBean;
        }


        //二. 建立DefaultWebSecurityMangager

        @Bean(name = "securityManager")
        public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){

            DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();

            //關聯Realm
            securityManager.setRealm(userRealm);
            //注入快取管理器;
            securityManager.setCacheManager(ehCacheManager());
            //這個如果執行多次,也是同樣的一個物件;
            return securityManager;

        }


        //三. 建立Realm

        @Bean(name = "userRealm")
        public UserRealm getRaalm(){
            return new UserRealm();

        }


        /**
         * 配置ShiroDialect,用於thymeleaf和shiro標籤配合使用
         */
        @Bean
        public ShiroDialect getShiroDialect(){
            return new ShiroDialect();
        }


    /**
     * shiro快取管理器;
     * 需要注入對應的其它的實體類中:
     * 1、安全管理器:securityManager
     * 可見securityManager是整個shiro的核心;
     * @return
     */

    @Bean
    public EhCacheManager ehCacheManager(){
        System.out.println("ShiroConfiguration.getEhCacheManager()");
        EhCacheManager cacheManager = new EhCacheManager();
        cacheManager.setCacheManagerConfigFile("classpath:config/shiro-ehcache.xml");
        return cacheManager;

    }


}

這個配置類裡面配置的東西以及意義,我也做了相關的詳細註釋。至於你想更深入瞭解,那請更深入地自己去網上了解了。

然後是自定義的Realm,我這邊定義的名字叫UserRealm:

package com.shirodemo.shiro;

import com.shirodemo.pojo.User;
import com.shirodemo.service.UserService;
import com.shirodemo.util.StringUtils;
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.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * @Author: JCccc
 * @CreateTime: 2018-10-25
 * @Description:這是一個自定義的Realm類,繼承AuthorizingRealm類
 */

public class UserRealm extends AuthorizingRealm{
    @Autowired
    UserService userService;

    /**
     * 執行授權邏輯
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("執行授權邏輯!");
        //給資源進行授權

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //先到資料庫去查詢當前登入使用者的授權字串

        Subject subject= SecurityUtils.getSubject();
        User user=(User)subject.getPrincipal();
        User userNow=userService.queryUserInfoById(user.getId());
        System.out.println("許可權是L:"+userNow.getPerms());

        //新增資源的授權字串
        info.addStringPermission(userNow.getPerms());
        clearCachedAuthorizationInfo();
        return info;
    }

    /**
     * 執行認證邏輯
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("執行認證邏輯!");

        //編寫Shiro的判斷邏輯,判斷使用者名稱和密碼
        //1.判斷使用者名稱
        UsernamePasswordToken tokenNow=(UsernamePasswordToken)token;
        User user=userService.queryUserInfoByaccountname(tokenNow.getUsername());

        if(StringUtils.isNull(user)){
            //使用者名稱不存在
            //此刻shiro會丟擲一個UnknownAccountException,表示使用者名稱不存在
            return null;
        }

       // String password=user.getPassword();
        //2.判斷密碼
        return new SimpleAuthenticationInfo(user,user.getPassword(),"");
    }

    /**
     * 清理快取許可權
     */
    public void clearCachedAuthorizationInfo()
    {
        this.clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals());
    }



}


同理,註釋裡面我認為改說的也明明白白了。 

配置完這兩個大核心,接下來,再加上一個配置快取的xml,那麼關於shiro的配置部分(除了filter,這裡我沒用到)就大功告成了。

快取配置的xml:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <diskStore path="java.io.tmpdir/Tmp_EhCache"/>
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"/>

    <!-- 登入記錄快取鎖定1小時 -->
    <cache name="passwordRetryCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true"/>

     <cache name="shiro-activeSessionCache"
        maxEntriesLocalHeap="10000"
        overflowToDisk="false"
        eternal="false"
        diskPersistent="false"
        timeToLiveSeconds="300"
        timeToIdleSeconds="600"
        statistics="true"/>



</ehcache>


<!--<?xml version="1.0" encoding="UTF-8"?>-->
<!--<ehcache name="swmmotors">-->
    <!--&lt;!&ndash; 磁碟快取位置 &ndash;&gt;-->
    <!--<diskStore path="java.io.tmpdir"/>-->

    <!--&lt;!&ndash; 預設快取 &ndash;&gt;-->
    <!--<defaultCache-->
            <!--maxEntriesLocalHeap="1000"-->
            <!--eternal="false"-->
            <!--timeToIdleSeconds="3600"-->
            <!--timeToLiveSeconds="3600"-->
            <!--overflowToDisk="false">-->
    <!--</defaultCache>-->

    <!--&lt;!&ndash; 登入記錄快取 鎖定10分鐘 &ndash;&gt;-->
    <!--<cache name="loginRecordCache"-->
           <!--maxEntriesLocalHeap="2000"-->
           <!--eternal="false"-->
           <!--timeToIdleSeconds="600"-->
           <!--timeToLiveSeconds="0"-->
           <!--overflowToDisk="false"-->
           <!--statistics="true">-->
    <!--</cache>-->

    <!--&lt;!&ndash; 系統使用者快取  沒必要過期 &ndash;&gt;-->
    <!--<cache name="sys-userCache"-->
           <!--maxEntriesLocalHeap="10000"-->
           <!--overflowToDisk="false"-->
           <!--eternal="false"-->
           <!--diskPersistent="false"-->
           <!--timeToLiveSeconds="0"-->
           <!--timeToIdleSeconds="0"-->
           <!--statistics="true"/>-->

    <!--&lt;!&ndash; 系統使用者授權快取  沒必要過期 &ndash;&gt;-->
    <!--<cache name="sys-authCache"-->
           <!--maxEntriesLocalHeap="10000"-->
           <!--overflowToDisk="false"-->
           <!--eternal="false"-->
           <!--diskPersistent="false"-->
           <!--timeToLiveSeconds="0"-->
           <!--timeToIdleSeconds="0"-->
           <!--memoryStoreEvictionPolicy="LRU"-->
           <!--statistics="true"/>-->

    <!--&lt;!&ndash; 選單快取  沒必要過期 &ndash;&gt;-->
    <!--<cache name="sys-menuCache"-->
           <!--maxEntriesLocalHeap="10000"-->
           <!--overflowToDisk="false"-->
           <!--eternal="false"-->
           <!--diskPersistent="false"-->
           <!--timeToLiveSeconds="0"-->
           <!--timeToIdleSeconds="0"-->
           <!--statistics="true"/>-->

    <!--&lt;!&ndash; shiro 會話快取 不需要序列化到磁碟 此處我們放到db中了 此處cache沒必要過期 因為我們存放到db了 &ndash;&gt;-->
    <!--<cache name="shiro-activeSessionCache"-->
           <!--maxEntriesLocalHeap="10000"-->
           <!--overflowToDisk="false"-->
           <!--eternal="false"-->
           <!--diskPersistent="false"-->
           <!--timeToLiveSeconds="300"-->
           <!--timeToIdleSeconds="600"-->
           <!--statistics="true"/>-->

<!--</ehcache>-->

我去,差點忘了yml檔案了:
server:
  servlet:
    context-path: /
  port: 8012
spring:
  datasource:
    druid:
      # 資料庫訪問配置, 使用druid資料來源
      type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/shirotest?useSSL=false&useUnicode=true&characterEncoding=utf-8
      username: root
      password: 123456
      # 連線池配置
      initial-size: 5
      min-idle: 5
      max-active: 20
     # 連線等待超時時間
      max-wait: 30000
      # 配置檢測可以關閉的空閒連線間隔時間
      time-between-eviction-runs-millis: 60000
      # 配置連線在池中的最小生存時間
      min-evictable-idle-time-millis: 300000
      validation-query: select '1' from dual
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
       # 開啟PSCache,並且指定每個連線上PSCache的大小
      pool-prepared-statements: true
      max-open-prepared-statements: 20
      max-pool-prepared-statement-per-connection-size: 20
       # 配置監控統計攔截的filters, 去掉後監控介面sql無法統計, 'wall'用於防火牆
      filters:
        commons-log.connection-logger-name: stat,wall,log4j
      useGlobalDataSourceStat: true
       # 通過connectProperties屬性來開啟mergeSql功能;慢SQL記錄
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

      # StatViewServlet配置
      stat-view-servlet:
        enabled: true
              # 訪問路徑為/druid時,跳轉到StatViewServlet
        url-pattern: /druid/*
              # 是否能夠重置資料
        reset-enable: false
              # 需要賬號密碼才能訪問控制檯
        login-username: root
        login-password: 123456
              # IP白名單
              # allow: 127.0.0.1
              # IP黑名單(共同存在時,deny優先於allow)
              # deny: 192.168.1.218
            # 配置StatFilter
      filter:
        stat:
           log-slow-sql: true
mybatis:
  # type-aliases掃描路徑
  type-aliases-package: com.shirodemoe.pojo
  # mapper xml實現掃描路徑
  mapper-locations: classpath:mybatis/mapper/*.xml
  property:
    order: BEFORE

配置完關於shiro的一些條條框框,那麼我們可以直接開始使用了。
當然我這裡的使用是指,已經配置了mybatis啊、Durid連線池啊、Thymeleaf啊那些其他的東西。

開始使用,先從資料庫開始:

資料庫表,一張(資料庫連線的配置資訊在yml檔案裡可以看,當然這個應該自己就會):

然後就是回到我們熟悉的SSM框架了其實。

廢話不多說,

先上個pojo的類:

package com.shirodemo.pojo;

/**
 * @Author: JCccc
 * @CreateTime: 2018-10-26
 * @Description:
 */
public class User {
    private Integer id;
    private String accountname;
    private String password;
    private  String perms;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", accountname='" + accountname + '\'' +
                ", password='" + password + '\'' +
                ", perms='" + perms + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getAccountname() {
        return accountname;
    }

    public void setAccountname(String accountname) {
        this.accountname = accountname;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPerms() {
        return perms;
    }

    public void setPerms(String perms) {
        this.perms = perms;
    }
}

然後到了dao層了,也就是mapper:
package com.shirodemo.mapper;

import com.shirodemo.pojo.User;
import org.apache.ibatis.annotations.Mapper;


/**
 * @Author: JCccc
 * @CreateTime: 2018-10-26
 * @Description:
 */

@Mapper
public interface UserMapper {
    User queryUserInfoByaccountname(String accountname);
    User queryUserInfoById(Integer id);

}

接著,就是mapper.xml咯,當然你可以用註解SQL方式。
<?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.shirodemo.mapper.UserMapper">


    <select id="queryUserInfoByaccountname" resultType="com.shirodemo.pojo.User">
        SELECT *FROM user
        WHERE  accountname=#{accountname}

    </select>


    <select id="queryUserInfoById" resultType="com.shirodemo.pojo.User">
        SELECT *FROM user
        WHERE  id=#{id}

    </select>

</mapper>

然後就是service了,

先service介面:

package com.shirodemo.service;

import com.shirodemo.pojo.User;


/**
 * @Author: JCccc
 * @CreateTime: 2018-10-26
 * @Description:
 */


public interface UserService {

  User queryUserInfoByaccountname(String accountname);
  User queryUserInfoById(Integer id);
}

再是impl實現類:

package com.shirodemo.service.impl;

import com.shirodemo.mapper.UserMapper;
import com.shirodemo.pojo.User;
import com.shirodemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


/**
 * @Author: JCccc
 * @CreateTime: 2018-10-26
 * @Description:
 */

@Service
public class UserServiceImpl implements UserService {

    @Autowired
   public UserMapper userMapper;
    @Override
    public User queryUserInfoByaccountname(String accountname){
        return userMapper.queryUserInfoByaccountname(accountname);
    }

    @Override
    public User queryUserInfoById(Integer id) {
        return userMapper.queryUserInfoById(id);
    }
}

好了!終於到了controller層了:

package com.shirodemo.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;

/**
 * @Author: JCccc
 * @CreateTime: 2018-10-25
 * @Description:
 */


@Controller
public class UserController {

        @ResponseBody
        @RequestMapping("/hello")
        public String helloContro(){

            return  "hello man";
        }

        @RequestMapping("/add")
        public String add(){

            return  "/user/add";
        }

        @RequestMapping("/update")
        public String update(){

            return  "/user/update";
        }

        //用於攔截完之後去登入頁面

        @RequestMapping("/toLogin")
        public String login(){


                return "login";
        }

        //用於未授權識別後跳轉的提示頁面

        @RequestMapping("/noAuth")
        public String noAuth(){


            return "noAuth";
        }


        //測試頁面主頁
        @RequestMapping("/testThymeleaf")
        public  String testThymeleafController(Model model){
            model.addAttribute("name","測試模板引擎");
            return "test";
        }



    @RequestMapping("/login")
    public  String loginController(String accountname, String password, Model model, HttpServletRequest request){
        /**
         *
         * 使用Shiro編寫認證操作
         */
        System.out.println("開始執行認證"+"name為"+accountname);



        //1.獲取Subject
        Subject subject= SecurityUtils.getSubject();

        //2.封裝使用者賬號資訊資料
        UsernamePasswordToken token=new UsernamePasswordToken(accountname,password);



        //3.執行登入方法
        try {
            subject.login(token);
            //當執行subject的login方法,那麼就必定會到shiro裡面的認證操作方法裡面AuthenticationInfo doGetAuthenticationInfo
            //登入成功
            //跳轉到test.html
            return "test";
        }
        catch (UnknownAccountException e) {
            //e.printStackTrace();
            //如果是catch到UnknownAccountException異常,那麼就是使用者名稱不存在
            model.addAttribute("msg","使用者名稱不存在");
            //如果這裡使用重定向,那麼msg將不會跟著過去下個函式方法
            return "login";
        }
        catch (IncorrectCredentialsException e) {
         //   e.printStackTrace();
            //登陸失敗,密碼錯誤
            model.addAttribute("msg","密碼錯誤");
            //如果這裡使用重定向,那麼msg將不會跟著過去下個函式方法
            return "login";
        }



    }
}

頁面是時候給你們看一眼了,簡簡單單的:

test.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>測試Thymeleaf模板的使用</title>
</head>


    <body>
         <h3 th:text="${name}"></h3>
         <hr/>

         <div shiro:hasPermission="user-add">
            點選進入使用者新增頁面:<a href="add">使用者新增</a><br/>
        </div>
         <div shiro:hasPermission="user-update">
             點選進入使用者更新:<a href="update">使用者更新</a><br/>
         </div>

            <a href="tologin">使用者登入</a>

    </body>


</html>

noAuth.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>未授權提示頁面</title>
</head>
<body>
親,你暫時沒有被授權訪問頁面。
</body>
</html>

login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8">
    <title>登入頁面</title>
</head>
<body>


                   <h3>登入</h3>

      <h3  th:text="${msg}" style="color: red"/>
      <form method="post" action="/login">

          使用者名稱:<input type="text" name="accountname"/><br/>
          密碼:<input type="password" name="password"/><br/>
          <input type="submit" value="登入">
    </form>
</body>
</html>

然後是新增和更新的驗證頁面,
add.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Add使用者</title>
</head>
<body>
使用者新增
</body>
</html>

update.html
 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>update使用者</title>
</head>
<body>
使用者的更新
</body>
</html>

好了,其實裡面我還用到了一個工具類是關於判斷字元是否為空的:

StringUtils,也貼了吧:

package com.shirodemo.util;



import org.apache.commons.lang.text.StrBuilder;

import java.util.Collection;
import java.util.Map;

/**
 * 字串工具類
 */
public class StringUtils
{
    /** 空字串 */
    private static final String NULLSTR = "";

    /** 下劃線 */
    private static final char SEPARATOR = '_';

    /**
     * 獲取引數不為空值
     * 
     * @param value defaultValue 要判斷的value
     * @return value 返回值
     */
    public static <T> T nvl(T value, T defaultValue)
    {
        return value != null ? value : defaultValue;
    }

    /**
     * * 判斷一個Collection是否為空, 包含List,Set,Queue
     * 
     * @param coll 要判斷的Collection
     * @return true:為空 false:非空
     */
    public static boolean isEmpty(Collection<?> coll)
    {
        return isNull(coll) || coll.isEmpty();
    }

    /**
     * * 判斷一個Collection是否非空,包含List,Set,Queue
     * 
     * @param coll 要判斷的Collection
     * @return true:非空 false:空
     */
    public static boolean isNotEmpty(Collection<?> coll)
    {
        return !isEmpty(coll);
    }

    /**
     * * 判斷一個物件陣列是否為空
     * 
     * @param objects 要判斷的物件陣列
     ** @return true:為空 false:非空
     */
    public static boolean isEmpty(Object[] objects)
    {
        return isNull(objects) || (objects.length == 0);
    }

    /**
     * * 判斷一個物件陣列是否非空
     * 
     * @param objects 要判斷的物件陣列
     * @return true:非空 false:空
     */
    public static boolean isNotEmpty(Object[] objects)
    {
        return !isEmpty(objects);
    }

    /**
     * * 判斷一個Map是否為空
     * 
     * @param map 要判斷的Map
     * @return true:為空 false:非空
     */
    public static boolean isEmpty(Map<?, ?> map)
    {
        return isNull(map) || map.isEmpty();
    }

    /**
     * * 判斷一個Map是否為空
     * 
     * @param map 要判斷的Map
     * @return true:非空 false:空
     */
    public static boolean isNotEmpty(Map<?, ?> map)
    {
        return !isEmpty(map);
    }

    /**
     * * 判斷一個字串是否為空串
     * 
     * @param str String
     * @return true:為空 false:非空
     */
    public static boolean isEmpty(String str)
    {
        return isNull(str) || NULLSTR.equals(str.trim());
    }

    /**
     * * 判斷一個字串是否為非空串
     * 
     * @param str String
     * @return true:非空串 false:空串
     */
    public static boolean isNotEmpty(String str)
    {
        return !isEmpty(str);
    }

    /**
     * * 判斷一個物件是否為空
     * 
     * @param object Object
     * @return true:為空 false:非空
     */
    public static boolean isNull(Object object)
    {
        return object == null;
    }

    /**
     * * 判斷一個物件是否非空
     * 
     * @param object Object
     * @return true:非空 false:空
     */
    public static boolean isNotNull(Object object)
    {
        return !isNull(object);
    }

    /**
     * * 判斷一個物件是否是陣列型別(Java基本型別的陣列)
     * 
     * @param object 物件
     * @return true:是陣列 false:不是陣列
     */
    public static boolean isArray(Object object)
    {
        return isNotNull(object) && object.getClass().isArray();
    }

    /**
     * 去空格
     */
    public static String trim(String str)
    {
        return (str == null ? "" : str.trim());
    }

    /**
     * 擷取字串
     * 
     * @param str 字串
     * @param start 開始
     * @return 結果
     */
    public static String substring(final String str, int start)
    {
        if (str == null)
        {
            return NULLSTR;
        }

        if (start < 0)
        {
            start = str.length() + start;
        }

        if (start < 0)
        {
            start = 0;
        }
        if (start > str.length())
        {
            return NULLSTR;
        }

        return str.substring(start);
    }

    /**
     * 擷取字串
     * 
     * @param str 字串
     * @param start 開始
     * @param end 結束
     * @return 結果
     */
    public static String substring(final String str, int start, int end)
    {
        if (str == null)
        {
            return NULLSTR;
        }

        if (end < 0)
        {
            end = str.length() + end;
        }
        if (start < 0)
        {
            start = str.length() + start;
        }

        if (end > str.length())
        {
            end = str.length();
        }

        if (start > end)
        {
            return NULLSTR;
        }

        if (start < 0)
        {
            start = 0;
        }
        if (end < 0)
        {
            end = 0;
        }

        return str.substring(start, end);
    }


    /**
     * 駝峰首字元小寫
     */
    public static String uncapitalize(String str)
    {
        int strLen;
        if (str == null || (strLen = str.length()) == 0)
        {
            return str;
        }
        return new StrBuilder(strLen).append(Character.toLowerCase(str.charAt(0))).append(str.substring(1)).toString();
    }

    /**
     * 下劃線轉駝峰命名
     */
    public static String toUnderScoreCase(String s)
    {
        if (s == null)
        {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        boolean upperCase = false;
        for (int i = 0; i < s.length(); i++)
        {
            char c = s.charAt(i);

            boolean nextUpperCase = true;

            if (i < (s.length() - 1))
            {
                nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
            }

            if ((i > 0) && Character.isUpperCase(c))
            {
                if (!upperCase || !nextUpperCase)
                {
                    sb.append(SEPARATOR);
                }
                upperCase = true;
            }
            else
            {
                upperCase = false;
            }

            sb.append(Character.toLowerCase(c));
        }

        return sb.toString();
    }

    /**
     * 是否包含字串
     * 
     * @param str 驗證字串
     * @param strs 字串組
     * @return 包含返回true
     */
    public static boolean inStringIgnoreCase(String str, String... strs)
    {
        if (str != null && strs != null)
        {
            for (String s : strs)
            {
                if (str.equalsIgnoreCase(trim(s)))
                {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 將下劃線大寫方式命名的字串轉換為駝峰式。如果轉換前的下劃線大寫方式命名的字串為空,則返回空字串。 例如:HELLO_WORLD->HelloWorld
     * 
     * @param name 轉換前的下劃線大寫方式命名的字串
     * @return 轉換後的駝峰式命名的字串
     */
    public static String convertToCamelCase(String name)
    {
        StringBuilder result = new StringBuilder();
        // 快速檢查
        if (name == null || name.isEmpty())
        {
            // 沒必要轉換
            return "";
        }
        else if (!name.contains("_"))
        {
            // 不含下劃線,僅將首字母大寫
            return name.substring(0, 1).toUpperCase() + name.substring(1);
        }
        // 用下劃線將原始字串分割
        String[] camels = name.split("_");
        for (String camel : camels)
        {
            // 跳過原始字串中開頭、結尾的下換線或雙重下劃線
            if (camel.isEmpty())
            {
                continue;
            }
            // 首字母大寫
            result.append(camel.substring(0, 1).toUpperCase());
            result.append(camel.substring(1).toLowerCase());
        }
        return result.toString();
    }
}

好了,記錄分享完畢。按照這個從零開始手把手配置,絕對能完成!