Spring Boot +Shiro 用户角色权限设计

首先创建 用户-角色-权限 三个实体类 和 用户与角色关系表和 角色与权限关系表

  1. 用户表UserInfo:在用户表中保存了用户的基本信息,账号、密码、姓名等;
  2. 权限表SysPermission(资源+控制权限):这个表中主要是保存了用户的URL地址,权限信息;
  3. 角色表SysRole:在这个表重要保存了系统存在的角色;
  4. 关联表:用户-角色管理表SysRoleUser(用户在系统中都有什么角色,比如admin,manage等),角色-权限关联表SysRolePermission(每个角色对应什么权限可以进行操作)。

    用户实体

@TableName("user_info")
public class UserInfo extends BaseEntity<UserInfo> {

    private static final long serialVersionUID = 1L;

    private Long id;//用户id;
    private String name;//名称(昵称或者真实姓名,不同系统不同定义)
    private String password; //密码;
    private String salt;//加密密码的盐
    private Integer state;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定.
    @TableField("user_name")
    private String userName; //账号.

    @TableField(exist=false)
    private List<SysRole> roleList;// 一个用户具有多个角色
    }

角色实体

@TableName("sys_role")
public class SysRole extends BaseEntity<SysRole> {

    private static final long serialVersionUID = 1L;

    private String available;// 是否可用,如果不可用将不会添加给用户
    private String description; // 角色描述,UI界面显示使用
    private Long id;// 编号
    private String role; // 角色标识程序中判断使用,如"admin",这个是唯一的:

    @TableField(exist = false)
    private List<SysPermission> permissions;// 角色 - 权限关系定义;
    }

权限实体

@TableName("sys_permission")
public class SysPermission extends BaseEntity<SysPermission> {

    private static final long serialVersionUID = 1L;

    private Long id;
    private String name;
    @TableField("parent_id")
    private Integer parentId;
    @TableField("parent_ids")
    private String parentIds;
    private String permission;
    @TableField("resource_type")
    private String resourceType;
    private String url;
    }

对应关系实体(mybatisPlus生成)

@TableName("sys_role_user")
public class SysRoleUser extends Model<SysRoleUser> {

    private static final long serialVersionUID = 1L;

    private Long id;
    private Long roleId;
    private Long uid;
}


@TableName("sys_role_permission")
public class SysRolePermission extends Model<SysRolePermission> {

    private static final long serialVersionUID = 1L;

    private Integer id;
    private Long permissionId;
    private Long roleId;
    }

部分接口mapper.XML不展示


    <resultMap id="userInfoWithRole" type="com.core.shiro.entity.UserInfo"  extends="BaseResultMap">
        <!--<collection property="roleList" resultMap="com.core.shiro.mapper.SysRoleMapper.BaseResultMap"></collection>-->
        <collection property="roleList" ofType="com.core.shiro.entity.SysRole" >
            <id column="rid" property="id" />
            <result column="create_time" property="createTime" />
            <result column="creator" property="creator" />
            <result column="edit_time" property="editTime" />
            <result column="editor" property="editor" />
            <result column="is_del" property="isDel" />
            <result column="available" property="available" />
            <result column="description" property="description" />
            <result column="role" property="role" />
        </collection>
<!-- 一对多配置 -->
<select id="selectUserByUserNameWithRole" resultMap="userInfoWithRole" parameterType="java.lang.String">
        SELECT
        u.*,
        r.id as rid,
        r.ROLE,
        r.description,
        r.available
        FROM user_info u,sys_role r,sys_role_user  ru
        <where>
        u.user_name=#{userName}
        and u.id=ru.uid
        and r.id=ru.role_id
        and r.is_del='f'
        </where>
    </select>


<resultMap id="RoleWithPermission" type="com.core.shiro.entity.SysRole" extends="BaseResultMap">
        <collection property="permissions" ofType="com.core.shiro.entity.SysPermission">
            <id column="pid" property="id" />
            <result column="name" property="name" />
            <result column="parent_id" property="parentId" />
            <result column="parent_ids" property="parentIds" />
            <result column="permission" property="permission" />
            <result column="resource_type" property="resourceType" />
            <result column="url" property="url" />
        </collection>
        <!-- 通用查询映射结果 -->
    </resultMap>

    <select id="selectRoleByIdWithPermission" resultMap="RoleWithPermission" parameterType="java.lang.Long">

        SELECT
        r.*,
        p.id as pid,
        P . NAME,
        P .parent_id,
        P .parent_ids,
        P .permission,
        P .resource_type,
        P .url
        from sys_permission p,sys_role r,sys_role_permission rp
        <where>
        r.id=#{id} AND
        r.id=rp.role_id AND
        p.id=rp.permission_id AND
        p.is_del='f'
        </where>
    </select>

