Shiro(二)一篇文章入門Shiro(入門篇)

話不多說,直接上代碼。
認真觀看代碼,裏面含有大量的註釋
github上的代碼,修改一下數據庫的信息,就可以直接使用。附帶sql文件
遺憾的是,沒有實現RBAC
github地址:https://github.com/ZiCheng-Web/springboot-shiro
在這裏插入圖片描述

0、創建springboot項目(springboot-shiro)

項目結構

在這裏插入圖片描述
在這裏插入圖片描述

1、修改pom.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>com.zicheng</groupId>
    <artifactId>springboot-shiro</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>springboot-shiro</name>
    <description>Demo project for Spring Boot</description>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.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>
        <!-- 修改thymeleaf的版本 -->
        <!--<thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>-->
        <!--<thymeleaf-layout-dialect.version>2.0.4</thymeleaf-layout-dialect.version>-->
    </properties>

    <dependencies>
        <!--springboot Start-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--springboot End-->

        <!-- mybatis Start -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.21</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
        <!-- mybatis End -->

        <!--shiro start-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-web-starter</artifactId>
            <version>1.5.1</version>
        </dependency>
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>
        <!--Shiro End-->
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--lombok-->
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.zicheng.SpringBootShiroApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

2、修改配置文件

server:
  port: 8080

spring:
  thymeleaf:
    cache: false
    prefix: classpath:/templates/
    suffix: .html
    encoding: UTF-8
    mode: HTML5
    
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://127.0.0.1:3306/testshiro?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  type-aliases-package: com.zicheng.domain
  mapper-locations: classpath:mapper/*.xml

3、shiro的配置文件

package com.zicheng.shiro;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
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;

/**
 * 子誠
 * Description:Shiro的配置類
 * 時間:2020/3/7 14:20
 */
