Spring Boot整合Shiro实现认证与授权

8 篇文章 0 订阅
1 篇文章 0 订阅

SpringBoot整合Shiro实现认证与授权

简介

什么是shiro?

Shiro是一个强大的简单易用的Java安全框架,主要用来更便捷的认证,授权,加密,会话管理。Shiro首要的和最重要的目标就是容易使用并且容易理解。

首先明白几个概念:shiro三个核心组件

  • Subject : 用户主体,即当前操作用户(它把操作交给SecurityManager)
    所有Subject都需要SecurityManager,当你与Subject进行交互,这些交互行为实际上被转换为与SecurityManager的交互。
  • SecurityManager : 安全管理器或安全管理员(它关联 Realm)
    Shiro架构的核心,它就像Shiro内部所有原件的保护伞。然而一旦配置了SecurityManager,SecurityManager就用到的比较少,开发者大部分时间都是和Subject打交道。
  • Realm : 是Shiro连接数据的桥梁
    充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行==认证 (登录) ==和 ==授权(访问控制)==验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
shiro能做什么?

在这里插入图片描述

shiro认证工作流程:

在这里插入图片描述
认证流程:
在这里插入图片描述
授权流程:
在这里插入图片描述
Shiro相关类介绍
(1)Authentication :认证 ---- 用户登录
(2)Authorization :授权 — 用户具有哪些权限
(3)Cryptography :安全数据加密
(4)Session Management :会话管理
(5)Web Integration :web系统集成
(6)Interations: 集成其它应用,spring、缓存框架

Shiro 特点
(1)易于理解的 Java Security API;
(2)简单的身份认证(登录),支持多种数据源(LDAP,JDBC,Kerberos,ActiveDirectory 等);
(3)对角色的简单的签权(访问控制),支持细粒度的签权;
(4)支持一级缓存,以提升应用程序的性能;
(5)内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境;
(6)异构客户端会话访问;
(7)非常简单的加密 API;
(8)不跟任何的框架或者容器捆绑,可以独立运行

SpringBoot整合Shiro实现认证与授权验证

第一步:pom文件导入相关依赖
        <!--shiro与spring整合依赖-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>

        <!--导入mybatis相关的依赖-->
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--springboot的mybatis启动器-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!--这里数据源使用:德鲁伊-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.9</version>
        </dependency>
        
        <!--导入thymeleaf模板,使用模板页面进行测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
第二步,编写一个shiro配置类

因为shiro有三个核心的组件,并且shiro主要通过过滤器进行拦截请求来实现安全控制。因此,编写一个配置类依次创建对应的bean对象,放入spring容器。

/**
 * shiro的配置类
 */
@Configuration
public class ShiroConfig {
    /**
     * 创建ShiroFilterFactoryBean
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器(关联SecurityManager)
        shiroFilterFactoryBean.setSecurityManager(securityManager);

    //============== 开始配置拦截:==================
        //1.添加Shiro内置过滤器
        /**
         * Shiro内置过滤器,可以实现权限相关的拦截器。用于拦截需要权限才能访问资源的请求。
         *  常用的过滤器:
         *      anon: 无需认证(登录)就可以访问。允许匿名身份访问
         *      authc: 必须认证才可以访问
         *      user: 如果使用rememberMe的功能可以直接访问
         *      perms: 该资源必须得到资源权限才可以访问
         *      role: 该资源必须得到角色权限才可以访问
         */
        Map<String,String> filterMap = new LinkedHashMap<String,String>();
        //添加认证过滤器
        //filterMap.put("/api/add","authc");
        //filterMap.put("/api/update","authc");
        filterMap.put("/api/testThymeleaf","anon"); // 放行该请求。
        filterMap.put("/api/login","anon"); // 放行该请求。

        //添加授权过滤器
        filterMap.put("/api/add","perms[user:add]");// 需要权限才能访问。并且要放在拦截所有的上面才有效。
        filterMap.put("/api/update","perms[user:update]");

        filterMap.put("/api/*","authc"); // 拦截/api/所有请求
        //authc模式拦截后默认跳转到login.jsp页面,无论有没有该页面。
        // 修改调整的登陆页面
        shiroFilterFactoryBean.setLoginUrl("/api/toLogin");

        //设置未授权提示页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/api/unAuthorized");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
    //====================================结束==========================================
        return shiroFilterFactoryBean;
    }

    /**
     * 创建DefaultWebSecurityManager
     */
    @Bean(name="securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联Realm
        securityManager.setRealm(userRealm);
        return securityManager;
    }
    /**
     * 创建Realm
     * 我们可以先自定义一个Realm类
     */
    @Bean(name = "userRealm")
    public UserRealm getRealm(){
        return new UserRealm();
    }
}

