Springboot集成mybatis框架、Shiro权限管理框架

Springboot集成Shiro权限管理框架

  • 一 Springboot的web项目创建
  • 二 Apache Shiro
    • 2.1 创建Shiro的实体类
    • 2.2 Shiro的Realm和Config
      • MyRealm:
      • 上文中的service接口及其实现类
      • 上述代码中用到的Mapper接口及其Sql映射文件:
  • 三 测试
    • 3.1 浏览器访问测试

最近入职了第一份Java后端开发的工作,在正式干活之前,部门老大首先给我派了个小任务,给部门的员工进行信息登记。要求每个员工首先注册自己信息,而员工的任何删改查操作都需要登录,并且只能查询或者修改自己的信息,部门老大需要所有权限。

这个小需求我用Springboot、Mybatis、Thymeleaf、Shiro、MySql做了个小网站,连接池用的Druid,分页插件使用Pagehelper,日志使用的默认日志。

为了记录一下整个项目的过程,并且巩固一下自己的知识,在完成之后,重新建立了一个小demo,重点重现了该Web环境(Mybatis及逆向工程、Shiro权限管理框架集成)的搭建过程。

完成之后,项目的目录结构如下:

Springboot集成mybatis框架、Shiro权限管理框架_第1张图片

Springboot集成mybatis框架、Shiro权限管理框架_第2张图片

一 Springboot的web项目创建

这个就没有什么好讲的,我们用IDEA直接创建一个Springboot的Web环境即可。

application.yml文件如下:

server:
  port: 80
