Shiro

1、权限的5张表

一个管理系统,不同身份的人登录后,操作的权限(访问的功能)是不同的

Shiro SpringSecurity 都是解决授权(判断权限)、认证(登录)的框架

权限:能访问的功能。

通常设计一张表,用于存储系统中的所有功能

权限表:Permission

在这里插入图片描述

在这里插入图片描述
【注意】如果直接为每个用户去分配具体的权限,但是比较繁琐,需要做重复性的分配

角色:是具有相同权限的一个称呼

角色表:role

在这里插入图片描述
【问题】如何给每个角色分配具体的权限?

角色权限对应表:每个角色都对应哪些权限。

role_permission

在这里插入图片描述

用户表:用户注册的基本信息

在这里插入图片描述

用户角色对应表:把每个用户对应的角色进行关联

在这里插入图片描述
在这里插入图片描述
查询每个用户对应的角色以及权限信息

2、搭建shiro环境

1、新建maven项目
2、添加jar依赖

<packaging>war</packaging>
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.4.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.0.5</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.2</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp.jstl</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.6.0</version>
    </dependency>
</dependencies>

3、创建启动类

package com.qf.shiro2205;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.qf.shiro2205.dao") //让mybatis扫描此包,扫描到所有接口,mybatis就生成接口的代理类,并创建代理对象,放于bean容器中
public class ShiroApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShiroApplication.class,args);
    }
}

4、创建application.yml配置文件

配置连接池、mybatis的相关信息

在这里插入图片描述

server:
  port: 8081
spring:
  application:
    name: Shiro2205
  datasource:   # 配置连接池信息
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://localhost:3306/java2205
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
    druid:
      initial-size: 10   #连接池中连接的核心连接数量
