SpringBoot-22-整合Shiro

1. Apache Shiro简介


1.1 什么是Apache Shiro?

  • 它是一个强大灵活的强大开源安全框架,可以干净利落地处理身份认证,授权,企业会化管理和加密。

  • Apache Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。

  • Apache Shiro的首要目标就是易于使用和理解,在可能的情况下掩盖复杂性,并公开一个干净直观的API,以简化开发人员确保其应用程序安全的工作。

1.2 Apache Shiro的特点:

在这里插入图片描述

根据官网我们可以知道:

Shiro主要关心的四个方面:

  • 认证(authenticate):有时也称登录,对用户的身份进行认证。
  • 授权(authorize):访问控制的过程,确定谁有权访问什么。
  • 会话管理:管理特定用户的会话,即使在非Web程序或EJB的应用程序也是如此。
  • 密码学: 使用加密算法确保数据安全,且容易使用。

Shiro还支持其他的功能,对于不同环境的应用程序提供支持和加强这些问题:

  • Web支持,Shiro的Web支持API有助于保护Web应用程序.
  • 缓存: 缓存能够确保安全操作并保持快速高效。
  • 并发性: 通过并发功能支持多线程应用程序。
  • 测试: 测试支持旨在帮助您编写单元和集成测试,并确保您的代码按预期受到保护。
  • “记住我”: 记住用户在会话中的身份,以便他们只需要在强制登录时登录。
  • 运行方式" 允许用户采用其他用户的身份(如果允许)的功能,有时在管理方案中很有用。

1.3 Apache Shiro的架构

Apache Shiro的设计目标是通过直观和易于使用来简化应用程序安全性。Apache Shiro在几乎任何应用程序中都保持直观且易于使用。
在这里插入图片描述
Shiro的体系结构有三个主要概念:

  • 主题(Subject): 本质上是当前正在执行的用户,也可以是第三方服务,守护程序或者cron作业,与软件交互的东西。
  • 安全管理器(SecurityManager): Shiro的核心,协调其内部安全组件,为任何安全操作完成所有繁重的工作。
  • 连接数据(Realm):作为Shiro和应用程序安全的桥梁,用于用户身份认证和授权。Realm本质上是一个特定于安全的DAO:封装了数据源的连接详细信息,并将需要将相关数据提供给Shiro,至少需要指定一个Realm用于认证和授权。

详细架构
在这里插入图片描述
讲解部分属性:

  • Authenticator (org.apache.shiro.authc.Authenticator) 是负责执行和响应用户身份验证(登录)尝试的组件
  • 认证策略 (org.apache.shiro.authc.pam.AuthenticationStrategy) 如果配置了多个Realm,则将协调 Realm 数据库以确定身份验证尝试成功或失败的条件
  • Authorizer (org.apache.shiro.authz.Authorizer) 是负责确定用户在应用程序中的访问控制的组件
  • SessionManager (org.apache.shiro.session.mgt.SessionManager) 知道如何创建和管理用户生命周期,为所有环境中的用户提供强大的会话体验
  • SessionDAO (org.apache.shiro.session.mgt.eis.SessionDAO) 代表 执行持久性 (CRUD) 操作
  • CacheManager (org.apache.shiro.cache.CacheManager) 创建和管理其他 Shiro 组件使用的实例生命周期。

1.4 下载

进入官网: shiro官网

找到10分钟教程,然后能看到下载,

要求:

我们的电脑要有jdk1.8以上版本,Maven版本要在3.0.3+

官方建议的下载地址

1.5 研究quickStart

使用下载的Shiro,使用Intellij IDEA找到下载的shiro.zip包,点进samples/quickstart

在这里插入图片描述

查看一下这个文件配置了些什么:

    1. 导入的依赖
<dependencies>
		<!-- shiro 核心依赖 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
        </dependency>

        <!-- configure logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    1. 配置了log4j2.xml,实现了日志
<Configuration name="ConfigTest" status="ERROR" monitorInterval="5">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Logger name="org.springframework" level="warn" additivity="false">
            <AppenderRef ref="Console"/>
        </Logger>
        <Logger name="org.apache" level="warn" additivity="false">
            <AppenderRef ref="Console"/>
        </Logger>
        <Logger name="net.sf.ehcache" level="warn" additivity="false">
            <AppenderRef ref="Console"/>
        </Logger>
        <Logger name="org.apache.shiro.util.ThreadContext" level="warn" additivity="false">
            <AppenderRef ref="Console"/>
        </Logger>
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

    1. 多了个shiro.ini

