Guns框架学习记录-4-Swagger2+Shiro权限管理

7.Swagger

Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化RESTful风格的Web服务。是一套流行的API框架,可以帮助开发人员快速构建API文档,还可以方便测试项目各项功能。

谈及两个问题:
Swagger如何集成?
    maven引入依赖,配置swagger,设置静态资源映射 
Swagger如何使用?
    @ApiOperation, @ApiImplicitParams, @ApiImplicitParam

关于Swagger的使用已经发表博客,详情见:https://blog.csdn.net/Nerver_77/article/details/81476073

8.Shiro:Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。

关于权限管理和shiro的入门之前也整理了博客,详情见:

权限管理:https://blog.csdn.net/Nerver_77/article/details/81318299

Shiro入门:https://blog.csdn.net/Nerver_77/article/details/81318357

下面要说到的是Guns框架中关于Shiro这一框架的集成和使用:

1. 首先,要明确的一个问题是权限管理内容:用户 – 角色 – 资源(权限) 三者之间关系

2. Shiro权限管理配置

    2.1 依赖导入:
    <!--shiro依赖-->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <exclusions>
            <!--commons.io中slf4j-api依赖冲突-->
            <exclusion>
                <artifactId>slf4j-api</artifactId>
                <groupId>org.slf4j</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <!--shiro整合spring-->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
    </dependency>
    <!--shiro整合ehcache缓存-->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-ehcache</artifactId>
        <exclusions>
            <exclusion>
                <artifactId>slf4j-api</artifactId>
                <groupId>org.slf4j</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.ehcache</groupId>
        <artifactId>ehcache</artifactId>
    </dependency>
    2.2 shiro配置 ShiroConfig.java 这里记录下常用配置
    /**
     * 安全管理器:权限管理的核心配置
     */
    @Bean
    public DefaultWebSecurityManager securityManager(CookieRememberMeManager rememberMeManager, CacheManager cacheShiroManager, SessionManager sessionManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //设置管理器成员
        securityManager.setRealm(this.shiroDbRealm());
        securityManager.setCacheManager(cacheShiroManager);
        securityManager.setRememberMeManager(rememberMeManager);
        securityManager.setSessionManager(sessionManager);
        return securityManager;
    }

    /**
     * Shiro的过滤器链:shiro内置过滤器
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        /**
         * 默认的登陆访问url
         */
        shiroFilter.setLoginUrl("/login");
        /**
         * 登陆成功后跳转的url
         */
        shiroFilter.setSuccessUrl("/");
        /**
         * 没有权限跳转的url
         */
        shiroFilter.setUnauthorizedUrl("/global/error");
        /**
         * 配置shiro拦截器链
         *
         * anon  不需要认证
         * authc 需要认证
         * user  验证通过或RememberMe登录的都可以
         *
         * 当应用开启了rememberMe时,用户下次访问时可以是一个user,但不会是authc,因为authc是需要重新认证的
         */
        Map<String, String> hashMap = new LinkedHashMap<>();
        //除一下声明无需认证的资源外,其他资源均需要认证authc。
        //注意排序顺序,由于采用了LinkedHashMap,有序链表,自顶向下,优先级依次降低。
        hashMap.put("/static/**", "anon");
        hashMap.put("/login", "anon");
        hashMap.put("/global/sessionError", "anon");
        hashMap.put("/kaptcha", "anon");
        hashMap.put("/**", "user");
        shiroFilter.setFilterChainDefinitionMap(hashMap);
        return shiroFilter;
    }

    2.3 自定义Realm ShiroDbRealm.java  重点
    上述博文中也提到了Realm的作用,这里是实际应用Realm中,继承AuthorizingRealm类,需要实现认证和授权方法。
    public class ShiroDbRealm extends AuthorizingRealm {

        /**
         * 登录认证:用户subject通过token进行登录,之后进入shiro登录人认证模块,进行token认证。
         * 最后返回认证信息SimpleAuthenticationInfo。
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
                throws AuthenticationException {
            //自定义了Dao层的数据查询接口
            IShiro shiroFactory = ShiroFactroy.me();
            //认证token为Username和Password
            UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
            /*
            * 这里进入自定义的数据查询接口根据username查询对应user
            * 如果user == null 会抛出异常,响应前端登录失败信息。
            */
            User user = shiroFactory.user(token.getUsername());
            //将返回的user设置成shiroUser
            //这里的shiroUser:自定义Authentication对象,使得Subject除了携带用户的登录名外还可以携带更多信息。
            ShiroUser shiroUser = shiroFactory.shiroUser(user);
            //在生成认证信息的时候,会将user中的password进行加盐操作,之后进行MD5加密。和数据库中的password进行比对,比对成功
            //即通过认证,这里的user需要说明,是从token中获取的。
            SimpleAuthenticationInfo info = shiroFactory.info(shiroUser, user, super.getName());
            return info;
        }

        /**
         * 权限认证:通过认证的用户subject,进行权限的管理,shiroUser中携带了用户的更多信息。
         * ShiroUser的成员变量:
         *   public Integer id;          // 主键ID
         *   public String account;      // 账号
         *   public String name;         // 姓名
         *   public Integer deptId;      // 部门id
         *   public List<Integer> roleList; // 角色集
         *   public String deptName;        // 部门名称
         *   public List<String> roleNames; // 角色名称集
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            //调用静态方法从spring容器中获取接口实例
            IShiro shiroFactory = ShiroFactroy.me();
            //获取认证通过的主体subject身份信息
            ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
            //获取shiroUser的角色集
            List<Integer> roleList = shiroUser.getRoleList();

            //构建当前shirouser的权限集和角色名集
            Set<String> permissionSet = new HashSet<>();
            Set<String> roleNameSet = new HashSet<>();

            //遍历角色集
            for (Integer roleId : roleList) {
                //根据角色ID查询对应的权限集
                List<String> permissions = shiroFactory.findPermissionsByRoleId(roleId);
                if (permissions != null) {
                    //遍历对应角色ID的权限集
                    for (String permission : permissions) {
                        if (ToolUtil.isNotEmpty(permission)) {
                            //非空的加入到构建的权限集合中。
                            permissionSet.add(permission);
                        }
                    }
                }
                String roleName = shiroFactory.findRoleNameByRoleId(roleId);
                //将角色ID对应的角色名称也加入构建角色名集合中。
                roleNameSet.add(roleName);
            }

            //创建授权信息对象,将对应shiroUser的权限集和角色名集加入授权信息对象并返回。
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            info.addStringPermissions(permissionSet);
            info.addRoles(roleNameSet);
            return info;
        }

    }

3. 自定义注解@Permission:关于自定义注解上文中也提到过@Bussinesslog的自定义注解,类比学习下。

Shiro框架中也含有权限管理的注解:@RequiresPermissions("/xxx/xxx")
自定义注解@Permission,利用AOP的方式进行权限管理:参数需要说明,是角色ID,不是请求URL。
其他注解使用和含义可以参考上文,这里就不赘述了。
/**
 * 权限注解 用于检查权限 规定访问权限
 *
 * @example @Permission({roleID1,roleID2})
 * @example @Permission
 */
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Permission {
    String[] value() default {};
}

