美文网首页个人学习
SpringBoot+Shiro搭建权限管理系统(Demo)

SpringBoot+Shiro搭建权限管理系统(Demo)

作者: Jesse4023 | 来源:发表于2020-10-22 19:48 被阅读0次

搭建SpringBoot基本框架

image.png
image.png
pom.xml导入MyBatis Plus依赖
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
        </dependency>
application.yml添加数据源配置
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shiroadmin?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
    username: root
    password: root
创建数据库
image.png
image.png
image.png
image.png
使用mybatis plus代码生成器生成entity

1.添加依赖

<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>
</dependency>

2.运行Main方法

public class Main {
    public static void main(String[] args) {
        //创建generator对象
        AutoGenerator autoGenerator = new AutoGenerator();
        //数据源
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        dataSourceConfig.setDbType(DbType.MYSQL);
        dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/shiroadmin?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false");
        dataSourceConfig.setUsername("root");
        dataSourceConfig.setPassword("root");
        dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
        autoGenerator.setDataSource(dataSourceConfig);
        //全局配置
        GlobalConfig globalConfig = new GlobalConfig();
        globalConfig.setOutputDir(System.getProperty("user.dir")+"/src/main/java");
        //创建好之后打开文件
        globalConfig.setOpen(false);
        globalConfig.setAuthor("Jesse");
        globalConfig.setServiceName("%sService");
        autoGenerator.setGlobalConfig(globalConfig);
        //包信息
        PackageConfig packageConfig = new PackageConfig();
        packageConfig.setParent("com.glg.shiroadmin");

        packageConfig.setController("controller");
        packageConfig.setService("service");
        packageConfig.setServiceImpl("service.impl");
        packageConfig.setEntity("entity");
        packageConfig.setMapper("mapper");
        autoGenerator.setPackageInfo(packageConfig);
        //配置策略
        StrategyConfig strategyConfig = new StrategyConfig();
        strategyConfig.setEntityLombokModel(true);
        strategyConfig.setTablePrefix("tb_");
        //开启字段驼峰命名
        strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
        autoGenerator.setStrategy(strategyConfig);

        autoGenerator.execute();
    }
}
MyBatis Plus查询功能测试
@Controller
public class UserController {
    @Autowired
    UserService userService;

    @GetMapping("/userList")
    @ResponseBody
    public List<User> getUserList(){
        List<User> userList = userService.getAllUser();
        return userList;
    }
添加用户角色和权限查询方法

根据用户名查询角色 findRoleByUser,
根据对应的角色查询权限 findPermissionByRole

public interface RoleMapper extends BaseMapper<Role> {
//根据用户查询角色
    List<Role> findRoleByUser(@Param("user") User user);
}

在resource下创建mapper包,编写RoleMapper.xml文件

<?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="com.glg.shiroadmin.mapper.RoleMapper">
<resultMap id="BaseResultMap" type="com.glg.shiroadmin.entity.Role">
    <id column="id" jdbcType="BIGINT" property="id" />
    <result column="role_name" jdbcType="VARCHAR" property="roleName" />
    <result column="remark" jdbcType="VARCHAR" property="remark" />
    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
    <result column="modified_time" jdbcType="TIMESTAMP" property="modifiedTime" />
    <result column="status" jdbcType="INTEGER" property="status" />
</resultMap>
    <select id="findRoleByUser" resultType="com.glg.shiroadmin.entity.Role" parameterType="com.glg.shiroadmin.entity.User">
        select
            r.id,r.role_name roleName,r.remark,r.create_time createTime,r.modified_time modifiedTime,r.status
        from
            tb_user u
        LEFT JOIN tb_user_role ur ON u.id = ur.user_id
        LEFT JOIN tb_role r ON r.id = ur.role_id
        <where>
            1 = 1
            <if test="user != null and user != '' ">
                AND u.username = #{user.username}
            </if>
        </where>
    </select>
</mapper>
public interface MenuMapper extends BaseMapper<Menu> {
    //根据角色名查权限
       List<Menu> findPermissionByRole(@Param("role") Role role);
}

resource/mapper创建MenuMapper.xml

<?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="com.glg.shiroadmin.mapper.MenuMapper">
    <resultMap id="BaseResultMap" type="com.glg.shiroadmin.entity.Menu">
        <id column="id" jdbcType="BIGINT" property="id" />
        <result column="parent_id" jdbcType="BIGINT" property="parentId" />
        <result column="menu_name" jdbcType="VARCHAR" property="menuName" />
        <result column="url" jdbcType="VARCHAR" property="url" />
        <result column="perms" jdbcType="VARCHAR" property="perms" />
        <result column="icon" jdbcType="VARCHAR" property="icon" />
        <result column="type" jdbcType="CHAR" property="icon" />
        <result column="order_num" jdbcType="BIGINT" property="orderNum" />
        <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
        <result column="modified_time" jdbcType="TIMESTAMP" property="modifiedTime" />
        <result column="available" jdbcType="INTEGER" property="available" />
        <result column="open" jdbcType="INTEGER" property="open" />
    </resultMap>
    <select id="findPermissionByRole" resultType="com.glg.shiroadmin.entity.Menu" parameterType="com.glg.shiroadmin.entity.Role">
        SELECT
        p.id,
        p.perms,
        p.url
        from
        tb_role r
        LEFT JOIN tb_role_menu rp ON r.id = rp.role_id
        LEFT JOIN tb_menu p ON rp.menu_id = p.id
        <where>
            1 = 1
            <if test="role != null and role != '' ">
                AND r.role_name = #{role.roleName}
            </if>
        </where>
    </select>
</mapper>

测试:

