SpringBoot与Shiro整合

1.Shiro框架简介

Apache Shiro是一个强大且易用的权限管理框架,执行身份验证、授权、密码管理和会话管理等

1-1.Shiro 核心组件:

  • UsernamePasswordToken,Shiro 用来封装用户登录信息,使用用户的登录信息创建令牌 Token,登录的过程即 Shiro 验证令牌是否具有合法身份以及相关权限。
  • SecurityManager,Shiro 的核心部分,负责安全认证与授权。
  • Subject,Shiro 的一个抽象概念,包含了用户信息。
  • Realm,开发者自定义的模块,根据项目的需求,验证和授权的逻辑在 Realm 中实现。
  • AuthenticationInfo,用户的角色信息集合,认证时使用。
  • AuthorizationInfo,角色的权限信息集合,授权时使用。
  • DefaultWebSecurityManager,安全管理器,开发者自定义的 Realm 需要注入到 DefaultWebSecurityManager 进行管理才能生效。
  • ShiroFilterFactoryBean,过滤器工厂,Shiro 的基本运行机制是开发者定制规则,Shiro 去执行,具体的执行操作就是由 ShiroFilterFactoryBean 创建一个个 Filter 对象来完成。

 1-2.Apache Shiro 认证流程:

 

  1. Subject:即当前用户,在权限管理的应用程序里往往需要知道谁能够操作什么,谁拥有操作该程序的权利,shiro中则需要通过Subject来提供基础的当前用户信息,Subject 不仅仅代表某个用户,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。Subject自己不会实现相应的身份验证/授权逻辑,而是通过DelegatingSubject委托给SecurityManager实现。
  2. SecurityManager:即所有Subject的管理者,这是Shiro框架的核心组件,可以把他看做是一个Shiro框架的全局管理组件,用于调度各种Shiro框架的服务.
  3. Realms:Realms则是用户的信息认证器和用户的权限认证器,我们需要自己来实现Realms来自定义的管理我们自己系统内部的权限规则。
  4. authenticator:认证器,主体进行认证最终通过authenticator进行。

 2.搭建springBoot环境——前后的未分离

        具体请参考:springBoot整合mybatis带日志输出

        本次用到的数据库表:test_shiro.sql

       2-1 . 使用idea创建项目时添加web、thymeleaf、mybatis-framewoek、MySQL-driver依赖,注意:这里使用的不是最新版的springBoot,使用的是Spring Boot 1.5.4.RELEASE和Shiro1.4.0

还需导入这些依赖,否侧运行异常:

        <dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.23</version>
		</dependency>
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-api</artifactId>
			<version>5.7.1</version>
			<scope>test</scope>
		</dependency>

        接下来导入spring整合shiro的依赖和Shiro 整合 Thymeleaf的依赖:

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>1.2.1</version>
        </dependency>

2-2.编写mapper层及mapper映射文件

        mapper接口:

@Mapper
public interface UserMapper {
    //根据用户名查询用户
    User findUserByName(@Param("name") String name);
    //根据用户id查询用户的权限
    List<String> findPermsById(@Param("userId") Integer userId);
}

         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="cn.shiro.mapper.UserMapper">
    <!--根据用户名查询用户相关信息-->
    <select id="findUserByName" resultType="cn.shiro.pojo.User">
        select *
        from user
        where username = #{name}
    </select>
    <!--根据用户id查询用户的权限-->
    <select id="findPermsById" resultType="java.lang.String">
        SELECT `permission`
        FROM `user`
                 INNER JOIN `user_role`
                            ON `user`.`user_id` = `user_role`.`user_id`
                 INNER JOIN `role`
                            ON `user_role`.`role_id` = `role`.`role_id`
                 INNER JOIN `role_perssion`
                            ON `role`.`role_id` = `role_perssion`.`role_id`
                 INNER JOIN `permission`
                            ON `role_perssion`.`perssion_id` = `permission`.`permission_id`
        WHERE `user`.`user_id` = #{userId};
    </select>
</mapper>

2-3.编写service及serviceImpl

        service接口:

package cn.shiro.service;
import cn.shiro.pojo.User;
import java.util.List;

public interface UserService {
    //根据用户名查询用户信息
    User findUserByName(String name);
    //查询用户权限
    List<String> findPermsById(Integer userId);
}

         serviceImpl:

package cn.shiro.service.impl;
import cn.shiro.mapper.UserMapper;
import cn.shiro.pojo.User;
import cn.shiro.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper um;
    @Override
    public User findUserByName(String name) {
        return um.findUserByName(name);
    }

    @Override
    public List<String> findPermsById(Integer userId) {
        return um.findPermsById(userId);
    }
}