@Configuration
public class ShiroConfig {
    /**
     * 創建ShiroFilterFactoryBean(shiro過濾器工廠實例)
     */
    @Bean(name = "shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager securityManager) {
        //創建ShiroFilterFactoryBean的實例
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //關聯->安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //修改跳轉的登陸頁面
        shiroFilterFactoryBean.setLoginUrl("/toLogin");
        //設置未授權提示頁面
        shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");


        //添加shiro的內置過濾器
        /**
         * shiro的內置過濾器,可以實現權限相關的攔截
         *      常用的過濾器:
         *      1、anon :無需認證(無需登錄),即可訪問
         *      2、authc :必須認證纔可以訪問(可以理解爲登陸了才能訪問)
         *      3、user  :如果使用rememberMe(登陸框下面的記住我功能)的功能就可以直接訪問
         *      4、perms :該資源必須得到資源權限才能好訪問
         *      5、role: 該資源必須得到角色權限才能訪問
         */
        Map<String, String> filterMap = new LinkedHashMap<String, String>();
        //將登陸請求,放行
        filterMap.put("/login", "anon");
        //anon:無需認證即可訪問(要在下面的所有到需要認證的上面,順序要注意)
        //filterMap.put("/test", "anon");

//        filterMap.put("/add", "authc");
//        filterMap.put("/update", "authc");

        //授權過濾器
        //注意:當前授權攔截後,shiro會自動跳轉到未授權頁面
        filterMap.put("/add", "perms[user:add]");
        filterMap.put("/update", "perms[user:update]");

        //所有資源鏈接都必須登陸認證才能訪問,一定要寫在最後面
        filterMap.put("/*", "authc");

        //setFilterChainDefinitionMap:設置過濾器定義Map
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 創建DefaultWebSecurityManager
     * <p>
     * getDefaultWebSecurityManager()
     */
    @Bean(name = "defaultWebSecurityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        //@Qualifier("userRealm"):代表着從spring的bean工廠中userRealm爲合格者
        //創建 DefaultWebSecurityManager的實例 securityManager
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //關聯UserRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    /**
     * 創建Realm
     */
    @Bean(name = "userRealm")
    public UserRealm getRealm() {
        return new UserRealm();
    }

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

package com.zicheng.shiro;

import com.zicheng.domain.User;
import com.zicheng.service.UserService;
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;

/**
 * 子誠
 * Description:自定義的Realm,繼承shiro提供的AuthorizingRealm
 * 時間:2020/3/7 14:24
 */
public class UserRealm extends AuthorizingRealm {

    /**
     * 執行授權邏輯
     * AuthorizationInfo 用於聚合授權信息;
     * 當我們使用 AuthorizingRealm 時,
     * 如果身份驗證成功,在進行授權時就通過doGetAuthorizationInfo 方法獲取角色/權限信息用於授權驗證。
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("執行了授權邏輯");


        //給資源進行授權
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //添加資源的授權字符串
        //info.addStringPermission("user:add");

        //到數據庫查詢當前登錄用戶的授權字符串
        //獲取當前登錄用戶
        Subject subject = SecurityUtils.getSubject();
        User user = (User)subject.getPrincipal();
        User dbUser = userSerivce.findById(user.getId());
        info.addStringPermission(dbUser.getPerms());
        return info;
    }
    @Autowired
    private UserService userSerivce;
    /**
     * 執行認證邏輯
     * AuthenticationInfo:用於進行認證用戶名和密碼的。以及密碼是否加鹽,是否加密
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("執行了認證邏輯");



        //編寫shiro判斷邏輯,判斷用戶名和密碼
        //1、判斷用戶名
        UsernamePasswordToken token= (UsernamePasswordToken) authenticationToken;
        //根據頁面上填寫的用戶名,獲取到整個user
        User user = userSerivce.findByName(token.getUsername());

        if(null == user){
            //意味着用戶名不存在
            return null;//shiro的底冊會拋出一個UnknownAccountException:這個異常代表用戶名不存在
        }
        //2、判斷密碼
        return new SimpleAuthenticationInfo(user,user.getPassword(),getName());
        /**
         * SimpleAuthenticationInfo中可以傳三個參數也可以傳四個參數。
         * 第一個參數:
         *       傳入的都是com.zicheng.domain 包下的User類的user對象。
         * 第二個參數:
         *      傳入的是從數據庫中獲取到的password,然後再與token中的password進行對比,匹配上了就通過,匹配不上就報異常。
         * 第三個參數:
         *      鹽–用於加密密碼對比。 若不需要,則可以設置爲空 “ ”
         * 第四個參數:
         *      當前realm的名字。
         */
    }
}

4、DAO層

package com.zicheng.mapper;

import com.zicheng.domain.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.web.bind.annotation.Mapping;

@Mapper
public interface UserMapper {

	public User findByName(String username);
	
	public User findById(Integer id);
}

<?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">
<!-- 該文件存放CRUD的sql語句 -->
<mapper namespace="com.zicheng.mapper.UserMapper">

    <select id="findByName" parameterType="string" resultType="com.zicheng.domain.User">
	SELECT 	id, 
		username,
		PASSWORD
		FROM 
		t_user where username = #{value}
	</select>

    <select id="findById" parameterType="int" resultType="com.zicheng.domain.User">
		SELECT 	id, 
		username,
		PASSWORD,
		perms 
		FROM 
		t_user where id = #{value}
	</select>
</mapper>

5、service層

package com.zicheng.service;

import com.zicheng.domain.User;

public interface UserService {

	public User findByName(String username);
	
	public User findById(Integer id);
}

package com.zicheng.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.zicheng.domain.User;
import com.zicheng.mapper.UserMapper;
import com.zicheng.service.UserService;

@Service
public class UserServiceImpl implements UserService{

	//注入Mapper接口
	@Autowired
	private UserMapper userMapper;
	
	@Override
	public User findByName(String username) {
		return userMapper.findByName(username);
	}

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

6、Controller層

package com.zicheng.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
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.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.HttpSession;
import java.lang.annotation.IncompleteAnnotationException;

/**
 * 子誠
 * Description:
 * 時間:2020/3/6 7:02
 */
@Controller
public class UserController {
    /**
     * 跳轉到增加用戶頁面
     */
    @RequestMapping("/add")
    public String add() {
        return "/user/add";
    }

    /**
     * 跳轉到更新用戶頁面
     */
    @RequestMapping("/update")
    public String update() {
        return "/user/update";
    }
    /**
     * 跳轉到未授權錯誤頁面
     */
    @RequestMapping("/noAuth")
    public String noAuth(){
        return "/noAuth";
    }

    /**
     * 跳轉到登陸頁面
     */
    @RequestMapping("/toLogin")
    public String toLogin() {
        return "login";
    }

    /**
     * 登陸邏輯處理
     * UsernamePasswordToken是一個簡單的包含username及password即用戶名及密碼的登錄驗證用token,
     * 這個類同時繼承了HostAuthenticationToken及RememberMeAuthenticationToken,
     * 因此UsernamePasswordToken能夠存儲客戶端的主機地址以及確定token是否能夠被後續的方法使用。
     * 值得注意的是在這裏password是以char數組的形式存儲的,這樣設計是因爲String本身是不可變的,
     * 這可能會導致無效的密碼被訪問到。
     * 另外,開發者應在驗證之後調用clear方法清除token從而杜絕該token在後續被訪問的可能性。
     */
    @RequestMapping("/login")
    public String login(String username, String password, Model model, HttpSession session) {
        /**
         * 使用shiro編寫認證操作
         */
        //1、獲取subject
        Subject subject = SecurityUtils.getSubject();
        //2、獲取用戶數據
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        //3、執行登陸方法
        try {
            subject.login(token);
            //沒有異常就是登陸成功,轉發到主頁面
            session.setAttribute("username",username);
            return "redirect:/test";
        } catch (UnknownAccountException e) {
            //任何異常,都代表着登陸失敗
            // UnknownAccountException:這個異常代表用戶名不存在
            model.addAttribute("msg", "用戶名不存在");
            return "login";
        } catch (IncorrectCredentialsException e) {
            //IncorrectCredentialsException:代表着密碼不正確
            model.addAttribute("msg", "密碼錯誤");
            return "login";
        }

    }


    /**
     * 測試方法
     */
    @RequestMapping("/hello")
    @ResponseBody
    public String hello() {
        System.out.println("UserController.hello()");
        return "ok";
    }

    /**
     * 測試thymeleaf
     */
    @RequestMapping("/test")
    public String testThymeleaf(Model model) {

        //返回test.html
        return "test";
    }
}

7、實體類

package com.zicheng.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private Integer id;
    private String  name;
    private String  password;
    private String  perms;
}

8、前端頁面

在這裏插入圖片描述

test.html

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

歡迎您!<b><span th:text="${session.username}"></span></b>
<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>

login.html

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>登陸</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            font-family: sans-serif;
            background: #34495e;
        }