    @Test
    void testt(){
        User user = new User();
        user.setUsername("admin");
        roleMapper.findRoleByUser(user).forEach(System.out::println);
    }
image.png
    @Test
    void test(){
        Role role = new Role();
        role.setRoleName("超级管理员");
        menuMapper.findPermissionByRole(role).forEach(System.out::println);
    }
image.png

开始添加Shiro

1.pom.xml添加shiro依赖

<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.5.3</version>
        </dependency>
自定义Shiro过滤器

2.创建一个realm包 ,编写UserRealm类

AuthorizationInfo 授权
AuthenticationInfo 认证
1.先在AuthenticationInfo 认证,再在AuthorizationInfo授权
2.此处认证方式使用Shiro给账号密码 加盐、加密。

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.glg.shiroadmin.entity.Menu;
import com.glg.shiroadmin.entity.Role;
import com.glg.shiroadmin.entity.User;
import com.glg.shiroadmin.mapper.MenuMapper;
import com.glg.shiroadmin.mapper.RoleMapper;
import com.glg.shiroadmin.mapper.UserMapper;
import com.glg.shiroadmin.service.UserService;
import com.glg.shiroadmin.utils.AesCipherUtil;
import org.apache.shiro.SecurityUtils;
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 org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class UserRealm extends AuthorizingRealm {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleMapper roleMapper;
    @Autowired
    private MenuMapper menuMapper;
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //1.获取当前用户信息,
        Subject subject = SecurityUtils.getSubject();
        //获取用户信息
        User user = (User) subject.getPrincipal();
        //设置角色
        Set<String> roles = new HashSet<>();
        //根据用户查询角色
        List<Role> roleList =  roleMapper.findRoleByUser(user);
        System.out.println(user.getUsername()+"拥有的角色有"+roleList.toString());
        for (Role role : roleList){
            if (role != null){
                //添加用户角色
                simpleAuthorizationInfo.addRole(role.getRoleName());
                //根据用户角色查询权限
                List<Menu> menuList = menuMapper.findPermissionByRole(role);
                System.out.println(role.getRoleName()+"角色拥有的权限有"+menuList.toString());
                for(Menu menu : menuList){
                    if(menu!=null){
          simpleAuthorizationInfo.addStringPermission(menu.getPerms());
                    }
                }
            }
        }
        return simpleAuthorizationInfo;
    }
    //认证
    /**
     * 执行subject.login(token);这个方法的时候shiro就会直接到我们自己创建的Realm类中寻找
     * doGetAuthenticationInfo方法,在这个方法里我们使用如下方式来验证数据库中的密码
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1.先认证
        /**客户端传来的用户名封装在token里,根据用户名进行查询,如果不存在return null,shiro自动抛出账户不存在的异常。
         * 不为空,返回SimpleAuthenticationInfo对象,对象里传入数据库查询的密码和token的得到的密码。自动验证,
          *如果密码不对,shiro也会抛出异常 我们只需要捕获这两个异常
         */
        String userName = (String) token.getPrincipal();
        if (userName == null || userName.length() == 0) { return null; }
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.eq("username",userName);
        User userInfo = userMapper.selectOne(wrapper);

        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                userInfo, //用户名
                userInfo.getPassword(), //密码
                ByteSource.Util.bytes(userInfo.getSalt()), //盐
                getName() //realm name
        );
        return authenticationInfo;
    }
}

