SpringBoot07---整合Shiro

1. Shrio简介

1.1 概述

  • Apache Shiro是一个强大且易用的Java安全框架

  • 可以完成身份验证、授权、密码和会话管理

  • 官网: http://shiro.apache.org/

1.2 有哪些功能

Authentication:身份认证/登录,验证用户是不是拥有相应的身份;

Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

Web Support:Web支持,可以非常容易的集成到Web环境;

Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;

Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

Testing:提供测试支持;

Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

1.3 Shiro架构(外部)

Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;

SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;

Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

【也就是说对于我们而言,最简单的一个Shiro应用:】

1. 应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;

2. 我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。

2. 快速入门

2.1 拷贝案例

GitHub地址:shiro/samples/quickstart/

  1. 新建一个 Maven 工程,删除其 src 目录,将其作为父工程

  2. 在父工程中新建一个 Maven 模块

  3. 复制快速入门案例 POM.xml 文件中的依赖 (版本号自选)

<dependencies>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.4.1</version>
    </dependency>

    <!-- configure logging -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>1.7.21</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.21</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>
  1. 把快速入门案例中的 resource 下的log4j.properties 复制下来

  2. 复制一下 shiro.ini 文件(IDEA安装 ini 插件)

  3. 复制一下 Quickstart.java 文件

2.2 分析案例

  1. 通过 SecurityUtils 获取当前执行的用户 Subject
Subject currentUser = SecurityUtils.getSubject();
  1. 通过当前用户拿到 Session
Session session = currentUser.getSession();
  1. 用 Session 存值取值
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
  1. 判断用户是否被认证
if (!currentUser.isAuthenticated())
  1. 执行登录操作
currentUser.login(token);
  1. 打印其标识主体
currentUser.getPrincipal()
  1. 注销
currentUser.logout();

3. SpringBoot 集成 Shiro

3.1 实验准备

【目录结构】

导入依赖

<dependencies>
    <!-- thymeleaf-shiro整合包 -->
    <dependency>
        <groupId>com.github.theborakompanioni</groupId>
        <artifactId>thymeleaf-extras-shiro</artifactId>
        <version>2.0.0</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.2</version>
    </dependency>

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

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.21</version>
    </dependency>

    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>

    <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>

    <!--thymeleaf模板-->
    <dependency>
        <groupId>org.thymeleaf</groupId>
        <artifactId>thymeleaf-spring5</artifactId>
    </dependency>
    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-java8time</artifactId>
    </dependency>

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

    <!--快速生成pojo的方法有关的lombok-->
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    </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>
    <dependency>
        <groupId>org.xmlunit</groupId>
        <artifactId>xmlunit-core</artifactId>
    </dependency>
</dependencies>

前端页面

  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>shiro</title>
</head>
<body>
<h1>首页</h1>
<div th:if="${session.loginUser==null}">
    <a th:href="@{/toLogin}">登录</a>
</div>
<p th:text="${msg}"></p>
<hr/>

<div shiro:hasPermission="ss:mm">
    <a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:update">
    <a th:href="@{/user/update}">update</a>
</div>
<div th:if="${session.loginUser}">
    <p><a th:href="@{/logout}">退出</a></p>
</div>
</body>
</html>
  1. login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>shiro登录</title>
</head>
<body>
<div>
    <p th:text="${msg}" style="color: #ff0000"></p>
    <form method="get" th:action="@{/login}">
        <p>用户名:<input type="text" name="username"></p>
        <p>密  码:<input type="text" name="password"></p>
        <p><input type="submit" value="登录"></p>
    </form>
</div>
</body>
</html>
  1. add.html/update.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>add</h1>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>update</h1>
</body>
</html>

application.yaml

spring:
  datasource:
    username: root
    password: 123456
    #?serverTimezone=UTC解决时区的报错
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖即可,Maven 地址: https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

【数据库使用简单的User(id,name,pwd,perms)】

3.2 编写Shiro配置文件

下面是编写配置文件 Shiro 三大要素

  • subject -> ShiroFilterFactoryBean

  • securityManager -> DefaultWebSecurityManager

  • realm

实际操作中对象创建的顺序 : realm -> securityManager -> subject

@Configuration
public class ShiroConfig {

    //3. ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(securityManager);
        /*
            添加shiro的内置过滤器
            anon:无须认证就可以访问
            authc:必须认证了才可以访问
            perms:拥有对某个资源的权限才能访问
            role:拥有某个角色才可以访问
         */
        Map<String, String> filterMap = new LinkedHashMap();
        //登陆认证拦截:未登录会跳转登陆页面
        filterMap.put("/user/add","anon");
        filterMap.put("/user/update","authc");
        //也可使用通配符*
        //filterMap.put("/user/*","authc");

        //登陆后授权,正常情况下没有授权会跳转到未授权页面
        filterMap.put("/user/add","perms[user:add]");
        filterMap.put("/user/update","perms[user:update]");

        bean.setFilterChainDefinitionMap(filterMap);
        //设置跳转到登录页面
        bean.setLoginUrl("/toLogin");
        //设置到未授权页面
        bean.setUnauthorizedUrl("/noauth");

        return bean;
    }

    //2. DefaultWebSecurityManager
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联UserRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    //1. 创建realm,自定义
    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }

    //shiro 整合thymeleaf
    @Bean
    public ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }
}

3.3 编写UserRealm类(认证授权)

public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    @Override  //授权
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了=>授权doGetAuthorizationInfo");

        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
        //从数据库查权限
        Subject subject = SecurityUtils.getSubject();
        User currentUser = (User) subject.getPrincipal();//其实就是拿认证成功的时候的那个user
        info.addStringPermission(currentUser.getPerms());

        return info;
    }

    @Override   //认证
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
        System.out.println("执行了=>认证doGetAuthorizationInfo");
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        //用户名,密码去数据库取
        User user = userService.queryUserByUsername(userToken.getUsername());
        if (user==null){  //没有这个人
            return null;  //其实就是抛出UnknownAccountException异常
        }
        //之后密码认证,shiro   它自己会做
        SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(user,user.getPwd(),"");
        Subject currentSubject = SecurityUtils.getSubject();
        Session session = currentSubject.getSession();
        session.setAttribute("loginUser",user);
        return info;
    }
}

3.4 MyController测试

@Controller
public class MyController {

    @RequestMapping({"/","/index"})
    public String toIndex(Model model) {
        model.addAttribute("msg","hello,shiro");
        return "index";
    }

    @RequestMapping("/user/add")
    public String toAdd(){
        return "user/add";
    }

    @RequestMapping("/user/update")
    public String toUpdate(){
        return "user/update";
    }

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

    @RequestMapping("/login")
    public String login(String username,String password,Model model){
        //获取当前输入的用户
        Subject subject = SecurityUtils.getSubject();
        //封装用户的数据
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        //登录,没有异常就说明登录成功
        try {
            subject.login(token);
            return "index";
        } catch (UnknownAccountException e) {
            model.addAttribute("msg","用户名错误");
            return "login";
        }catch (IncorrectCredentialsException e){
            model.addAttribute("msg","密码错误");
            return "login";
        }
    }

    //没授权
    @RequestMapping("/noauth")
    @ResponseBody
    public String unauthorized(){
        return "没经授权无法进入";
    }
    //退出
    @RequestMapping("/logout")
    public String logout(){
        Subject currentUser = SecurityUtils.getSubject();
        currentUser.logout();
        System.out.println("退出了");
        return "login";
    }

}
posted @ 2021-08-03 23:03  qi_chao  阅读(64)  评论(0编辑  收藏  举报