shiro.ini是shiro的配置文件,用于生成安全管理器。

查看源码quickstart

    private static final transient Logger logger = LoggerFactory.getLogger(QuickStartShiro.class);
		

transient 一些敏感的信息,为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输, 这些信息的变量应该加上transient关键字

   //通过ini配置文件工厂获取SecurityManager对象工厂
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //获取SecurityManager实例
        SecurityManager securityManager = factory.getInstance();

        //这里使用SecurityManager作为一个单例,实际上不这样做
        SecurityUtils.setSecurityManager(securityManager);

通过ini配置文件来创建一个shiro安全管理器实例,ini配置文件里面包含了realms,users,roles和perssions内容。

查看一下shiro.ini

在这里插入图片描述
其中分为两类,一类是登录的用户【users】,一类是角色身份【roles】

【users】:包含账户,密码,角色
【roles】:包含对某些资源的权限

 //在工具类中获取一个Subject,Subject可以是用户,第三方服务 ,cron
        Subject currentUser = SecurityUtils.getSubject();

Security需要我们去给与一个SecurityManage,然后我们可以去通过getSubject()来获取一个当前正在执行的用户。

进行会话操作:

进行会话的时候不需要web环境:这也证实了可以在几乎任何应用程序上使用。

//可以通过用户获取一个Session对象

            Session session = currentUser.getSession();
            //给Session设置属性
            session.setAttribute("liang","hello");
            String  value = (String) session.getAttribute("liang");
            if(value.equals("hello"))
            {
                logger.info("消息为:"+value);
            }

授权和认证