3.编写配置ShiroConfig.java

ShiroFilterFactoryBean 一个ShiroFilter,一般配置需要安全控制的URL。
DefaultWebSecurityManager 主要定义了设置subjectDao,获取会话模式,设置会话模式,设置会话管理器,是否是http会话模式等操作,它继承了DefaultSecurityManager类,实现了WebSecurityManager接口,现对其解析如下:
1.WebSecurityManager接口
可以参考WebSecurityManager接口源码解析,里面只有一个方法,定义了是否http会话模式。
2.DefaultSecurityManager类
可以参考DefaultSecurityManager类源码解析,里面主要定义了登录,创建subject,登出等操作。
3.DefaultWebSecurityManager类
HashedCredentialsMatcher 凭证匹配器 密码校验交给Shiro的SimpleAuthenticationInfo进行处理
UserRealm setCredentialsMatcher(hashedCredentialsMatcher());

import com.glg.shiroadmin.realm.UserRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
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.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Hashtable;
import java.util.Map;

@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);
        //权限设置
        Map<String,String> map = new Hashtable<>();
        map.put("/public","anon");
        map.put("/userList","authc");
        map.put("/systemadmin", "perms[system]");
        map.put("/menusystem", "perms[menu]");
        map.put("/usersystem", "perms[users]");
        factoryBean.setFilterChainDefinitionMap(map);
        //设置登录页面