4. PermissionAop:采用@Permission注解结合AOP进行权限管理。

//在ShiroConfig中我们下应该先进行shiro授权注解方式进行拦截,AOP式方法级别的权限检查。
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
    AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
            new AuthorizationAttributeSourceAdvisor();
    authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
    return authorizationAttributeSourceAdvisor;
}

构建AOP:PermissionAop.java

@Aspect
@Component
public class PermissionAop {

    //声明切点,对@Permission注解进行拦截。
    @Pointcut(value = "@annotation(com.stylefeng.guns.common.annotion.Permission)")
    private void cutPermission() {

    }

    //通知类型为环绕通知
    @Around("cutPermission()")
    public Object doPermission(ProceedingJoinPoint point) throws Throwable {
        MethodSignature ms = (MethodSignature) point.getSignature();
        Method method = ms.getMethod();
        //通过注解接口获得实例
        Permission permission = method.getAnnotation(Permission.class);
        //获取注解中的唯一成员变量value集合
        Object[] permissions = permission.value();
        //判断value集合是否为空 -- 刚才指出了value中包含的是角色ID集
        if (permissions == null || permissions.length == 0) {
            //检查全体角色
            boolean result = PermissionCheckManager.checkAll();
            if (result) {
                return point.proceed();
            } else {
                throw new NoPermissionException();
            }
        } else {
            //检查指定角色
            boolean result = PermissionCheckManager.check(permissions);
            if (result) {
                return point.proceed();
            } else {
                throw new NoPermissionException();
            }
        }

    }

}