使用shiro框架 大概需要三个步骤。

  1. 注入ShiroFilterFactoryBean

    • 主要功能 自己编写的过滤器 可以添加到ShiroFilterFactoryBean
    • 添加过滤权限规则
    • 设置登录url和无权限url
  2. 注入securityManager

    • 将我们自己实现的Realm设置到securityManager中。
    • 注入管理器到securityManager中(比如 缓存管理器、记住我管理器)。
    @Bean
    public DefaultWebSecurityManager securityManager(){
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        //设置realm.
        securityManager.setRealm(myShiroRealm());
        //注入缓存管理器;
        securityManager.setCacheManager(ehCacheManager());//这个如果执行多次,也是同样的一个对象;
        //注入记住我管理器;
        securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }

3.实现Realm继承AuthorizingRealm然后重写两个方法

  • 身份认证(重写doGetAuthenticationInfo)
  • 权限控制(重写doGetAuthorizationInfo)

ShiroConfiguration.java

@Configuration
public class ShiroConfiguration {
    /**
     * ShiroFilterFactoryBean 处理拦截资源文件问题。
     * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,以为在
     * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
     *
     Filter Chain定义说明
     1、一个URL可以配置多个Filter,使用逗号分隔
     2、当设置多个过滤器时,全部验证通过,才视为通过
     3、部分过滤器可指定参数,如perms,roles
     *
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        System.out.println("ShiroConfiguration.shirFilter()");
        ShiroFilterFactoryBean shiroFilterFactoryBean  = new ShiroFilterFactoryBean();
        Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();//获取filters
        filters.put("authc", new CustomFormAuthenticationFilter());//将自定义 的FormAuthenticationFilter注入shiroFilter中
        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);



        //拦截器.
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();

        //配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/logout", "logout");

        //配置记住我或认证通过可以访问的地址
        filterChainDefinitionMap.put("/index", "user");
        filterChainDefinitionMap.put("/", "user");

        //验证码可以匿名访问
        filterChainDefinitionMap.put("/getValidateCode", "anon");



        filterChainDefinitionMap.put("/static/**", "anon");//解决静态资源文件


        //<!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
        //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
        filterChainDefinitionMap.put("/**", "authc");

        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //未授权界面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }


    @Bean
    public DefaultWebSecurityManager securityManager(){
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        //设置realm.
        securityManager.setRealm(myShiroRealm());
        //注入缓存管理器;
        securityManager.setCacheManager(ehCacheManager());//这个如果执行多次,也是同样的一个对象;
        //注入记住我管理器;
        securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }
    /**
     * 身份认证realm;
     * (这个需要自己写,账号密码校验;权限等)
     * @return
     */
    @Bean
    public MyShiroRealm myShiroRealm(){
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());;
        return myShiroRealm;
    }
    /**
     * 凭证匹配器
     * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
     *  所以我们需要修改下doGetAuthenticationInfo中的代码;
     * )
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
//        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        HashedCredentialsMatcher hashedCredentialsMatcher = new RetryLimitHashedCredentialsMatcher(ehCacheManager());
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
        return hashedCredentialsMatcher;
    }

    /**
     *  开启shiro aop注解支持.
     *  使用代理方式;所以需要开启代码支持;
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    /**
     * shiro缓存管理器;
     * 需要注入对应的其它的实体类中:
     * 1、安全管理器:securityManager
     * 可见securityManager是整个shiro的核心;
     * @return
     */
    @Bean
    public EhCacheManager ehCacheManager(){
        System.out.println("ShiroConfiguration.getEhCacheManager()");
        EhCacheManager cacheManager = new EhCacheManager();
        cacheManager.setCacheManagerConfigFile("classpath:config/ehcache-shiro.xml");
        return cacheManager;
    }

    /**
     * cookie对象;
     * @return
     */
    @Bean
    public SimpleCookie rememberMeCookie(){
        System.out.println("ShiroConfiguration.rememberMeCookie()");
        //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        //<!-- 记住我cookie生效时间30天 ,单位秒;-->
        simpleCookie.setMaxAge(259200);
        return simpleCookie;
    }

    /**
     * cookie管理对象;
     * @return
     */
    @Bean
    public CookieRememberMeManager rememberMeManager(){
        System.out.println("ShiroConfiguration.rememberMeManager()");
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        return cookieRememberMeManager;
    }
}

常用的权限Filter
anon:所有url都都可以匿名访问;

authc: 需要认证才能进行访问;

user:配置记住我或认证通过可以访问;

MyShiroRealm.java

public class MyShiroRealm extends AuthorizingRealm {

    @Autowired
    private IUserInfoService iUserInfoService;

    @Autowired
    private ISysRoleService sysRoleService;