//        factoryBean.setLoginUrl("/login");
        //设置未授权页面
        factoryBean.setUnauthorizedUrl("/unauth");
        return factoryBean;
    }
    @Bean
    public DefaultWebSecurityManager securityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(userRealm);
        return manager;
    }
    /**
     * 凭证匹配器
     * 密码校验交给Shiro的SimpleAuthenticationInfo进行处理
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");//散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(2);//散列的次数;
        return hashedCredentialsMatcher;
    }
    //MD5加密
    @Bean
    public UserRealm userRealm(){
        UserRealm  userRealm = new UserRealm();
        userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return userRealm;
    }
    /**
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
     */
     /* 下面的代码是添加注解支持
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 强制使用cglib,防止重复代理和可能引起代理出错的问题,https://zhuanlan.zhihu.com/p/29161098
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}
认证以及授权完成,接下来编写登录,注册,退出的controller

此注册功能处,用户数据表中注册时间以及修改时间可设置为MybatisPlus自动填充

1.在实体类,添加注解

      @TableField(fill = FieldFill.INSERT)
      private Date createTime;
      @TableField(fill = FieldFill.INSERT_UPDATE)
      private Date modifiedTime;

2.编写handler.java

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("createTime",new Date(),metaObject);
        this.setFieldValByName("modifiedTime",new Date(),metaObject);}
    @Override
    public void updateFill(MetaObject metaObject) {
    this.setFieldValByName("modifiedTime",new Date(),metaObject);}}

3.开始写登录注册的controller

 /**
     * 登录方法
     */
    @PostMapping("/login")
    @ResponseBody
    public ResultTypeDTO login(@RequestBody User user){
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.eq("username",user.getUsername());
        List<User> userList = userMapper.selectList(wrapper);
        if(userList.size()==0){ return new ResultTypeDTO().errorOf(CustomizeErrorCode.USER_NOT_FOUND); }
        //账号密码令牌
        AuthenticationToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
        //获得当前用户到登录对象,现在状态为未认证
        Subject subject = SecurityUtils.getSubject();
            try {
                //将令牌传到shiro提供的login方法验证,需要自定义realm
                subject.login(token);
                User logineduser = (User) subject.getPrincipal();
                subject.getSession().setAttribute("logineduser",logineduser);
                return new ResultTypeDTO().okOf().addMsg("message","登陆成功");
            }catch (UnknownAccountException e){
                e.printStackTrace();
                return new ResultTypeDTO().errorOf(CustomizeErrorCode.USER_NOT_FOUND);
            }catch (IncorrectCredentialsException e){
                e.printStackTrace();
                return new ResultTypeDTO().errorOf(CustomizeErrorCode.LOGIN_FAIL);
            }
    }
    @GetMapping("/logout")
    public String logout(){
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "login";
    }
    /**
     * 用户注册
     */
        @PostMapping("/register")
        @ResponseBody
        public ResultTypeDTO userRegister(@RequestBody User user){
            // 查询数据库中的帐号信息 判断当前帐号是否存在
            QueryWrapper wrapper = new QueryWrapper();
            wrapper.eq("username",user.getUsername());
            List<User> userList = userMapper.selectList(wrapper);
            if (userList.size() != 0 && StringUtil.isNotBlank(userList.get(0).getPassword())) {
                return new ResultTypeDTO().errorOf(CustomizeErrorCode.THIS_USER_USED); }

            String originalPassword = user.getPassword(); //原始密码
            String hashAlgorithmName = "MD5"; //加密方式
            int hashIterations = 2; //加密的次数
            //盐
            String salt = new SecureRandomNumberGenerator().nextBytes().toHex();
            //加密
            SimpleHash simpleHash = new SimpleHash(hashAlgorithmName, originalPassword, salt, hashIterations);
            String encryptionPassword = simpleHash.toString();
            //创建用户信息
            user.setPassword(encryptionPassword);
            user.setSalt(salt);
            user.setStatus(1);
            //执行新增
            int count = userMapper.insert(user);
            if (count <= 0) {
                throw new CustomException("新增失败(Insert Failure)");
            }
            /** 根据user添加角色
             * user.getId() 直接取插入新数据的id
             */
            int flag = userRoleMapper.insertUserRole(user.getId(),user.getType());
            if(flag <= 0){
                throw new CustomException("新增失败(Insert Failure)");
            }
            return new ResultTypeDTO().okOf();
        }
userRoleMapper.java
package com.glg.shiroadmin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.glg.shiroadmin.entity.User_role;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
public interface userRoleMapper extends BaseMapper<User_role> {
    @Insert("insert into tb_user_role(user_id,role_id) values(#{uid},#{rid})")
    int insertUserRole(@Param("uid") Long uid, @Param("rid")Integer rid);
}

注册 登录测试

=========================

注册不同角色的用户,进行认证授权测试

type=1:系统管理员
type=2:功能管理员
type=3:用户管理员

图片.png

访问如下url进行测试

@Controller
public class AdminController {
    @Autowired
    private UserService userService;
    //开放接口 无需登录
    @GetMapping("/public")
    @ResponseBody
    public String open(){
        return "这是open权限接口";
    }
    @GetMapping("/unauth")
    @ResponseBody
    public ResultTypeDTO unauth(){
        return new ResultTypeDTO().errorOf(CustomizeErrorCode.USER_NOT_PERMISSION);
    }
    //只需登录
    @GetMapping("/userList")
    @ResponseBody
    public List<User> getUserList(){
        List<User> userList = userService.getAllUser();
        return userList;
    }
    //系统管理员才能访问
    @GetMapping("/systemadmin")
    @ResponseBody
    public String systemadmin(){
        return "这是systemadmin权限接口,拥有系统管理权限才能访问";
    }
    //功能模块管理员才能访问
    @GetMapping("/menusystem")
    @ResponseBody
    public String menusystem(){
        return "这是menusystem权限接口,拥有功能模块管理权限才能访问";
    }
    //用户管理员才能访问
    @GetMapping("/usersystem")
    @ResponseBody
    public String usersystem(){
        return "这是usersystem权限接口,拥有用户管理权限才能访问";
    }
}

相关文章

网友评论

    本文标题:SpringBoot+Shiro搭建权限管理系统(Demo)

    本文链接:https://www.haomeiwen.com/subject/stjymktx.html