既然是安全性框架,自然离不开授权和认证,在Shiro中是通过令牌实现的UsernamePasswordToken

 //判断用户是否被认证
        if(!currentUser.isAuthenticated())
        {
            //定义一个认证指令
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            //开启RememberMe默认为false
            token.setRememberMe(true);
            try {
                //登录功能,以我们的认证指令为参数
                currentUser.login(token);
            } catch (UnknownAccountException uae) {
                logger.info("用户不存在 " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                logger.info("密码不正确" + token.getPrincipal());
            } catch (LockedAccountException lae) {
                logger.info("用户已经锁定" + token.getPrincipal());
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
            }
        }

通过令牌来登录,验证正在执行的用户是否认证,以及实现了记住我功能,都通过subject来实现操作

授权
判断用户是否拥有某种身份或者是有某些权利

//        若用户已经认证了,则输出认证信息
        logger.info("用户"+currentUser.getPrincipal()+"成功登录了!");

//        判断用户是否有某种角色
        if(currentUser.hasRole("schwartz"))
        {
            logger.info("你拥有schwartz身份");
        } else {
            logger.info("不好意思");
        }
        //粗粒度
        if(currentUser.isPermitted("lightsaber:wield"))
        {
            logger.info("You may use a lightsaber ring.  Use it wisely.");
        } else {
            logger.info("Sorry, lightsaber rings are for schwartz masters only.");
        }
        //细粒度
        //a (very powerful) Instance Level permission:
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            logger.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            logger.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        //用户注销
        currentUser.logout();

进行授权操作,判断谁有权对什么进行操作

2.整合Shiro

2.1 环境准备

导入的坐标如下

    <dependencies>


<!--        Thymeleaf 环境-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
<!--        Web环境-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</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>

<!--        整合Shiro的第三方模块-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-web-starter</artifactId>
            <version>1.8.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>



<!--        Mysql的Jar包-->
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>

<!--        数据源:Druid-->
        <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>

<!--        整合Mybaits-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>


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

    </dependencies>

2.2 导入静态文件

添加的配置文件:包含一个登录页面,以及首页,访问成功页面

login.html

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

  <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
  <style>
    html,
    body {
      height: 100%;
    }

    body {
      display: -ms-flexbox;
      display: -webkit-box;
      display: flex;
      -ms-flex-align: center;
      -ms-flex-pack: center;
      -webkit-box-align: center;
      align-items: center;
      -webkit-box-pack: center;
      justify-content: center;
      padding-top: 40px;
      padding-bottom: 40px;
      background-color: #f5f5f5;
    }

    .form-signin {
      width: 100%;
      max-width: 330px;
      padding: 15px;
      margin: 0 auto;
    }
    .form-signin .checkbox {
      font-weight: 400;
    }
    .form-signin .form-control {
      position: relative;
      box-sizing: border-box;
      height: auto;
      padding: 10px;
      font-size: 16px;
    }
    .form-signin .form-control:focus {
      z-index: 2;
    }
    .form-signin input[type="email"] {
      margin-bottom: -1px;
      border-bottom-right-radius: 0;
      border-bottom-left-radius: 0;
    }
    .form-signin input[type="password"] {
      margin-bottom: 10px;
      border-top-left-radius: 0;
      border-top-right-radius: 0;
    }

  </style>
</head>
<body class="text-center">
    <form class="form-signin" th:action="@{/login}" method="post">
        <img class="mb-4" src="https://getbootstrap.com/docs/4.0/assets/brand/bootstrap-solid.svg" alt="" width="72" height="72">
        <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
        <label for="inputUsername" class="sr-only">Username</label>
        <input type="username" id="inputUsername" name="username" class="form-control" th:placeholder="#{login.username}" required autofocus>
        <label for="inputPassword" class="sr-only">Password</label>
        <input type="password" id="inputPassword"  name="password" class="form-control" th:placeholder="#{login.password}" required>
        <div class="checkbox mb-3">
          <label>
            <input type="checkbox" value="remember-me" th:text="#{login.remember}">
          </label>
        </div>
        <span class="mt-5 mb-3 text-muted" th:text="${msg}" style="color:red"></span>
        <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>

        <p class="mt-5 mb-3 text-muted">0 2020-2021</p>
      </form>
      </body>
    </div>
</body>
</html>

这里记得配置i18n配置文件,类似于如下

login.btn=登录
login.password=请输入密码
login.remember=记住我
login.tip=登录界面
login.username=请输入用户名

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>Liang</title>
</head>
<body>

<h1>首页</h1>

<!-- 通过thymeleaf判断-->

<a th:href="@{/toLogin}">登录</a>


<a th:href="@{/toLogout}">注销 </a>


<a th:href="@{/user/add}">add</a>

<a th:href="@{/user/delete}">delete</a>


</body>
</html>

add.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Liang</title>
</head>
<body>

<h1>add</h1>
</body>
</html>

delete.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Liang</title>
</head>
<body>

<h1>delete</h1>
</body>
</html>

2.3编写Shiro核心代码

  1. 自定义Realm类
    在com.liang.config包下创建一个UserRealm类

@Slf4j
public class UserRealm extends AuthorizingRealm {


    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.info("进入授权信息阶段");
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("进入认证信息阶段");
		return null;
    }

需要使用继承AuthorizingRealm,重写授权和认证两个方法。

  1. Shiro配置文件的类
    在com.liang.config包下创建一个ShiroConfig来编写Shiro的配置
    代码:

@Configuration
public class ShiroConfig {


    //定义拦截的东西
    //ShiroFilterFactoryBean :(3)
    @Bean("shiroFilterFactoryBean")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager1") DefaultWebSecurityManager securityManager)
    {
        //创建一个ShiroFilterFactoryBean对象
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        return  shiroFilterFactoryBean;
    }


    //DefaultWebSecurityManager(2)

    @Bean(name ="securityManager1")
    public DefaultWebSecurityManager webSecurityManager(@Qualifier("userRealm")UserRealm realm)
    {
        //创建默认网络安全管理器对象
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //这是一个RealmSecurityRealm对象
        securityManager.setRealm(realm);
        return securityManager;
    }


    //Real对象,连接数据   //自定义Real对象,需要继承AuthorizingRealm(1)
    @Bean("userRealm")
    public UserRealm userRealm()
    {
        return new UserRealm();
    }

    //整合ShiroDialect : 用来整合shiro-thymeleaf
    @Bean
    public ShiroDialect shiroDialect()
    {
        return new ShiroDialect();
    }
}


  1. 编写Controller类
    在com.liang.controller包下创建一个ShiroController类
   @Slf4j
@Controller
public class ShiroController {


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

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


    //登录页面
    @RequestMapping("/toLogin")
    public String toLogin()
    {
        return "login";
    }
}

此时项目基本操作已经搭建好啦,但没有进行认证和授权操作

2.4 Shiro整合Mybaits

1.编写application.yaml

spring:
  datasource:
    username: root
    password: root
    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

mybatis:
  type-aliases-package: com.liang.pojo
  mapper-locations: classpath:mapper/*.xml



2.编写pojo

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

    private int id;
    private String name;
    private String pwd;
    private String perms;       //权限
}

4.编写Mapper

Mapper接口

package com.liang.mapper;

import com.liang.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper
@Repository
public interface UserMapper {

    //获取指定用户
    User getUserByID(int id );

    //获取全部用户
    List<User> getUsers();

    //添加用户
    int addUser(User user);

    //删除用户
    int deleteUser(int id);

    //更新用户
    int updateUser(User user);

    //通过名字查询
    User getUserByName(String name);
}

Mapper接口

<?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.liang.mapper.UserMapper">

    <!--    SQL片段-->

    <sql id="items">
        id,name,pwd,perms
    </sql>

    <!-- 去除多余的,号-->
    <sql id="key">
        <trim suffixOverrides=",">
            <if test="id!=0">
                id,
            </if>
            <if test="name!=null and name!=''">
                name,
            </if>
            <if test="pwd!=null and pwd!=''">
                pwd,
            </if>
            <if test="perms!=null and perms!=''">
                perms,
            </if>
        </trim>
    </sql>
    <!--  用于插入,更加灵活-->
    <sql id="value">
        <trim suffixOverrides=",">
            <if test="id!=0">
                #{id},
            </if>
            <if test="name!=null and name!=''">
                #{name},
            </if>
            <if test="pwd!=null and pwd!=''">
                #{pwd},
            </if>
            <if test="perms!=null and perms!=''">
                #{perms},
            </if>
        </trim>
    </sql>


<!--    查询用户-->
    <select id="getUserByID" resultType="User">
        select  <include refid="items"></include> from user where id = #{id}
    </select>

    <select id="getUserByName" resultType="User">
        select  <include refid="items"></include> from user where name = #{name}
    </select>

    <select id="getUsers" resultType="User">
        select  <include refid="items"></include> from user
    </select>


    <!--添加用户-->
    <insert id="add" parameterType="User" >
        insert into `article`(<include refid="key"></include>)
        values(<include refid="value"></include>)
    </insert>

<!--    更新用户-->
    <update id="updateUser" parameterType="User" >
    update user
    <set>
        <if test="name!=null and name!=''">
            name=#{name},
        </if>
        <if test="pwd!=null and pwd!=null">
            pwd=#{pwd},
        </if>
        <if test="perms!=null and perms!=null">
            perms=#{perms},
        </if>
    </set>
    <where>
        <if test="id!=0">
            id=#{id}
        </if>
    </where>
    </update>

<!--    删除用户 -->
    <delete id="deleteUser" parameterType="int">
        delete from user
        <where>
            <if test="id!=0">
                id=#{id}
            </if>
        </where>
    </delete>

</mapper>

4.编写service

service接口

package com.liang.service;

import com.liang.pojo.User;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public interface UserService {
      //获取指定用户
      public User getUserByID(int id );
      //获取全部用户
      public List<User> getUsers();

      //添加用户
      public int addUser(User user);

      //删除用户
      public int deleteUser(int id);

      //更新用户
      public int updateUser(User user);
      //通过名字查询
      public  User getUserByName(String name);
}

service实现类

package com.liang.service;

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

import java.util.List;

@Service("userService")
public class UserServiceImpl implements UserService{

    @Autowired
    private UserMapper mapper;

    @Override
    public User getUserByID(int id) {
       return  mapper.getUserByID(id);
    }

    @Override
    public List<User> getUsers() {
        return mapper.getUsers();
    }

    @Override
    public int addUser(User user) {
        return mapper.addUser(user);
    }

    @Override
    public int deleteUser(int id) {
        return mapper.deleteUser(id);
    }

    @Override
    public int updateUser(User user) {
        return mapper.updateUser(user);
    }

    @Override
    public User getUserByName(String name) {
        return mapper.getUserByName(name);
    }
}

通过测试认证看是否能够运行成功,数据库文件需自行编写。

2.5 认证和授权

编写Shiro核心文件

添加访问过滤条件

//定义拦截的东西
    //ShiroFilterFactoryBean :(3)
    @Bean("shiroFilterFactoryBean")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager1") DefaultWebSecurityManager securityManager)
    {
        //创建一个ShiroFilterFactoryBean对象
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        System.out.println(shiroFilterFactoryBean.isSingleton());
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //添加shiro的内置过滤器
        /*
            anon : 无须认证就可以访问
            authc: 必须认证了才可以访问
            user: 只有记住我才可以访问
            perms: 拥有某种资源的权限才可以访问
            roles: 拥有某种角色才可以访问
         */

        //过滤请求 这里我们使用字符串来表达我们的拦截规则,

        Map<String, String> map = new LinkedHashMap<>();

//        map.put("/user/add","authc");
//        map.put("/user/delete","authc");

        //用户拥有某些资源的权限才可以访问,未登录保证调到控制页面
        map.put("/user/add","perms[user:add]");
        map.put("/user/delete","perms[user:delete]");

        //只有认证了才可以登录
        map.put("/user/*","authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);


        //设置首页
        shiroFilterFactoryBean.setLoginUrl("/toLogin");

        //设置登录成功页面
        shiroFilterFactoryBean.setSuccessUrl("/index");

        //设置未经授权不可访问跳转的页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");


        return  shiroFilterFactoryBean;
    }

在ShiroFilterFactoryBean中添加Shiro过滤器过滤的条件,对资源的访问进行认证和权限要求,通过setFilterChainDefinitionMa()进行设置。

编写Controller

    //登录请求
    @RequestMapping("/login")
    public String login(String username, String password,Model model)
    {
        //从securityUtils下获取一个Subject对象,表示当前用户
        Subject subject = SecurityUtils.getSubject();
        //定义一个令牌
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        try {
            //用户登录的时候需要令牌
            subject.login(token);
        }catch (UnknownAccountException e) //用户不存在
        {
            log.info("用户名错误");
            model.addAttribute("msg","用户名不存在");
            return "login";
        }catch (IncorrectCredentialsException e)
        {
            log.info("密码错误");
            model.addAttribute("msg","密码错误");
            return "login";
        } catch (LockedAccountException lae ) {
            log.info("账号是被锁定的");
            model.addAttribute("msg","账号是被锁定的");
            return "login";
        }

        return "/index";
    }

通过当前执行用户进行令牌登录,来认证登录请求,交给Realm去验证处理

Realm认证

 @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("进入认证信息阶段");
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        //查询数据库中是否有此用户
        User user = userService.getUserByName(token.getUsername());
        if(user == null)
        {
            return null;
        }
        token.setRememberMe(true);       //令牌记住我

        //获得当前用户
        Subject currentUser = SecurityUtils.getSubject();
        Session session = currentUser.getSession();
        //给当前Session存一个对象
        session.setAttribute("loginUser",user);

        //AuthenticationInfo 是接口 ,我们返回它的实现类 SimpleAuthenticationInfo是用来简单认证信息的
        //principal获取当前用户的认证 ,credentials 密码的对象 realmName 认证名
        //可以加密,默认为简单加密 使用md5盐值加密
//        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("",user.getPwd(),"");
        //添加user认证信息,方便我们授权操作
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,user.getPwd(),"");
        return simpleAuthenticationInfo;
    }

