springboot-mybatis-shiro集成的步骤及代码解析

目录

shiro

shiro概念:

RBAC

一、 实现步骤:

1.1、创建数据库表:

1.2、创建springboot项目

1.3、导入pom依赖

1.4、改配置

 1.5、 核心配置类

1.6、创建Realm域对象

1.7、测试接口

1.8、其它


shiro

Shiro的功能包括以下几点:

  • Authentication: 认证 判断是否为合法用户 有时被称为 "登录”,这是证明用户合法
  • Authorization: 授权 验证某个已认证的用户是否拥有某个权限
  • Session Management: session会话管理
  • Cryptography: 安全数据加解密
  • Web Support: 支持 web
  • Caching: 支持缓存
  • Concurrency: 支持并发
  • Testing: 支持测试
  • "Run As": 用其他用户登录
  • "Remember Me": 记住我

shiro概念:

中枢管理中心:DefaultWebSecurityManager类,我们所自定义的方法对象都会添加到这个管理中心去,会由它来调控我们所定义的方法从而代替系统自带的默认认证方法。其次是Realm对象,它翻译过来叫做"",是由它充当了 Shiro 与应用安全数据间的“桥梁”或者“连接器”。我们所自定义的方法需要写在继承了AuthorizingRealm的类中,才能添加到管理中心去。最后是Subject,通常我们会将Subject对象理解为一个用户,同样它也有可能是一个三方程序,它是一个抽象的概念,可以理解为任何与系统交互的“东西”都是Subject。

名词解释:

Realm : 域,提供数据的组件

  • getAuthencationInfo 入参 Token 出参 Info
  • Info 有 Principal + Credentials

AuthenticationToken:认证令牌,认证入参

AuthenticationInfo: 认证信息,用户信息

AuthorizationInfo: 授权信息

Principal: 经过认证的用户

Credentials: 凭证=密码

RBAC

role based access control 基于角色的访问控制

授权基于角色实现

role 角色

permission 权限

user 用户

都是多对多关系,所以需要建立关联表

一、 实现步骤:

1.1、创建数据库表:

        上图中的五个表,并插入数据

create table edu_user(
    user_id int auto_increment primary key ,
    username varchar(256) not null comment '用户名',
    password varchar(256) not null comment '密码',
    status varchar(32) not null comment '状态 NORMAL | LOCKED | CANCELD'
)comment '用户表';

create table edu_user_role(
    user_role_id int auto_increment primary key ,
    user_id int not null comment '用户id',
    role_id int not null comment '角色id'
)comment '用户角色关联表';

create table edu_role(
    role_id int auto_increment primary key ,
    role_name varchar(32) not null comment '角色'
)comment '角色表';

create table edu_role_perm(
    role_perm_id int auto_increment primary key ,
    role_id int not null comment '角色id',
    perm_id int not null comment '权限id'
)comment '角色权限关联表';

create table edu_perm(
    perm_id int auto_increment primary key ,
    perm_name varchar(32) not null comment '权限名'
)comment '权限表';
#插入用户数据
insert into edu_user values
    (default,'zhangsan','wohenshuai','NORMAL'),
    (default,'wangwu','wohenchou','NORMAL'),
    (default,'lisi','wohenku','NORMAL'),
    (default,'maliu','wohenliu','NORMAL'),
    (default,'tianqi','wohenniu','NORMAL');
#插入角色数据
insert into edu_role values
    (default,'headmaster'),
    (default,'teacher');
#插入用户角色关联数据
insert into edu_user_role values
    (default,1,1),
    (default,2,2),
    (default,3,2),
    (default,4,2),
    (default,5,2);
#插入权限数据
insert into edu_perm values
    (default,'fireEmployee'),
    (default,'raiseSalary'),
    (default,'recess'),
    (default,'teach'),
    (default,'fireStudent'),
    (default,'manageStudents');
#插入角色权限关联数据
insert into edu_role_perm values
    (default,1,1),
    (default,1,2),
    (default,1,3),
    (default,2,4),
    (default,2,5),
    (default,2,6);

1.2、创建springboot项目

1.3、导入pom依赖

<properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-web-starter</artifactId>
            <version>1.4.1</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </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>
        <!--集成mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>
        <!--数据库-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.5</version>
        </dependency>
        <!--日志-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!--代码生成器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.1</version>
            <scope>test</scope>
        </dependency>
        <!--日志打印-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
    </dependencies>

1.4、改配置

       配置文件application.yml

#shiro默认跳转登录页面设置
shiro:
  loginUrl:
    /login.html

  #设置端口号
server:
  port:
    8081
spring:

  #数据源
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/edu?characterEncoding=utf8&useSSL=false&serverTimezone=UTC
    password: 12345678
    username: root

  #日期格式转换
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  mvc:
    format:
      date: yyyy-MM-dd HH:mm:ss

#mybatis配置
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl

    map-underscore-to-camel-case: true
  type-aliases-package: com.woniuxy.shiroboot.model
  mapper-locations: classpath:mappers/**/*.xml

#日志配置
logging:
  level:
    com.woniuxy.boo1: debug

 1.5、 核心配置类