mybatis:
  type-aliases-package: com.qf.shiro2205.pojo
  mapper-locations: mybatis/*.xml
  configuration:
    use-generated-keys: true
    map-underscore-to-camel-case: true

5、实体类的生成

6、编写登录的dao

public interface UserDao {
    User login(String userId);
}

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.shiro2205.dao.UserDao">
    <select id="login" parameterType="string" resultType="User">
        select id,userId,pwd,realName,phone
        from `user`
        where userId=#{userId}
    </select>
</mapper>

7、编写根据用户Id查询角色的dao

public interface RoleDao {
    List<Role> findRolesByUserId(String userId);
}

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.shiro2205.dao.RoleDao">
    <select id="findRolesByUserId" parameterType="string" resultType="Role">
        SELECT r.roleId,r.roleName
        from `user` u
        INNER JOIN user_role ur on u.userId=ur.userId
        INNER JOIN role r on ur.roleId=r.roleId
        where u.userId=#{userId}
    </select>
</mapper>

8、编写根据角色id查询对应的权限

public interface PermissionDao {
    List<Permission> findPermissionsByRoleId(int roleId);
}

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.shiro2205.dao.PermissionDao">
    <select id="findPermissionsByRoleId" parameterType="int" resultType="Permission">
        SELECT p.permissionId,p.permissionName,p.permissionUrl
        from role_permission rp,permission p
        where rp.permissionId=p.permissionId
        AND
        rp.roleId=#{roleId}
    </select>
</mapper>

3、shiro的体系结构

由三部分组成
在这里插入图片描述
1、Realm: 与数据库的关联层,负责登录的数据库查询及角色、权限的查询调用

2、SecurityManager 安全管理器 核心

1、Realm: 与数据库的关联层,负责登录的数据库查询及角色、权限的查询调用

2、SecurityManager  安全管理器   核心

​		1、认证:判断密码是否正确

 	    2、授权:存储当前登录用户的角色、权限信息

​		3、鉴权:当当前用户进行网络请求时,拦截该请求,判断是否有该请求的权限

​                如果有:放行

​                如果没有:则返回,抛出无权限的异常

​		4、session的管理功能

​		5、记住我,下次自动登录

​		6、密码加密的功能

​		7、登出功能

​		8、以另一种身份登录的功能

3、Subject 登录功能

4、自定义Realm

通过Realm做两件事情:

1、认证:通过调用登录方法,把用户的账号及密码信息返回给SecurityManager

2、授权:通过调用 查询角色 、查询权限方法,把用户的角色、权限存储给SecurityManager

package com.qf.SpringBootApplication.realms;

import com.qf.SpringBootApplication.dao.PermissionDao;
import com.qf.SpringBootApplication.dao.RoleDao;
import com.qf.SpringBootApplication.dao.UserDao;
import com.qf.SpringBootApplication.pojo.Permission;
import com.qf.SpringBootApplication.pojo.Role;
import com.qf.SpringBootApplication.pojo.User;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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.springframework.beans.factory.annotation.Autowired;

import java.util.List;

public class MyRealm extends AuthorizingRealm {
    @Autowired
    private UserDao userDao;
    @Autowired
    private RoleDao roleDao;
    @Autowired
    private PermissionDao permissionDao;

    @Override
//    授权:查询登录账号的角色,权限信息
//    再认证方法认证通过后(账号,密码都正确的时候)才调用此授权方法
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//    1.通过形式参数principalCollection获取账号信息
        String userId = principalCollection.getPrimaryPrincipal().toString();
//    2.创建一个SimpleAuthorizationInfo对象用来存储该用户的角色,权限
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//    3.根据账号查询对应的角色信息
        List<Role> roles = roleDao.findRolesByUserId(userId);
//    4.遍历角色集合,在便利过程中赋值权限,同时查询每一个角色的权限信息
        for (Role role : roles) {
//          把当前角色赋值给simpleAuthorizationInfo
            simpleAuthorizationInfo.addRole(role.getRoleName());
//          根据角色信息查询权限信息
            List<Permission> permissions = permissionDao.findPermissionsByRoleId(role.getRoleId());
//          遍历权限集合,把每一个权限赋值给simpleAuthorizationInfo对象
            for (Permission permission : permissions) {
//              把权限赋值给simpleAuthorizationInfo对象
                simpleAuthorizationInfo.addStringPermission(permission.getPermissionUrl());
            }
        }
//        经过二层循环,已经把角色,权限赋值给了simpleAuthorizationInfo
//        把simpleAuthorizationInfo对象返回给
        return simpleAuthorizationInfo;
    }

    @Override
//    认证:登录
//    SecurityManager先调用此认证方法
//    参数authenticationToken:里面封装着登录用户的账号
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//        1.先通过参数authenticationToken获取账号信息
        Object userId = authenticationToken.getPrincipal();
//        2.判断账号信息是否为空
        if (userId == null) {
//       把null值返回给SecurityManager,然后SecurityManager就以报异常的方式提示用户,账号或密码是空的
            return null;
        }
//        3.调用dao的login方法查询用户信息
        User user = userDao.login(String.valueOf(userId));
//        声明对象:用于存储登录用户的密码信息等
        SimpleAuthenticationInfo simpleAuthenticationInfo = null;
//        4.判断查询的账户是否存在
        if (user == null) {
//            说明登录账号是错误的
            return null;
        } else {
            //把查询到的密码封装后返回给securityManager即可
            //在账号正确的情况下创建simpleAuthenticationInfo对象,用于封装查询到的密码信息
            //参数1:登录的账号
            //参数2:从数据库根据账号查询到的密码
            //参数3:当前realm的名字
            simpleAuthenticationInfo =
                    new SimpleAuthenticationInfo(userId, user.getPwd(), getName());
        }
        //return给SecurityManager对象,自动判断密码是否正确
        //如果正确,继而调用上面的授权方法
        //如果错误,以报异常的方式返回给登录的调用处
        return simpleAuthenticationInfo;
    }
}

创建config包,建一个配置类

package com.qf.SpringBootApplication.config;

import com.qf.SpringBootApplication.realms.MyRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration  //当前类就是一个配置类 相当于spring的配置文件
public class ShiroConfig {
    @Bean
    @ConditionalOnMissingBean  //如果此方法需要传参,到bean工厂中寻找参数类型的对象,进行传参
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        //创建一个代理对象,去控制shiro
        DefaultAdvisorAutoProxyCreator
                defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
        //设置此代理对象理解生效
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }
    //创建自定义的realm对象
    @Bean
    public MyRealm createMyRealm(){
        return  new MyRealm();
    }
    //创建SecurityManager对象
    @Bean
    public SecurityManager createSecurityManager(){
        DefaultWebSecurityManager securityManager=
                new DefaultWebSecurityManager();
        //关联自定义Realm
        securityManager.setRealm(createMyRealm());
        return securityManager;
    }
    //配置shiro的拦截规则
    //拦截所有网络请求
    @Bean
    @ConditionalOnMissingBean
    public ShiroFilterFactoryBean createShiroFilterFactoryBean(SecurityManager securityManager){
        //把过滤器拦截到的请求,转移到参数securityManager中
        //创建过滤器
        ShiroFilterFactoryBean shiroFilterFactoryBean=
                new ShiroFilterFactoryBean();
        //设置此过滤器shiroFilterFactoryBean把拦截的请求转移到securityManager中
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //声明一个map集合,存放拦截、放行规则等
        Map<String,String> map=new HashMap<String, String>();
        //设置登出功能
        //key是请求的路径  value是关键字
        //配置登出
        map.put("/logout","logout"); //logout securitymanager 就清空session,跳转到登录页面
        //配置可以匿名访问的请求:无需登录就可以访问的
        map.put("/user/register","anon"); //anon  代表可以匿名访问
        map.put("/user/login","anon");
        //企业的请求都需要认证(登录)后才能访问
        map.put("/**","authc");//authc  需要认证、鉴权才能访问
        //把集合里配置的内容赋值给过滤器
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

        //配置登录页面的名字
        shiroFilterFactoryBean.setLoginUrl("/login.jsp");
        //配置主页的名字
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //设置当权限不足时,报异常,跳转的页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/error");
        return shiroFilterFactoryBean;
    }
    //接收Realm中授权方法返回的角色、权限,赋值给SecurityManager
    @Bean
    @ConditionalOnMissingBean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor
                authorizationAttributeSourceAdvisor=
                new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

添加视图解析器

server:
  port: 8081  # 端口号
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource   # Druid连接池
    url: jdbc:mysql://localhost:3306/db2205 # 数据库连接地址
    username: root # mysql账号
    password: admin # mysql 密码
    driver-class-name: com.mysql.jdbc.Driver #驱动
    druid:
      initial-size: 3 # 连接池中初始化连接的数量
  mvc:
    view:
      prefix: /
      suffix: .jsp
mybatis: #mybatis的配置
  configuration:
    map-underscore-to-camel-case: true
  mapper-locations: classpath:mappers/*.xml
  type-aliases-package: com.qf.SpringBootApplication.pojo

author.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
权限不足
</body>
</html>

error.jsp


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
出错了
</body>
</html>

index.jsp


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
项目主页
</body>
</html>

login.jsp


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录</title>
</head>
<body>
<form method="post" action="user/login">
    <p>
        账号:<input type="text" name="userId">
    </p>
    <p>
        密码:<input type="password" name="pwd">
    </p>
    <p>
        <input type="submit" value="登录">
    </p>
</form>
</body>
</html>

Shiro工具类

package com.qf.SpringBootApplication.util;

import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class ShiroAdvice {
    @ExceptionHandler
    public String doException(AuthorizationException ex){
        //只捕获权限不足的异常
        System.out.println("权限不足");
        return "redirect:author.jsp";
    }
}

控制层controller

DeptController

package com.qf.SpringBootApplication.controller;

import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/dept")
public class DeptController {

    @RequiresPermissions("dept/add")
    @GetMapping("/add")
    public String add(){
        System.out.println("调用了dept/add");
        return "调用了dept/add请求";
    }
    @RequiresPermissions("dept/update")
    @GetMapping("/update")
    public String update(){
        System.out.println("调用了dept/update");
        return "调用了dept/update";
    }
    @RequiresPermissions("dept/delete")
    @GetMapping("/delete")
    public String delete(){
        System.out.println("调用了dept/delete");
        return "调用了dept/delete";
    }
    @RequiresPermissions("dept/query")
    @GetMapping("/query")
    public String query(){
        System.out.println("调用了dept/query");
        return "调用了dept/query请求";
    }
}

EmpController

package com.qf.SpringBootApplication.controller;

import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/emp")
public class EmpController {
    @RequiresPermissions("emp/add")
    @GetMapping("/add")
    public String add(){
        return "调用了emp/add请求";
    }

    @RequiresPermissions("emp/edit")
    @GetMapping("/edit")
    public String edit(){
        return "调用了emp/edit请求";
    }

    @RequiresPermissions("emp/query")
    @GetMapping("/query")
    public String query(){
        return "调用了emp/query请求";
    }

    @RequiresPermissions("emp/exist")
    @GetMapping("/exist")
    public String exist(){
        return "调用了emp/exist请求";
    }
}

UserController

package com.qf.SpringBootApplication.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/user")
public class UserController {

    @PostMapping("/login")
    public String login(String userId,String pwd){
        System.out.println("账号:"+userId);
        System.out.println("密码:"+pwd);
        //1、把账号、密码必须封装在token中
        UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken(userId,pwd);
        //2、获取Subject对象
        Subject subject= SecurityUtils.getSubject();
        try {
            //3、调用登录方法
            subject.login(usernamePasswordToken);
            //如果登录认证的过程失败,抛出异常
        }catch (UnknownAccountException ex){
            System.out.println("账号不存在");
            return "error";
        }catch (AuthenticationException ex){
            System.out.println("密码错误");
            return "error";
        }catch (Exception ex){
            System.out.println("发生异常,信息如下:"+ex.getMessage());
            return "error";
        }
        return "index";
    }
}

网页访问:http://localhost:8081/login.jsp
测试登录功能
在这里插入图片描述
登录成功

在这里插入图片描述
测试访问权限,测试成功(因为zym用户,具备了5678的员工执行功能)

在这里插入图片描述
在这里插入图片描述
如果我访问dept部门中的方法会超出权限

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值