通过验证账户是否存在,从数据库中取,然后密码交给Shiro来处理

Realm授权

  @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.info("进入授权信息阶段");

        //权限操作
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //添加准许权限,硬编码
//        info.addStringPermission("user:add");
          //从数据库中取,需要获取数据库的对象
        Subject subject = SecurityUtils.getSubject();
        //获取认真信息
        User currentUser = (User) subject.getPrincipal();


        //设置相应查询用户的权限
        info.addStringPermission(currentUser.getPerms());

        return info;
    }

进行访问资源认证,来判断用户是否有某种资源权限,权限从数据库中取

Controller实现无法访问资源的请求和注销请求

  //权限不够页面
    @RequestMapping("/noAuth")
    @ResponseBody
    public String unauthorized()
    {
        return "未授权无法访问!";
    }

    //注销
    @RequestMapping("/toLogout")
    public String loginOut()
    {
        //得到当前用户
        Subject subject = SecurityUtils.getSubject();
        //注销
        subject.logout();
        return "login";
    }

实现用户注销,用户需要重新登录才能访问,提供了更友好的无法访问的页面

2.6 Shiro整合Thymeleaf

<!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>Liang</title>
</head>
<body>

<h1>首页</h1>

<!-- 通过thymeleaf判断-->
<div th:if="session.loginUser==null">
    <a th:href="@{/toLogin}">登录</a>
</div>

<div th:if="session.loginUser!=null">
    <a th:href="@{/toLogout}">注销 </a>
</div>

<div shiro:hasPermission="user:add">
    <a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPerssion="user:delete">
    <a th:href="@{/user/delete}">delete</a>
</div>

</body>
</html>

Shiro整合Thymeleaf,需要添加如下标记xmlns:shiro = "http://www.thymeleaf.org/thymeleaf-extras-shiro",实现更好的体验感,给出客户能看见的页面,无关的页面不给出。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值