import com.woniuxy.shiroboot.components.ShiroRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SecurityConfig {
    /**
     * 自定义realm 提供数据源的,用来存储数据库获取的对象
     * @return
     */
    @Bean
    public Realm shiroRealm(){
        return new ShiroRealm();
    }

    /**
     * shiro的过滤器链
     * @return
     */
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition(){
        //shiro默认的shiro过滤器链定义
        DefaultShiroFilterChainDefinition sfcd = new DefaultShiroFilterChainDefinition();
        /*定义 某个路径 使用哪个过滤器处理
         *警告:过滤器定义有顺序
         *过滤器列表参考:DefaultFilter
          */
        //anon表示直接放过
        sfcd.addPathDefinition("/","anon");
        sfcd.addPathDefinition("/login","anon");
        sfcd.addPathDefinition("/css/**","anon");
        sfcd.addPathDefinition("/js/**","anon");
        sfcd.addPathDefinition("/images/**","anon");
        sfcd.addPathDefinition("/fonts/**","anon");
        sfcd.addPathDefinition("/html/**","anon");
        //登出
        sfcd.addPathDefinition("/logout","logout");
        //其它所有都需要认证
        sfcd.addPathDefinition("/**","user");
        return sfcd;
    }
    @Bean
    public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();

        /**
         * setUsePrefix(false)用于解决一个奇怪的bug。在引入spring aop的情况下。
         * 在@Controller注解的类的方法中加入@RequiresRole等shiro注解,会导致该方法无法映射请求,导致返回404。
         * 加入这项配置能解决这个bug
         */
        defaultAdvisorAutoProxyCreator.setUsePrefix(true);
        return defaultAdvisorAutoProxyCreator;
    }

1.6、创建Realm域对象

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.woniuxy.shiroboot.dao.PermMapper;
import com.woniuxy.shiroboot.dao.RoleMapper;
import com.woniuxy.shiroboot.dao.UserMapper;
import com.woniuxy.shiroboot.model.Role;
import com.woniuxy.shiroboot.model.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
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 javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
public class ShiroRealm extends AuthorizingRealm {

    @Resource
    private UserMapper userMapper;

    @Resource
    private RoleMapper roleMapper;

    @Resource
    private PermMapper permMapper;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
        User user = (User) principal.getPrimaryPrincipal();

        //查找该用户所有的角色
        List<Role> roleList = roleMapper.selectByUserId(user.getUserId());
        Set<String> strRoles = roleList.stream().map(r -> r.getRoleName()).collect(Collectors.toSet());
        List<String> permissions = new ArrayList<>();
        if (roleList.size()>0){
            //查询该用户所有的权限
            permissions = permMapper.selectPermInRoleIds(roleList);
        }
        SimpleAuthorizationInfo authzInfo = new SimpleAuthorizationInfo();
        authzInfo.setStringPermissions(new HashSet<>(permissions));
        authzInfo.setRoles(strRoles);

        return authzInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        log.info("执行ShiroRealm#doGetAuthenticationInfo{}",token);
        //从被shiro封装成的token中取出我们传入的username
        Object username = token.getPrincipal();
        //根据用户名查询用户
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("username",username);
        User user = userMapper.selectOne(wrapper);

        //返回一个新封装的认证实体,传入的是用户名,数据库查出来的密码,和当前Realm的名字
        return new SimpleAuthenticationInfo(user,user.getPassword(),getClass().getName());

    }
}

查询角色sql

public interface RoleMapper extends BaseMapper<Role> {
    @Select("select r.* from edu_user u " +
            "join edu_user_role eur on u.user_id = eur.user_id " +
            "join edu_role r on r.role_id = eur.role_id where u.user_id = #{userId}")
    List<Role> selectByUserId(int userId);//根据userId查询该用户所有角色,返回集合
}

根据用户角色id查询该用户所有权限

<mapper namespace="com.woniuxy.shiroboot.dao.PermMapper">

    <select id="selectPermInRoleIds" resultType="java.lang.String">
        select ep.perm_name
        from edu_role_perm erp join edu_perm ep on erp.perm_id = ep.perm_id
        where erp.role_id in
        <foreach collection="roles" item="r" separator="," open="(" close=")">
            #{r.roleId}
        </foreach>
    </select>
</mapper>

1.7、测试接口

import lombok.extern.java.Log;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@Slf4j
public class LoginController {
    @PostMapping("/login")
    public String login(String username,String password){

        Subject subject = SecurityUtils.getSubject();
        subject.login(new UsernamePasswordToken(username,password));

        return "success";
    }
    @GetMapping("/pay")
    public String pay(){
        return "success";
    }

    @GetMapping("destroyEarth")
    //权限注解,如果登录用户拥有该权限可正常访问,
    //没有则报错AuthorizationException,无法访问
    @RequiresPermissions("fireEmployee")
    public String destroyEarth(){
        return "success";
    }
}

先访问未认证不能访问的/pay,会发现无法访问且会自动跳转至/login,登陆后再次访问/pay即可访问成功,然后再访问/destroyEarth,如果有@RequiresPermissions("fireEmployee")该权限可访问成功,否则报错AuthorizationException,无法访问

1.8、其它

shiro将账号和密码分成两个地方进行验证,如果我们不自己定义,那么就会调用shiro默认的校验方法;我们可以重新创建一个MyCredentialsMatcher类继承SimpleCredentialsMatcher来实现我们自定义的密码校验方法。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值