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

来源:互联网 发布:手机动漫制作软件 编辑:程序博客网 时间:2024/05/17 03:13

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

  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

@Configurationpublic 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 (),重写获取用户信息的方法。

原创粉丝点击