spring:
  datasource:
    name: demo
    type: com.alibaba.druid.pool.DruidDataSource
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/shiroexample?useUnicode=true&charactorEncoding=utf-8&serverTimeZone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  mapper-locations: classpath*:/mybatis/mapper/*Mapper.xml
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logging:
  level:
    root: info
    com.hebin.shiroexample.mapper: debug
  file: log/log.log

一般Springboot的版本无需使用高版本(本项目使用2.1.14),版本太高会有插件或者工具因为未更新导致未知原因错误。插件选择图中几个,也可以一个不选在最后POM中手动添加依赖坐标,因为本文不展示Thymeleaf的使用,所以无需勾选Thymeleaf。其中Lombok插件用于简化JavaBean的开发,省略Getter/Setter/toString等方法代码,Spring Web是必须使用的,Mybatis和Mysql用于持久层。

Springboot集成mybatis框架、Shiro权限管理框架_第3张图片

POM文件,dependencies如下:

<dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>2.1.2version>
        dependency>

        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <scope>runtimescope>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
       
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druidartifactId>
            <version>1.1.17version>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.apache.shirogroupId>
            <artifactId>shiro-springartifactId>
            <version>1.4.0version>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>

二 Apache Shiro

关于Shiro的介绍和使用请看王诗林的这篇文章springboot整合shiro(完整版),因为我使用的也不是很熟练,我这里定性的讲一下Shiro权限验证和授权的大概原理。

首先,在一个项目中,会有用户,而用户除了用户名、密码等常规属性之外,还具有各种角色。比如论坛项目,角色有:管理员,版主,普通注册用户,游客等角色,同时应该注意到,一个用户可以同时拥有多个角色,是个一对多的关系。而一个角色会有各种权限,也是一对多的关系。例如管理员有封禁、修改用户角色、发帖、删帖等权限,而游客只有浏览权限。

因此,在Shiro框架下,至少有三个实体类:User,Role,Permission,对应有三张数据库表,同时也应该建立两张关联表,将用户与角色的关系User_Role,角色与权限Role_Permission的关系联系起来。

2.1 创建Shiro的实体类

  1. 在数据库中建立五张表

    Springboot集成mybatis框架、Shiro权限管理框架_第4张图片

    建表语句如下:

    create table t_user
    (
        id 			int auto_increment 		comment '用户主键ID'	primary key,
        username    varchar(10)  not null 	comment '用户名,不能重复',
        nickname    varchar(10)  not null 	comment '昵称',
        password    varchar(100) not null 	comment '密码',
        description varchar(256) null 		comment '用户描述',
        constraint t_user_username_uindex	unique (username)
    );
    
    create table t_role
    (
        id			int auto_increment 		comment '角色主键ID'	primary key,
        role_name   varchar(20) not null 	comment '角色名称',
        description varchar(50) null 		comment '角色描述',
        constraint t_role_role_name_uindex	unique (role_name)
    );
    
    create table t_permission
    (
        id				int auto_increment 		comment '权限主键ID'	primary key,
        permission_name varchar(20) not null 	comment '权限名称',
        description     varchar(30) null 		comment '权限描述',
        constraint t_permission_permission_name_uindex	unique (permission_name)
    );
    
    create table t_user_role
    (
        id		int auto_increment 	comment '用户和角色关联表主键ID'	primary key,
        user_id int not null 		comment '用户ID',
        role_id int not null 		comment '角色ID'
    );
    
    create table t_role_permission
    (
        id				int auto_increment 	comment '角色和权限关联表主键ID'	primary key,
        role_id			int not null 		comment '角色ID',
        permission_id 	int not null 		comment '权限ID'
    );
    
  2. 利用逆向工程生成User、Role、Permission单表的Bean和Mapper接口以及Mapper映射文件

    逆向工程可以查看这篇文章:Springboot项目Web环境引入Mybatis逆向工程MBG。

    在User、Role的类中添加字段,最终如下。

    @Data注解为Lombok插件注解,也可不用,但需要将后加属性添加getter/setter方法,以及toString方法。

    @Data
    public class User {
        private Integer id;
    
        private String username;
    
        private String nickname;
    
        private String password;
    
        private String description;
    
        // 用户对应有各种角色,后加。
        private Set<Role> roles;
    }
    
    @Data
    public class Role {
        private Integer id;
    
        private String roleName;
    
        private String description;
    
        // 每个角色都有一些权限,后加。
        private Set<Permission> permissions;
    }
    
    @Data
    public class Permission {
        private Integer id;
    
        private String permissionName;
    
        private String description;
    }
    

2.2 Shiro的Realm和Config

因为Shiro对外接口是Subject,外界与Shiro的交互首先交给Subject,其在内部交给SecurityManager,SecurityManager再根据相关的域(Realm)做出授权或者认证管理。

而这个域,需要进行自己的编写。

MyRealm:

package com.hebin.shiroexample.shiro;

import com.hebin.shiroexample.bean.Permission;
import com.hebin.shiroexample.bean.Role;
import com.hebin.shiroexample.bean.User;
import com.hebin.shiroexample.service.UserService;
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 org.springframework.util.StringUtils;

import java.util.Set;

public class MyRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    /**
     * 授权的方法,首先从系统中获取系统使用主体,从数据库中查出该主体拥有的角色信息和权限信息,并赋予给AuthorizationInfo,从而完成授权。
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 1.根据主体获取PrimaryPrincipal(不一定非得是用户名,也可以是手机号,邮箱地址等能够唯一确定用户的信息)
        String username = (String) principalCollection.getPrimaryPrincipal();

        // 这一步先判断是否存在被授权用户。
        if (principalCollection == null || StringUtils.isEmpty(username)) {
            return null;
        }
        // 2.根据PrimaryPrincipal获取用户。
        User user = userService.getUserWithRolesAndPermissionsByUsername(username);

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        // 3.将用户具有的角色和权限信息分别添加到AuthorizationInfo中
        Set<Role> roles = user.getRoles();
        for (Role r : roles) {
            // 添加角色
            simpleAuthorizationInfo.addRole(r.getRoleName());

            Set<Permission> permissions = r.getPermissions();
            // 添加权限
            for (Permission permission : permissions) {
                simpleAuthorizationInfo.addStringPermission(permission.getPermissionName());
            }
        }
        // 将该主体应该有的角色和权限全部添加到Shiro中返回,Controller接收到返回值后判断是否有授权放行。
        return simpleAuthorizationInfo;
    }

    /**
     * 认证的方法
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        // 从token中获取用户名、密码等信息
        String username = (String) authenticationToken.getPrincipal();
        // 如果获取不到,认证失败
        if (authenticationToken == null || StringUtils.isEmpty(username)) {
            return null;
        }

        // 根据获取到的信息,从数据库查询相关实体
        User user = userService.getUserWithRolesAndPermissionsByUsername(username);
        // 如果实体不存在,认证失败
        if (user == null) {
            return null;
        }

        // 将查询到的实体的相关信息交给Shiro进行判断是否通过认证。
        return new SimpleAuthenticationInfo(username,user.getPassword(),getName());
    }
}

上文中的service接口及其实现类

package com.hebin.shiroexample.service;

import com.hebin.shiroexample.bean.User;

public interface UserService {
    User getUserWithRolesAndPermissionsByUsername(String username);
}
package com.hebin.shiroexample.service.impl;

import com.hebin.shiroexample.bean.Role;
import com.hebin.shiroexample.bean.User;
import com.hebin.shiroexample.mapper.RoleExtendMapper;
import com.hebin.shiroexample.mapper.UserExtendMapper;
import com.hebin.shiroexample.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Set;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserExtendMapper userExtendMapper;

    @Autowired
    private RoleExtendMapper roleExtendMapper;

    @Override
    public User getUserWithRolesAndPermissionsByUsername(String username) {

        // 根据用户名查出用户,其中用户的Set Roles属性中不包含Role的Set permissions属性。
        User user = userExtendMapper.selectUserWithRolesByUsername(username);

        // 取出用户的Roles Set,根据每个role查询出对应的Set,将role缺失的该权限属性集合用setter方法赋予。
        Set<Role> roles = user.getRoles();

        // 1.循环取出role
        for (Role r : roles) {
            // 2.每个role取出其id
            Integer roleId = r.getId();
            // 3.根据roleId取出对应的permissions完好的Role
            Role role = roleExtendMapper.selectRoleWithPermissionsByRoleId(roleId);
            // 4.调用role的setter方法赋值permissions。
            r.setPermissions(role.getPermissions());
        }

        // 返回User对象,该对象是完整对象,内包含全部的role和permission。
        return user;
    }
}

上述代码中用到的Mapper接口及其Sql映射文件:

UserExtendMapper:

package com.hebin.shiroexample.mapper;

import com.hebin.shiroexample.bean.User;
import org.springframework.stereotype.Repository;

@Repository
public interface UserExtendMapper extends UserMapper {
    User selectUserWithRolesByUsername(String username);
}



    
        
        
        
        
        
        
    
    
        
        
        
    

    

RoleExtendMapper:

package com.hebin.shiroexample.mapper;

import com.hebin.shiroexample.bean.Role;
import org.springframework.stereotype.Repository;

@Repository
public interface RoleExtendMapper extends RoleMapper {
    Role selectRoleWithPermissionsByRoleId(Integer id);
}



    
        
        
        
        
    
    
        
        
        
    

    

当完成上面的代码后,需要进行Shiro的配置了:

package com.hebin.shiroexample.config;

import com.hebin.shiroexample.shiro.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;

@Configuration
public class ShiroConfig {

    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator creator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    // 将自定义的Realm加入到Spring容器
    @Bean
    public MyRealm myShiroRealm() {
        return new MyRealm();
    }

    // 权限管理,特别需要注意的是,SecurityManager是个接口,一定要使用org.apache.shiro.mgt.SecurityManager;
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(myShiroRealm());
        return defaultWebSecurityManager;
    }

    // filter设置过滤条件和跳转条件
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
        filterFactoryBean.setSecurityManager(securityManager);
        HashMap<String, String> map = new HashMap<>();

        map.put("/logout", "logout");

        // authc表示需要认证才能访问,即拦截认证;anon表示无需认证也能访问,即不拦截
        map.put("/admin/**", "authc");
        map.put("/user/**", "authc");
        map.put("/leader/**", "authc");
   		map.put("/visitor/**", "anon");
        
        filterFactoryBean.setLoginUrl("/login");

        filterFactoryBean.setUnauthorizedUrl("/error");

        filterFactoryBean.setFilterChainDefinitionMap(map);

        return filterFactoryBean;
    }

    // 开启Shiro的注解
    @Bean
    public AuthorizationAttributeSourceAdvisor advisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

三 测试

首先我们建立一个异常拦截类拦截授权或者认证异常

package com.hebin.shiroexample.exceptionHandler;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.naming.AuthenticationException;

@RestControllerAdvice
@Slf4j
public class MyExceptionHandler {

    @ExceptionHandler(AuthorizationException.class)
    public String authorizationExceptionHandler(AuthorizationException e) {
        log.error("没有通过权限验证", e);
        return "没有通过权限验证!";
    }

    @ExceptionHandler(AuthenticationException.class)
    public String authenticationExceptionHandler(AuthenticationException e) {
        log.error("授权异常", e);
        return "授权异常!";
    }
}

我们建立一些假数据:

User:

Springboot集成mybatis框架、Shiro权限管理框架_第5张图片

Role:

Springboot集成mybatis框架、Shiro权限管理框架_第6张图片

Permission:

Springboot集成mybatis框架、Shiro权限管理框架_第7张图片

User_Role:

Springboot集成mybatis框架、Shiro权限管理框架_第8张图片

Role_Permission:

Springboot集成mybatis框架、Shiro权限管理框架_第9张图片

建立一个Controller测试权限情况

@RestController
public class IndexController {

    @RequestMapping("/index")
    public String index() {
        return "success!";
    }

    @RequiresRoles("admin")
    @RequestMapping("/admin/list")
    public String getUserList() {
        Subject subject = SecurityUtils.getSubject();
        Object principal = subject.getPrincipal();
        System.out.println(principal);
        return "userList";
    }

    @RequiresPermissions("delete")
    @RequestMapping("/admin/delete")
    public String deleteUser() {
        return "userDelete";
    }

    @GetMapping("/login")
    public String login(@RequestParam("username") String username,
                        @RequestParam("password") String password) {
        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        try {
            subject.login(token);
        } catch (AuthenticationException e) {
            e.printStackTrace();
            return "账号和密码错误!";
        } catch (AuthorizationException e) {
            e.printStackTrace();
            return "没有权限!";
        }

        return "登陆成功!";
    }
}

3.1 浏览器访问测试

  1. 访问:http://localhost/index,无需任何权限,成功!

    返回Success!

    image-20200604162326378

  2. 测试登录:

    1. 浏览器访问:http://localhost/login?username=aa&password=123

      image-20200604162639532

      控制台提示认证错误。

      org.apache.shiro.authc.AuthenticationException: Authentication failed for token submission [org.apache.shiro.authc.UsernamePasswordToken - aa, rememberMe=false]. 
      

      浏览器访问:http://localhost/login?username=a&password=123

    image-20200604162509394

  3. a用户为管理员,包含所有权限,访问:http://localhost/admin/list,成功

image-20200604162850759

  1. a用户为管理员,包含所有权限,访问:http://localhost/admin/delete,成功

    image-20200604163103173

  2. 我们访问:http://localhost/logout登出,对b用户登录:http://localhost/login?username=b&password=123

  3. b为leader、user用户,访问:http://localhost/admin/list,失败

    image-20200604163435081

  4. b用户不包含delete权限,访问:http://localhost/admin/delete,失败
    image-20200604163523819

你可能感兴趣的:(Springboot,Mybatis技巧)