第三步,自定义Realm类:一定要继承AuthorizingRealm类,并重写两个方法
public class UserRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;
    /**
     * 执行授权逻辑
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权逻辑");
                //给资源进行授权
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //从数据库中获取授权字符串
        Subject subject = SecurityUtils.getSubject();

        //subject.getPrincipal()获取的实际上是认证逻辑中return new SimpleAuthenticationInfo(user,user.getPassword(),"")第一个参数
        User user = (User) subject.getPrincipal();
        User dbUser = userService.findByName(user.getName());
        //添加资源的授权字符串
        info.addStringPermission(dbUser.getPerm());
        return info;
    }

    /**
     * 执行认证逻辑
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行认证逻辑");
        //编写shiro判断逻辑,判断用户名和密码
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //数据库中的username:admin和 password:123456
        User user = userService.findByName(token.getUsername());
        //1.判断用户名
        if (user==null){
            //用户名不存在
            return null;  // shiro底层会抛出UnknowAccountException异常;
        }

        //2.判断密码。我们只需要返回AuthenticationInfo的一个子类SimpleAuthenticationInfo
        //第一个参数:返回登录逻辑中login方法结果的数据;第二个参数:数据库密码;第三个参数:shiro的名字
        //return new SimpleAuthenticationInfo("",user.getPassword(),"");
        return new SimpleAuthenticationInfo(user,user.getPassword(),"");
    }
}
Controller代码:
@Controller
@RequestMapping("/api")
public class HelloController {
	//测试主页
    @RequestMapping("/testThymeleaf")
    public String testThymeleaf(Model model){
        model.addAttribute("name","SpringBooot整合Shiro");
        return "test";
    }

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

    @RequestMapping("/update")
    public String update(){
        return "/user/update";
    }
    //负责跳转到登陆页面
    @RequestMapping("/toLogin")
    public String toLogin(){
        return "login";
    }

    //负责跳转到未授权页面
    @RequestMapping("/unAuthorized")
    public String unAuthorized(){
        return "/user/unAuthorized";
    }

    //登录处理逻辑
    @RequestMapping("/login")
    public String login(String name,String password,Model model){
        System.out.println("name:"+name+" password: "+password);
        /**
         * 使用Shiro编写认证(登录)操作
         */
        //1,获取Subject
        Subject subject = SecurityUtils.getSubject();

        //2,封装用户数据,生成一个令牌
        UsernamePasswordToken token = new UsernamePasswordToken(name,password);

        //3.执行登录方法
        try {
            /*执行该方法时,会自动执行我们定义的UserRealm里doGetAuthenticationInfo方法认证逻辑代码!
            为什么?通过查看源码 它底层最终会调用到DefaultWebSecurityManager的login()方法,
            而DefaultWebSecurityManager关联了Realm且该方法内使用了AuthenticationInfo对象
            故会执行Realm里的认证逻辑。我们自定义了一个Realm类(UserRealm),重写了认证逻辑和授权逻辑方法。
            */
            subject.login(token);
            //登录成功
            return "redirect:/api/testThymeleaf";
        } catch (UnknownAccountException e) {
            //e.printStackTrace();
            //登录失败
            model.addAttribute("msg","用户名不存在");
            return "login"; //跳回login.html页面。
        } catch (IncorrectCredentialsException e){
            model.addAttribute("msg","密码错误");
            return "login"; //跳回login.html页面。
        }
    }
}

application.properties:

#数据库配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test_shiro?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

#下面两个配置可有可无
#配置别名允许实体类使用别名即不区分大小写
mybatis.type-aliases-package=com.itnet.myspringboot.domain
# mybatis xml文件的加载地址
mybatis.mapper-locations=classpath:com/itnet/myspringboot/mapper/*Mapper.xml

mapper接口文件:

@Mapper
@Repository
public interface UserMapper {
    public User findByName(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">
        
 <!-- namespace必须是UserMapper接口的权限的类目,不然没办法自动绑定 -->
<mapper namespace="com.itnet.myspringboot.mapper.UserMapper">
	<!-- 因为properties文件配置了别名,所以resultType不用写实体类的全限定类名了  -->
    <select id="findByName" parameterType="String" resultType="user">
        select * from user where name = #{name}
    </select>
</mapper>

Service实现类:

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

目录结构:
在这里插入图片描述

测试:

在这里插入图片描述
在这里插入图片描述

点击“添加页面”,跳转到登录页面,我输入admin 123456进行登录
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
数据库里: admin只拥有user:add权限。Jason只拥有user:update权限。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

thymeleaf整合shiro权限标签
 <!--导入thymeleaf模板-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--thymeleaf对shiro的扩展坐标-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

只需在shiro配置类中加上

/**
     * 配置ShiroDialect,用于thymeleaf和shiro标签配合使用
     */
    @Bean
    public ShiroDialect getShiroDialecct(){
        return new ShiroDialect();
    }

在前端页面就可以使用shiro标签了

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>testThymeleaf</title>
</head>
<body>
    <h3 th:text="${name}"></h3>
    <hr/>
	<!--拥有该权限才会显示该内容-->
    <div shiro:hasPermission="user:add">
        <a href="/api/add">添加页面</a>
    </div>
    <!--拥有该权限才会显示该内容-->
    <div shiro:hasPermission="user:update">
        <a href="/api/update">更新页面</a>
    </div>
</body>
</html>

在这里插入图片描述

拓展:

常见权限框架

Shiro

Apache Shiro是Java的一个安全框架。目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的Shiro就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。

Spring Security

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。它是一个轻量级的安全框架,它确保基于Spring的应用程序提供身份验证和授权支持。它与Spring MVC有很好地集成,并配备了流行的安全算法实现捆绑在一起。安全主要包括两个操作“认证”与“验证”(有时候也会叫做权限控制)。“认证”是为用户建立一个其声明的角色的过程,这个角色可以一个用户、一个设备或者一个系统。“验证”指的是一个用户在你的应用中能够执行某个操作。在到达授权判断之前,角色已经在身份认证过程中建立了。

Shiro和Spring Security比较

(1)Shiro比Spring更容易使用,实现和最重要的理解
(2)Spring Security更加知名的唯一原因是因为品牌名称
(3)“Spring”以简单而闻名,但讽刺的是很多人发现安装Spring Security很难
(4)Spring Security却有更好的社区支持
(5)Apache Shiro在Spring Security处理密码学方面有一个额外的模块
(6)Spring-security 对spring 结合较好,如果项目用的springmvc ,使用起来很方便。但是如果项目中没有用到spring,那就不要考虑它了。
(7)Shiro 功能强大、且 简单、灵活。是Apache 下的项目比较可靠,且不跟任何的框架或者容器绑定,可以独立运行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值