        .box {
            width: 300px;
            padding: 40px;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: #191919;
            text-align: center;
        }

        .box h1 {
            color: white;
            text-transform: uppercase;
            font-weight: 500;
        }

        .box input[type="text"],
        .box input[type="password"] {
            border: 0;
            background: none;
            display: block;
            margin: 20px auto;
            text-align: center;
            border: 2px solid #3498db;
            padding: 14px 10px;
            width: 200px;
            outline: none;
            color: white;
            border-radius: 24px;
            transition: 0.25s;
        }

        .box input[type="text"]:focus,
        .box input[type="password"]:focus {
            width: 280px;
            border-color: #2ecc71;
        }

        .box input[type="submit"] {
            border: 0;
            background: none;
            display: block;
            margin: 20px auto;
            text-align: center;
            border: 2px solid #2ecc71;
            padding: 14px 40px;
            outline: none;
            color: white;
            border-radius: 24px;
            transition: 0.25s;
        }

        .box input[type="submit"]:hover {
            background: #2ecc71;
        }
    </style>
</head>

<body>
    <form class="box" action="/login" method="post">
        <h1>Login</h1>
        <h4 th:text="${msg}" style="color: red"></h4>
        <input type="text" name="username" placeholder="Username">
        <input type="password" name="password" placeholder="Password">
        <input type="submit"  value="Login">
    </form>
</body>
</html>

noAth.html(錯誤信息的頁面)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>未授權提示頁面</title>
</head>
<body>
<div align="center">
    <h1>未經授權,無法訪問該頁面:</h1>
    <h3><a href="/test">返回主頁面</a></h3>
    <h3><a href="/toLogin">返回登錄頁面</a></h3>
</div>
</body>
</html>

add.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>用戶添加頁面</title>
</head>
<body>
<h1>用戶添加頁面</h1>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>用戶更新頁面</title>
</head>
<body>
<h1>用戶更新頁面</h1>
</body>
</html>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章