2-4.自定义Realm类——调用service

        继承AuthorizingRealm,然后实现getAuthenticationInfogetAuthorizationInfo方法,来完成登录和权限的验证逻辑:

package cn.shiro.shiros;


import cn.shiro.pojo.User;
import cn.shiro.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;

public class UserRealm extends AuthorizingRealm {
    //注入service
    @Autowired
    private UserService us;

    //授权方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    //认证方法
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //客户端传来的 username 和 password 会自动封装到 authenticationToken
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //根据 username 和 password调用service进行查询
        User user = us.findUserByName(token.getUsername());
        //判断根据用户名和密码查询的用户是否存在
        if (user != null) {
            //如果返回不为 null,则表示用户名正确,再验证密码,直接返回  SimpleAuthenticationInfo 对象即可
            return new SimpleAuthenticationInfo(user, user.getPassword(),user.getUsername());
        }
        //不存在返回null将由安全管理器处理
        return null;
    }
}

2-5.编写Shiro配置类(*)

        自定义过滤器创建完成之后,需要进行配置才能生效,在 Spring Boot 应用中,不需要任何的 XML 配置,直接通过配置类进行装配

  • 该类是Spring为Shiro框架提供的一个整合类
  • 作用:对前端请求进行过滤,判断该请求是否需要登录/权限才可以访问

这个配置类中一共自动装配了 4个 bean 实例

  • 最后一个@Bean(name="userRealm")用于将上方我们编写的Realm类定义在容器中,由下一个方使用,所有的验证和授权逻辑全部定义在这个 bean 中。
  • 第二个@Bean(name="securityManager"),并且将 MyRealm 注入到 DefaultWebSecurityManager bean 中,完成注册。
  • 第一个 bean ShiroFilterFactoryBean,这是 Shiro 自带的一个 Filter 工厂实例,所有的认证和授权判断都是由这个 bean 生成的 Filter 对象来完成的,这就是 Shiro 框架的运行机制,开发者只需要定义规则,进行配置,具体的执行者全部由 Shiro 自己创建的 Filter 来完成,它可以实现对哪些请求或访问进行验证与授权
package cn.shiro.config;

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

//shiro的配置类
@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        //创建过滤器工厂
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //配置安全管理器
        //将注入的securityManager()配置到过滤器工厂中
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //下面配置认证与授权规则:
        /*
          认证过滤器:
            anon:无需认证即可访问,游客身份。
            authc:必须认证(登录)才能访问。
            authcBasic:需要通过 httpBasic 认证。
            user:不一定已通过认证,只要是曾经被 Shiro 记住过登录状态的用户就可以正常发起请求,比如 rememberMe。
          授权过滤器:
            perms:必须拥有对某个资源的访问权限(授权)才能访问。
            roles:必须拥有某个角色权限才能访问。
            port:请求的端口必须为指定值才可以访问。
            rest:请求必须是 RESTful,method 为 post、get、delete、put。
            ssl:必须是安全的 URL 请求,协议为 HTTPS。
         */
        Map<String, String> filterMap = new LinkedHashMap<>();
        //认证:访问index必须要认证
        filterMap.put("/index", "authc");
        //认证:这里需要对login进行放行,否则表单无法提交到controller
        filterMap.put("/login", "anon");
        //认证:设置所有请求要需要验证
        //filterMap.put("/*", "authc");
        //授权:访问add操作页面需要user:add权限
        filterMap.put("/add", "perms[user:add]");
        //授权:访问update操作页面需要user:update权限
        filterMap.put("/update", "perms[user:update]");

        //没有通过认证需要返回login页面进行登录验证
        shiroFilterFactoryBean.setLoginUrl("/toLogin");
        //没有权限将跳转未授权提示页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/toNoAuth");

        //将上方配置好的认证与授权规则添加到过滤器中
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 创建DefaultWebSecurityManager
     * 安全管理器,相当于相当于SpringMVC中的DispatcherServlet
     * 所有的交互都通过SecurityManager进行控制,它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。
     */
    @Bean(name = "securityManager") //由上方的方法参数中注入
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联realm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    /**
     * 创建Realm(用于跟数据库交互,查询用户是否有权限等)
     */
    @Bean(name = "userRealm") //由上方的方法参数注入
    public UserRealm getRealm() {
        return new UserRealm();
    }

    @Bean
    public ShiroDialect getShiroDialect() {
        return new ShiroDialect();
    }
}


2-6.编写controller层

