Shiro-SpringBoot (二)
创始人
2024-03-13 22:01:55

在上一节中实现了在SpringBoot中使用Shiro做权限控制,但是针对上一节留下的不足点,在这里进行一下优化和改造,主要有一下几点:

  • 支持AJAX请求
  • 支持FreeMarker模板
  • URL拦截提取到yml配置文件

(一) 支持AJAX请求

如果是AJAX请求URL接口,没有登录或是没有权限的时候,我们希望是返回指定的JSON格式,而不是进行页面的重定向。当时这个问题也是困扰很久,好在Shiro在这方面提供了很好的扩展,在google帮助下,我决定使用的方案是重写Shiro提供的Filter过滤器,在没有登录或是角色权限认证失败时进行处理。如果是AJAX请求返回指定JSON,普通请求进行页面的重定向。于是乎我找到如下Shiro Filter的关系图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-anOXjCjW-1669988699578)(https://img-blog.csdn.net/20170601195007717?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxNDA0MjE0Ng==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)]

  • AdviceFilter 过滤器类似于类似于SpringMVC中的HandlerInterceptor拦截器,用于在请求URL之前进行拦截,主要处理一些是否登录的验证
  • PermissionsAuthorizationFilter提供了onAccessDenied方法,主要用于perms资源认证失败时回调onAccessDenied进行一些处理
  • RolesAuthorizationFilter提供了onAccessDenied方法,主要用于roles角色认证失败时回调onAccessDenied进行一些处理

(1) 重写AdviceFilter过滤器

class ShiroAdviceFilter extends AdviceFilter {/*** 在访问URL前判断是否登录* @param request* @param response* @return true-继续往下执行,false-该filter过滤器已经处理,不继续执行其他过滤器* @throws Exception*/@Overrideprotected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest httpServletRequest = (HttpServletRequest) request;HttpServletResponse httpServletResponse = (HttpServletResponse) response;User user = (User) httpServletRequest.getSession().getAttribute("user");if (null == user && !StringUtils.contains(httpServletRequest.getRequestURI(), "/login")&& !StringUtils.contains(httpServletRequest.getRequestURI(), "/js")&& !StringUtils.contains(httpServletRequest.getRequestURI(), "/images")) {String requestedWith = httpServletRequest.getHeader("X-Requested-With");if (StringUtils.isNotEmpty(requestedWith) && StringUtils.equals(requestedWith, "XMLHttpRequest")) {//如果是ajax返回指定数据JSONObject json = new JSONObject();json.put("statuscode":500);json.put("message":"没有登录");httpServletResponse.setCharacterEncoding("UTF-8");httpServletResponse.setContentType("application/json");httpServletResponse.getWriter().write(JSONObject.toJSONString(json));return false;} else {//不是ajax进行重定向处理httpServletResponse.sendRedirect("/login");return false;}}return true;}}

(2) Filter过滤器工具类

public class FilterUtils {/*** 如果是ajax返回指定格式数据,如果是普通请求重定向页面* @param servletRequest* @param servletResponse* @throws IOException*/public static void AuthorizationOnAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException {HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;String requestedWith = httpServletRequest.getHeader("X-Requested-With");if (StringUtils.isNotEmpty(requestedWith) &&StringUtils.equals(requestedWith, "XMLHttpRequest")) {//如果是ajax返回指定格式数据JSONObject json = new JSONObject();json.put("statuscode":403);json.put("message":"角色资源认证失败");httpServletResponse.setCharacterEncoding("UTF-8");httpServletResponse.setContentType("application/json");httpServletResponse.getWriter().write(JSONObject.toJSONString(responseHeader));} else {//如果是普通请求进行重定向httpServletResponse.sendRedirect("/403");}}}

(3) 重写PermissionsAuthorizationFilter过滤器

public class ShiroPermsFilter extends PermissionsAuthorizationFilter {/*** shiro认证perms资源失败后回调方法* @param servletRequest* @param servletResponse* @return* @throws IOException*/@Overrideprotected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException {FilterUtils.AuthorizationOnAccessDenied(servletRequest,servletResponse);return false;}
}

(4) 重写RolesAuthorizationFilter过滤器

public class ShiroRolesFilter extends RolesAuthorizationFilter {/*** shiro认证roles角色失败后回调方法* @param servletRequest* @param servletResponse* @return* @throws IOException*/@Overrideprotected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException {FilterUtils.AuthorizationOnAccessDenied(servletRequest,servletResponse);return false;}
}

(5) 在上一节Shiro配置类ShiroConfiguration对我们重写的Filter过滤器进行配置,修改如下

//增加ShiroLoginFilter实例化@Bean(name = "shiroLoginFilter")public ShiroLoginFilter shiroLoginFilter(){ShiroLoginFilter shiroLoginFilter = new ShiroLoginFilter();return shiroLoginFilter;}//修改shiroFilterFactoryBean配置@Bean(name = "shiroFilter")public ShiroFilterFactoryBean shiroFilterFactoryBean() {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager());Map filtersMap = new LinkedHashMap();filtersMap.put("shiroLoginFilter", shiroLoginFilter());filtersMap.put("perms", new ShiroPermsFilter());filtersMap.put("roles",new ShiroRolesFilter());shiroFilterFactoryBean.setFilters(filtersMap);Map filterChainDefinitionMap = new LinkedHashMap();//Shiro拦截URL规则filterChainDefinitionMap.put("/logout", "logout");filterChainDefinitionMap.put("/js/**", "anon");filterChainDefinitionMap.put("/css/**", "anon");filterChainDefinitionMap.put("/login", "anon");filterChainDefinitionMap.put("/403", "anon");filterChainDefinitionMap.put("/userInfo/**", "authc,perms[查看用户模块]");filterChainDefinitionMap.put("/messageInfo/**", "authc,roles[管理员]");filterChainDefinitionMap.put("/**", "authc");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}

可以看到我们用的是LinkedHashMap将我们写的ShiroLoginFilter、ShiroPermsFilter、ShiroRolesFilter存储起来并设置。这代表用户每一次URL请求,都会按顺序的经过ShiroLoginFilter -> ShiroPermsFilter -> ShiroRolesFilter处理,只有全部都通过认证才会请求成功,否则将返回JSON或是重定向。这样Shiro既能兼容普通请求的重定向,也能在AJAX时返回JSON,流程图如下:
cmd-markdown-logo

(二) 支持FreeMarker模板

Shiro本身是通提供了JSP的一套标签库,用于在JSP进行权限的控制,对FreeMarker官方是没有提供。但是有大神将其开源到了GitHub,参考shiro-freemarker-tags。下面讲述的是,在SpringBoot项目中使用shiro-freemarker-tags

(1) 在pom.xml中导入依赖

net.mingsoftshiro-freemarker-tags0.1

(2) 新建FreeMarkerConfig类用于配置FreeMarkers标签

@Service
public class FreeMarkerConfig implements InitializingBean {@Autowiredprivate Configuration configuration;@Overridepublic void afterPropertiesSet() throws Exception {configuration.setSharedVariable("shiro", new ShiroTags());}}

(3) 在FreeMarker页面使用标签库进行权限控制

<@shiro.hasRole name="ADMIN">

只有ADMIN的角色才能看到这段文字

<@shiro.hasAnyRoles name="admin,user,member">

只有admin或user或member其中一个角色才能看到这段文字

<@shiro.lacksRole name="admin">

只有不拥有admin角色才能看到这段文字

<@shiro.hasPermission name="查看用户模块">

只有拥有【查看用户模块】资源的用户才能看到这段文字

<@shiro.guest>您当前是游客,关于更多使用请自行查阅标签库

(三) URL拦截提取到yml配置文件

之前在ShiroConfiguration配置类的shiroFilterFactoryBean方法中,我们拦截的URL使用LinkedHashMap写在了代码里面,这样后期维护和可观性不够好,我们可以把URL的拦截单独提取到yml配置文件。这里不选择properties文件,是因为properties文件是不支持有序的,这里URL的顺序请务必保持有序的加载。

(1) 在yml文件中配置我们的URL拦截规则

shiro:filterUrl:/logout: logout/js/**: anon/css/**: anon/images/**: anon/login/**: anon/logout/**: anon/403/**: anon/userInfo/**: authc,perms[查看用户模块]/messageInfo/**: authc,roles[管理员]/**: authc

(2) 新建ShiroUrlStrategy用于从yml加载配置

@EnableConfigurationProperties
@ConfigurationProperties(prefix = "shiro") //yml配置文件的前缀
public class ShiroUrlStrategy {private LinkedHashMap filterChainDefinitionMap;public LinkedHashMap getFilterChainDefinitionMap() {return filterChainDefinitionMap;}public void setFilterChainDefinitionMap(LinkedHashMap filterChainDefinitionMap) {this.filterChainDefinitionMap = filterChainDefinitionMap;}
}

(3) 修改ShiroConfiguration配置类

//修改shiroFilterFactoryBean配置@Bean(name = "shiroFilter")public ShiroFilterFactoryBean shiroFilterFactoryBean() {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager());Map filtersMap = new LinkedHashMap();filtersMap.put("shiroLoginFilter", shiroLoginFilter());filtersMap.put("perms", new ShiroPermsFilter());filtersMap.put("roles",new ShiroRolesFilter());shiroFilterFactoryBean.setFilters(filtersMap);//从配置获取URL拦截规则ShiroUrlStrategy shiroUrlStrategy = ShiroUrlStrategy();LinkedHashMap filterChainDefinitionManager = shiroUrlFiler.getFilterChainDefinitionMap();shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionManager);return shiroFilterFactoryBean;}

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...