在spring boot中使用shiro进行简单的权限拦截

时间:2021-04-05 15:20:14

其实不知道要讲什么,但是又毕竟花了自己的一些时间,所以还是简单记录下这次学习并使用shiro的过程。

以前还真没在意过用户角色权限这方面的东西,因为平时业务代码写的很多,而且用户权限这部分每个公司基本都封装了一套,所以在此之前都没听说过shiro是个什么鬼。正好这次有个项目是前后端都重新开发,包括用户权限都需要自己写,所以无奈之下去百度了下相关的一些框架。谈到最多的就是spring security和shiro了。至于这两者有什么区别,以及为什么要使用shiro就不在此多说了,很多相关文章教程都讲的很详细,有空可以好好去了解一下。

在没有这些框架以前都是如何实现用户权限的问题呢?有的是直接根据用户去展示所拥有的权限列表,还有的就是直接在拦截器里面判断当前的请求是否与用户的权限列表相匹配。那么有了框架以后会有什么变化呢?其实很简单,不管是什么框架和工具,再怎么智能,还是离不开原始数据的,所以框架顶多就是帮我们做了身份认证和权限匹配的问题。我们只要提供数据即可,所以归根结底,原理是没变的,只是帮我们减少了一些操作,而且更加安全些。

废话不多说,直接贴上代码,由于第一次写没有太多经验,所以仅供参考,至于正确性和合理性就不保证了。

a.添加maven依赖

在spring boot中使用shiro进行简单的权限拦截

b.处理配置信息

/**
 * created by huxm on 2018/6/4
 */
@Configuration
public class ShiroConfiguration {
    /**
     * 将自定义的验证方式加入容器
     * @return
     */
    @Bean
    public ShiroRealm shiroRealm() {
        ShiroRealm shiroRealm = new ShiroRealm();
        return shiroRealm;
    }

    /**
     * 权限管理,配置主要是Realm的管理认证  这个类组合了登陆,登出,权限,session的处理,是个比较重要的类。
     * @return
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(shiroRealm());
        return securityManager;
    }


    /**
     * Filter工厂,设置对应的过滤条件和跳转条件
     *
     * ShiroFilterFactoryBean,是个factorybean,为了生成ShiroFilter。
     * 它主要保持了三项数据,securityManager,filters,filterChainDefinitionManager。
     */
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager());
        Map<String,String> map = new HashMap<>();
        /*//登出
        map.put("/logout","logout");
        //对所有用户认证
        map.put("/**","authc");
        //登录
        shiroFilterFactoryBean.setLoginUrl("/login");
        //首页
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //错误页面,认证不通过跳转
        shiroFilterFactoryBean.setUnauthorizedUrl("/error");*/
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    /**
     * 加入注解的使用,不加入这个注解不生效
     *
     * AuthorizationAttributeSourceAdvisor,shiro里实现的Advisor类,
     * 内部使用AopAllianceAnnotationsAuthorizingMethodInterceptor来拦截用以下注解的方法。
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor aASA = new AuthorizationAttributeSourceAdvisor();
        aASA.setSecurityManager(securityManager());
        return aASA;
    }
}

c.编写自定义的Realm(关于realm部分,不清楚的可以去网上看了解)

/**
 * created by huxm on 2018/6/4
 */
public class ShiroRealm extends AuthorizingRealm {
    Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    UserInfoMapper userInfoMapper;
    @Autowired
    PrivilegeInfoMapper privilegeInfoMapper;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        logger.error("doGetAuthorizationInfo+"+principalCollection.toString());

        UserInfo userInfo = (UserInfo) principalCollection.getPrimaryPrincipal();

        //把principals放session中 key=userId value=principals
        SecurityUtils.getSubject().getSession().setAttribute(String.valueOf(userInfo.getId()),SecurityUtils.getSubject().getPrincipals());

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //赋予角色
        RoleInfo roleInfo = userInfoMapper.queryRoleByUserId(userInfo.getId());

        info.addRole(roleInfo.getName());

        //赋予权限(此处使用的是 url,没用权限名称)
        List<String> permissionUrls = privilegeInfoMapper.optMenuNamesByRoleId(roleInfo.getId());
        for (String permission : permissionUrls) {
            //Wildcard string cannot be null or empty. Make sure permission strings are ... 否则会报这个异常
            if (Utils.isEmpty(permission)) {
                continue;
            }
            info.addStringPermission(permission);
        }

        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //加这一步的目的是在Post请求的时候会先进认证,然后在到请求
        if (authenticationToken.getPrincipal() == null) {
            return null;
        }
        //获取用户信息
        String userName = authenticationToken.getPrincipal().toString();
        UserInfo userInfo = userInfoMapper.queryByUserName(userName);
        if (userInfo == null) {
            //这里返回后会报出对应异常
            return null;
        } else {
            //这里验证authenticationToken和simpleAuthenticationInfo的信息
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userInfo, userInfo.getUserPwd().toString(), getName());
            return simpleAuthenticationInfo;
        }
    }
}

以上部分都是关于realm的配置信息,基本都是差不多的步骤。其实就是我们从库里面查询信息交给shiro,至于后面如何验证和授权的可以找找相关的源码看看。

以下部分就不固定了,可以根据自己的实际业务去写。由于我这边使用的是操作权限,所有菜单和功能按钮都在页面进行展示,所以我这里就把需要进行权限校验的接口做加了一个注解来进行标识,没有实质意义。然后在拦截器中对具有注解的接口进行用户权限校验,授权通过就没有问题,没通过就代表没有权限。

d.权限注解

/**
 * created by huxm on 2018/6/10
 *
 * Desc:这个注解没什么实际卵用 只是为了标记需要进行权限校验的url
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionService {

}

e.权限拦截器

/**
 * created by huxm on 2018/6/10
 */
@Component
public class PermissionInterceptor implements HandlerInterceptor {

    Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    OperateuRedisUtil operateuRedisUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.先判断请求url是否有 权限注解  有则代表需要做权限校验 无则直接放行
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        PermissionService permissionService = handlerMethod.getMethodAnnotation(PermissionService.class);
        if (permissionService == null) {
            return true;
        }

        //2.走到这里 说明需要对管理员进行操作权限的校验
        //a.查询当前用户
        try {
            UserInfo userInfo = operateuRedisUtil.getCurrentUser();

            //b.添加用户认证信息(使用的是shiro的验证功能)
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userInfo.getUserName(), userInfo.getUserPwd());

            //登陆
            subject.login(usernamePasswordToken);

//        System.out.println("是否认证:"+subject.isAuthenticated());

            //验证角色
//        subject.checkRole("admin");

            //验证权限
            String url = request.getRequestURI();

            subject.checkPermission(url);
        } catch (Exception e) {
            logger.error("权限校验失败!");
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/json;charset=UTF-8");
            R r = new R(R.CODE_NO_PERMISSION, "用户权限校验失败!");
            response.getWriter().write(JsonUtil.toJson(r));
            response.getWriter().flush();
            return false;
        }

        return true;
    }

}

f.将拦截器加入到配置信息中

/**
 * created by huxm on 2018/6/10
 *
 *
 * Desc:对pc端管理员的操作权限拦截
 */
@Configuration
public class PermissionConfig implements WebMvcConfigurer {
    @Autowired
    PermissionInterceptor permissionInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 添加权限拦截器
        registry.addInterceptor(permissionInterceptor);
    }
}

其他貌似差不多了,就先这样吧。。。