    /**
     * 认证信息.(身份验证)
     * :
     * Authentication 是用来验证用户身份
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("MyShiroRealm.doGetAuthenticationInfo()");


        //获取用户的输入的账号.
        String username = (String)token.getPrincipal();
        System.out.println(token.getCredentials());

        //通过username从数据库中查找 User对象,如果找到,没找到.
        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        UserInfo userInfo= iUserInfoService.selectUserByUserNameWithRole(username);
        System.out.println("----->>userInfo="+userInfo);
        if(userInfo == null){
            return null;
        }

       //账号判断;

        //加密方式;
        //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现
//        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
//                userInfo, //用户名
//                userInfo.getPassword(), //密码
//                ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt
//                getName()  //realm name
//        );

        //明文: 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验
      SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
           userInfo, //用户名
           userInfo.getPassword(), //密码
             getName()  //realm name
      );

        return authenticationInfo;
    }



    /**
     * 此方法调用  hasRole,hasPermission的时候才会进行回调.
     *
     * 权限信息.(授权):
     * 1、如果用户正常退出,缓存自动清空;
     * 2、如果用户非正常退出,缓存自动清空;
     * 3、如果我们修改了用户的权限,而用户不退出系统,修改的权限无法立即生效。
     * (需要手动编程进行实现;放在service进行调用)
     * 在权限修改后调用realm中的方法,realm已经由spring管理,所以从spring中获取realm实例,
     * 调用clearCached方法;
     * :Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
       /*
        * 当没有使用缓存的时候,不断刷新页面的话,这个代码会不断执行,
        * 当其实没有必要每次都重新设置权限信息,所以我们需要放到缓存中进行管理;
        * 当放到缓存中时,这样的话,doGetAuthorizationInfo就只会执行一次了,
        * 缓存过期之后会再次执行。
        */
        System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        UserInfo userInfo  = (UserInfo)principals.getPrimaryPrincipal();

        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
//     UserInfo userInfo = userInfoService.findByUsername(username)


        //权限单个添加;
        // 或者按下面这样添加
        //添加一个角色,不是配置意义上的添加,而是证明该用户拥有admin角色
//     authorizationInfo.addRole("admin");
        //添加权限
//     authorizationInfo.addStringPermission("userInfo:query");



        ///在认证成功之后返回.
        //设置角色信息.
        //支持 Set集合

        for(SysRole role:userInfo.getRoleList()){
            authorizationInfo.addRole(role.getRole());
            SysRole sysRole = sysRoleService.selectRoleByIdWithPermission(role.getId());//获取角色
            for(SysPermission p:sysRole.getPermissions()){
                authorizationInfo.addStringPermission(p.getPermission());
            }
        }

        //设置权限信息.
//     authorizationInfo.setStringPermissions(getStringPermissions(userInfo.getRoleList()));

        return authorizationInfo;
    }
}

在认证、授权内部实现机制中都有提到,最终处理都将交给Realm进行处理。因为在Shiro中,是通过Realm来获取应用程序中的用户、角色及权限信息的

认证实现

Shiro的认证过程最终会交由Realm执行,这时会调用Realm的getAuthenticationInfo(token)方法。
该方法主要执行以下操作:
1、检查提交的进行认证的令牌信息
2、根据令牌信息从数据源(通常为数据库)中获取用户信息
3、对用户信息进行匹配验证。
4、验证通过将返回一个封装了用户信息的AuthenticationInfo实例。
5、验证失败则抛出AuthenticationException异常信息。
而在我们的应用程序中要做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo (),重写获取用户信息的方法。

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
课程简介:历经半个多月的时间,Debug亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课程讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统中后端应用权限的管理,其中主要涵盖了六大核心业务模块、十几张数据库表。 其中的核心业务模块主要包括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,Debug还亲自撸了额外的附属模块,包括字典管理模块、商品分类模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流程! 核心技术栈列表: 值得介绍的是,本课程在技术栈层面涵盖了前端和后端的大部分常用技术,包括Spring BootSpring MVC、Mybatis、Mybatis-Plus、Shiro(身份认证与资源授权跟会话等等)、Spring AOP、防止XSS攻击、防止SQL注入攻击、过滤器Filter、验证码Kaptcha、热部署插件Devtools、POI、Vue、LayUI、ElementUI、JQuery、HTML、Bootstrap、Freemarker、一键打包部署运行工具Wagon等等,如下图所示: 课程内容与收益: 总的来说,本课程是一门具有很强实践性质的“项目实战”课程,即“企业应用员工角色权限管理平台”,主要介绍了当前企业级应用系统中员工、部门、岗位、角色权限、菜单以及其他实体模块的管理;其中,还重点讲解了如何基于Shiro的资源授权实现员工-角色-操作权限、员工-角色-数据权限的管理;在课程的最后,还介绍了如何实现一键打包上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图: 以下为项目整体的运行效果截图: 值得一提的是,在本课程中,Debug也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发中,前端到后端再到数据库,最后再到服务器的上线部署运行等流程,如下图所示:

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值