这里需要说明的是PermissionCheckManager中的检查方法:checkAll()和check()。

@Override
public boolean checkAll() {
    //从http中获取请求request
    HttpServletRequest request = HttpKit.getRequest();
    //通过ShiroKit工具类获取shiroUser对象
    ShiroUser user = ShiroKit.getUser();
    //对象判空处理
    if (null == user) {
        return false;
    }
    //对request请求进行分割 因为资源(权限)都是URL形式进行存储的,当用户subject进行访问的时候,request请求应该进行处理后进行比对。
    String requestURI = request.getRequestURI().replaceFirst(ConfigListener.getConf().get("contextPath"), "");
    String[] str = requestURI.split("/");
    if (str.length > 3) {
        requestURI = "/" + str[1] + "/" + str[2];
    }
    //判断是否有requestURL权限
    if (ShiroKit.hasPermission(requestURI)) {
        return true;
    }
    return false;
}

@Override
public boolean check(Object[] permissions) {
    ShiroUser user = ShiroKit.getUser();
    if (null == user) {
        return false;
    }
    //对指定角色ID进行权限检查,采用工具类对角色ID集进行处理
    String join = CollectionKit.join(permissions, ",");
    //验证当前shiroUser属于join角色ID集中的任意一个角色
    if (ShiroKit.hasAnyRoles(join)) {
        return true;
    }
    return false;
}

5. Shiro标签:在前端页面中进行页面展示控制的标签。

例:根据权限的不同,我们要根据不同角色进行有区分的内容展示。针对普通用户,只有对信息的查看权限,没有增加、修改、删除的权限。
因此,我们在前端页面渲染的时候要进行有选择的区分显示。
根据之前做过的订单管理的Demo,其中order.html中的按钮就进行了权限的显示控制,通过Shiro标签进行判断内容是否应被显示。
<div class="hidden-xs" id="OrderTableToolbar" role="group">
    @if(shiro.hasPermission("/order/add")){
        <#button name="添加" icon="fa-plus" clickFun="Order.openAddOrder()"/>
    @}
    @if(shiro.hasPermission("/order/update")){
        <#button name="修改" icon="fa-plus" clickFun="Order.openOrderDetail()" space="true"/>
    @}
    @if(shiro.hasPermission("/order/delete")){
        <#button name="删除" icon="fa-plus" clickFun="Order.delete()" space="true"/>
    @}
</div>

6. 权限管理步骤:在Guns框架中如何使用权限管理。

Guns框架中的权限管理构成:用户管理角色管理菜单管理

1. 通过Guns-admin的菜单管理进行资源权限的添加。
2. 在角色管理中,建立角色并进行权限配置。
3. 在用户管理中,建立用户并将其分配给指定的角色。
4. 在Controller层中需要进行权限控制的方法添加@Permission注解。
5. 前端页面如果需要对权限内容进行区分显示,采用Shiro的hasPermission标签进行配置。

8/7/2018 4:55:34 PM

猜你喜欢

转载自blog.csdn.net/Nerver_77/article/details/81484635