shiro框架从入门到实战

当我学完Springboot的基本用法之后,我们可以很快速很熟练的对某某数据库中的某某表进行CRUD(增,删,改,查),虽然CRUD是一个管理系统的主要内容,但是权限验证,访问系统前必须登录,不同用户有不同的权限,注销等操作,也是必不可少的。虽然使用过滤器,拦截器等操作,我们也可以实现上述功能,但是框架已经帮我们封装好了相关的拦截器,和过滤器来实现上述功能,因此掌握 shiro这个框架,就能简单快速的实现上述功能了

什么是Shiro

是一款主流的 Java 安全框架,不依赖任何容器,可以运行在 Java SE和 Java EE 项目中,它的主要作用是对访问系统的用户进行身份认证、授权、会话管理、加密等操作。
身份认证:例如,你只有登录了才能进行访问,判断用户是否是登录状态
(在之前MVC框架中,我们常用的是用过滤器来判断它是否有某个session,没有session的话,就让其登录
授权:例如,你必须拥有某些权限才能去访问某些模块

Shiro 就是用来解决安全管理的系统化框架。

Shiro的核心组件

  1. UsernamePasswordToken,Shiro 用来封装用户登录信息,使用用户的登录信息来创建令牌 Token。
  2. SecurityManager,Shiro 的核心部分,负责安全认证和授权。
  3. Suject,Shiro 的一个抽象概念,包含了用户信息。
  4. Realm,开发者自定义的模块,根据项目的需求,验证和授权的逻辑全部写在 Realm 中。
  5. AuthenticationInfo,用户的角色信息集合,认证时使用。
  6. AuthorzationInfo,角色的权限信息集合,授权时使用。
  7. DefaultWebSecurityManager,安全管理器,开发者自定义的Realm 需要注入到 DefaultWebSecurityManager 进行管理才能生效。
  8. ShiroFilterFactoryBean,过滤器工厂,Shiro 的基本运行机制是开发者定制规则,Shiro 去执行,具体的执行操作就是由ShiroFilterFactoryBean 创建的一个个 Filter 对象来完成。

用户,角色,权限 三者之间的关系
一般情况下,我们是给用户赋予角色,,给角色赋予权限,
在这里插入图片描述

Shiro 的运行机制如下图所示。

在这里插入图片描述

Shiro实现登录验证

结合一个Springboot项目来学习Shiro是如何实现登录验证的

Springboot项目搭建

导入项目的依赖

主要导入的依赖有,mysql(用来连接数据库),mybatispuls(用来简化SQL的一个工具,也可以用JPA来代替,或者直接自己写SQL语句),shiro(本次项目的主角),web依赖相关(web项目来展示效果),Lombook工具

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.southwind</groupId>
    <artifactId>springboot-shiro</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-shiro</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.5.3</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.1.tmp</version>
        </dependency>

        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

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

</project>
进行相关配置相关的配置文件,主要是一下数据库和日志的相关配置。
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8&serverTimezone=GMT%2B8

  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

整体项目结构如下:
在这里插入图片描述

建立数据库
--
-- Table structure for table `account`
--

DROP TABLE IF EXISTS `account`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `account` (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(20) DEFAULT NULL,
  `password` varchar(20) DEFAULT NULL,
  `perms` varchar(20) DEFAULT NULL,
  `role` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `account`
--

LOCK TABLES `account` WRITE;
/*!40000 ALTER TABLE `account` DISABLE KEYS */;
INSERT INTO `account` VALUES (1,'zs','123123','',''),(2,'ls','123123','manage','administrator'),(3,'ww','123123','','');
/*!40000 ALTER TABLE `account` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
entity实体类,与数据库进行对应
package com.southwind.entity;

import lombok.Data;

@Data
public class Account {
    private Integer id;
    private String username;
    private String password;
    private String perms;
    private String role;
}

:正常情况下,我们是有两个表的,一个是用户表(用户表里面就会说明这个用户是什么角色例如:它是普通用户,还是管理员用户),另外一个表就是权限表(权限表里面会说明,一个角色拥有哪些权限,例如普通用户就只有查看的权限,管理员用户就有增删改查的权限。
这里我将两个表合并了

Dao层(对应的是mapper包),这里直接采用的是mybatisplus,来简化SQL语句的书写,也可以用SpringDataJPA

代码如下:

package com.southwind.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.southwind.entity.Account;
import org.springframework.stereotype.Repository;

@Repository
public interface AccountMapper extends BaseMapper<Account> {

}
Service层
  1. 先定义个接口
package com.southwind.service;

import com.southwind.entity.Account;

public interface AccountService {
    public Account findByUsername(String username);
}
  1. 再定义接口实现类
package com.southwind.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.southwind.entity.Account;
import com.southwind.mapper.AccountMapper;
import com.southwind.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountMapper accountMapper;

    @Override
    public Account findByUsername(String username) {
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.eq("username",username);
        return accountMapper.selectOne(wrapper);
    }
}
Controller层
package com.southwind.controller;

import com.southwind.entity.Account;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class AccountController {

    @GetMapping("/{url}")
    public String redirect(@PathVariable("url") String url){
        return url;
    }

    @PostMapping("/login")
    public String login(String username, String password, Model model){
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        try {
            subject.login(token);
            Account account = (Account) subject.getPrincipal();
            subject.getSession().setAttribute("account",account);
            return "index";
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            model.addAttribute("msg","用户名错误!");
            return "login";
        } catch (IncorrectCredentialsException e){
            model.addAttribute("msg","密码错误!");
            e.printStackTrace();
            return "login";
        }
    }

    @GetMapping("/unauth")
    @ResponseBody
    public String unauth(){
        return "未授权,无法访问!";
    }

    @GetMapping("/logout")
    public String logout(){
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "login";
    }
}
简单页面展示

定义五个简单的页面

  1. index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="shortcut icon" href="#"/>
</head>
<body>
    <h1>index</h1>
    <div th:if="${session.account != null}">
        <span th:text="${session.account.username}+'欢迎回来!'"></span><a href="/logout">退出</a>
    </div>
    <a href="/main">main</a> <br/>
    <div shiro:hasPermission="manage">
        <a href="manage">manage</a> <br/>
    </div>
    <div shiro:hasRole="administrator">
        <a href="/administrator">administrator</a>
    </div>
</body>
</html>
  1. login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="shortcut icon" href="#"/>
</head>
<body>
    <form action="/login" method="post">
        <table>
            <span th:text="${msg}" style="color: red"></span>
            <tr>
                <td>用户名:</td>
                <td>
                    <input type="text" name="username"/>
                </td>
            </tr>
            <tr>
                <td>密码:</td>
                <td>
                    <input type="password" name="password"/>
                </td>
            </tr>
            <tr>
                <td>
                    <input type="submit" value="登录"/>
                </td>
            </tr>
        </table>
    </form>
</body>
</html>
  1. main.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="shortcut icon" href="#"/>
</head>
<body>
    <h1>main</h1>
</body>
</html>
  1. manage.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="shortcut icon" href="#"/>
</head>
<body>
    <h1>manage</h1>
</body>
</html>
  1. administator.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="shortcut icon" href="#"/>
</head>
<body>
    <h1>administator</h1>
</body>
</html>
环境测试

启动Springboot项目后,访问localhost:8080即可进入下面的页面
在这里插入图片描述
此时,在没有配置shiro之前,所有的链接,如main,manage,administrator,点击后都是可以访问的。
在配置好环境后,shiro主角就要登场了。

Shiro的配置

  1. Shiro到底要帮我们实现什么呢?
    Shiro要帮我们实现
自定义AccountReam

Realm,开发者自定义的模块,根据项目的需求,验证和授权的逻辑全部写在 Realm 中。
代码如下:

package com.southwind.realm;

import com.southwind.entity.Account;
import com.southwind.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.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashSet;
import java.util.Set;

public class AccoutRealm extends AuthorizingRealm {

    @Autowired
    private AccountService accountService;

    /**
     * 授权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取当前登录的用户信息
        Subject subject = SecurityUtils.getSubject();
        Account account = (Account) subject.getPrincipal();

        //设置角色
        Set<String> roles = new HashSet<>();
        roles.add(account.getRole());
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);

        //设置权限
        info.addStringPermission(account.getPerms());
        return info;
    }


    /**
     * 认证
     * 即登录相关事件交于这个重写的方法里面
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //这个authenticationToken是哪里来的呢??
        //会登录的接口,我们可以发现在登录时,我们封装了一个token
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        Account account = accountService.findByUsername(token.getUsername());
        if(account != null){
        	//当密码验证,即密码错误时,shiro会自己抛出一个密码错误的异常
        	//getName()这个参数是当前realm的名称
            return new SimpleAuthenticationInfo(account,account.getPassword(),getName());
        }
        //即用户名不存在,shiro框架自己会抛出一个用户不存在异常
        return null;
    }
}

到这里,reaml就已经写完了,这样它会生效吗??
不,这个它根本不能生效,我们还要把它配置到一个配置类中它才会生效的

写一个配置类
package com.southwind.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.southwind.realm.AccoutRealm;
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.Hashtable;
import java.util.Map;

//@Configuration
public class ShiroConfig {

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);
        //权限设置
        Map<String,String> map = new Hashtable<>();
        map.put("/main","authc"); // 访问main的时候一定要是登录的一个状态
        //下面两个是基于登录的,因为只有在登录之后才可能有权限和角色
        map.put("/manage","perms[manage]");//访问manage的时候,一定要有一个manage权限才能去访问
        map.put("/administrator","roles[administrator]");//。。。,一定要是administrator角色才可以访问。
        factoryBean.setFilterChainDefinitionMap(map);
        //设置登录页面
        factoryBean.setLoginUrl("/login");
        //设置未授权页面
        factoryBean.setUnauthorizedUrl("/unauth");
        return factoryBean;
    }


    @Bean
    public DefaultWebSecurityManager securityManager(@Qualifier("accoutRealm") AccoutRealm accoutRealm){
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(accoutRealm);
        return manager;
    }
	
    @Bean
    public AccoutRealm accoutRealm(){
        return new AccoutRealm();
    }
	//这个是用来整合thymeleaf的
    @Bean
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }
}

构建流程梳理
除去整合Thymeleaf注入的一个bean,这个配置类中只有3个bean,这3个bean是我们在前文中提到的shiro的核心组件,这个3个组件什么关系呢??
**首先是AccoutRealm ,它是我们自定义的模块,根据项目的需求,验证和授权的逻辑全部写在 Realm 中,这个逻辑应该放在DefaultWebSecurityManager,应为DefaultWebSecurityManager是负责安全认证和授权的。最后呢,DefaultWebSecurityManager应该放在ShiroFilterFactoryBean中,因为ShiroFilterFactoryBean它是真正处理这里逻辑的底层。

常用的认证和授权规则
  • 认证过滤器
    anon:无需认证。
    authc:必须认证。
    authcBasic:需要通过 HTTPBasic 认证。
    user:不一定通过认证,只要曾经被 Shiro 记录即可,比如:记住我。
  • 授权过滤器
    perms:必须拥有某个权限才能访问。
    role:必须拥有某个角色才能访问。
    port:请求的端口必须是指定值才可以。
    rest:请求必须基于 RESTful,POST、PUT、GET、DELETE。
    ssl:必须是安全的 URL 请求,协议 HTTPS。

Shiro框架与Spring Security框架

这篇文章,我们可以看到这个Shiro还是比较简单入手的,但是随着微服务和Springboot的发展,Spring Security越来越走向安全框架的中心,下一篇文章一起来学习Spring Security吧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值