import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class TestController {
    //测试
    @RequestMapping("/index")
    public String index(Model model){
        model.addAttribute("name","张三");
        return "test";
    }

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

    //跳转未授权页面
    @GetMapping("/toNoAuth")
    public String toNoAuth(){
        return "noAuth";
    }
    
    //用户登录
    @PostMapping("/login")
    public String update(String name,String password,Model model){
        //使用Shiro编写认证操作,步骤如下:
        //1.使用Shiro提供的工具类获取Subject(可以理解为一个用户)
        Subject subject = SecurityUtils.getSubject();
        //2.封装用户的数据 用户名和密码 到token中
        UsernamePasswordToken token = new UsernamePasswordToken(name, password);
        //3.执行登录方法,底层思路,只要subject.login(token)方法执行没有异常则认为是登陆成功
        try {
            subject.login(token);
            //登录成功
            //跳转到test.html 测试页面
            return "redirect:/index";
        } catch (UnknownAccountException e) {
            //e.printStackTrace();
            //登录失败:用户名不存在
            model.addAttribute("msg", "用户名或密码错误");
            return "login";
        }catch (IncorrectCredentialsException e) {
            //e.printStackTrace();
            //登录失败:密码错误
            model.addAttribute("msg", "密码错误");
            return "login";
        }
    }
}

2-7.在resources/templates下编写相关页面,作为测试使用

如图:

        login.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>用户登录</title>
    </head>
    <body>
        <h1>登录页面</h1>
        <form method="post" action="login">
            用户名:<input type="text" name="name"/><br/>
            密码:<input type="password" name="password"/><br/>
            <input type="submit" value="登录"/>
        </form>
        <h3 th:text="${msg}"></h3>
    </body>
</html>

         noAuth.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>无权限</title>
</head>
<body>
    <h1>您没有权限访问该页面</h1>
</body>
</html>

        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>Title</title>
    </head>
    <body>
        <h3 th:text="${name}"></h3>
        进入用户添加功能: <a href="add">用户添加</a><br/>
        进入用户更新功能: <a href="update">用户更新</a><br/>
        <a href="/logout">登出</a>
    </body>
</html>

        add.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Title</title>
</head>
<body>
    <h1>用户添加</h1>
</body>
</html>

        uodate.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Title</title>
</head>
<body>
    <h1>用户修改</h1>
</body>
</html>

2-8.启动项目

        首先测试直接访问index,看请求是否被拦截:

        回车后,会跳转到login页面,并没有直接跳转test页面,说明认证是生效的,因为我们并没有登录,所以无法直接访问test页面 

         接下来进行登录,故意输错用户名或密码,目的是为了测试TestController中的用户登录方法是否正常执行:

 接下来根据提供的数据库表中存在的用户进行登录,是否登录成功:

 到此说明我们的认证和登录验证是没问题的!

2-9.接下来就完善授权问题

        2-9-1:实现UserRealm中的授权方法

        授权逻辑:通过shiro提供的SecurityUtils工具类获取当前登录的用户,然后通过该用户的id查询当前这个用户有哪些权限,关于具体的mapper与service如何实现的上方代码已写好,不在描述,最后将查询到的权限封装到授权中,返回授权对象,具体代码如下:

//授权方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //给资源进行授权
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //获取当前登录用户
        Subject subject = SecurityUtils.getSubject();
        User user = (User) subject.getPrincipal();
        //根据当前用户的id查询该用户有哪些权限
        List<String> perms = us.findPermsById(user.getUserId());
        //打印查询到的权限
        System.out.println(perms);
        //传入权限
        info.addStringPermissions(perms);
        return info;
    }

         2-9-2:在配置类我们已经对访问/add和/update进行的授权规则,只允许有user:add和user:update权限的用户进行访问:

        2-9-3:启动项目,访问相应的操作页面

        这里根据登录不同的用户进行测试访问,有权限的可以访问/add和/update,无权限的会跳转提示页面,同时控制台也会打印出不同用户持有的权限:

 这里很明显,只有zhangsan是没有添加和更新的权力

2-10.最后实现登出功能

         2-10-1.在配置类中添加一个Bean  

    @Bean // 该bean用于登出时调用 
    public ShiroDialect getShiroDialect() {
        return new ShiroDialect();
    }

        2-10-2.在controller中添加对应的请求方法

    //登出
    @GetMapping("/logout")
    public String logout() {
        //获取当前登录用户的对象
        Subject subject = SecurityUtils.getSubject();
        //退出时将当前登录的对象信息进行清空
        subject.logout();
        return "login";
    }

退出之后再直接访问localhost:8081/index是访问不进去的,会跳转登录界面进行验证登录

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值