本文是在前两篇的基础上来完成的:Springboot整合shiro:第二篇 用户授权 及Springboot整合shiro:第一篇 用户验证。
本来第二篇中已经完成了Shrio用户授权的功能,但是有采用注解的方式不够灵活,一旦权限发生改变就必须得修改代码重新打包部署,麻烦。所以,这篇采用自定义URL过滤器的方式来实现,权限可以自由随意配置/分配,无需改动代码,更加灵活。
编写自定义过滤器
package com.xl.practice.springbootshiropractice.shiro;
import java.util.Set;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.PathMatchingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.xl.practice.springbootshiropractice.service.UserService;
import com.xl.practice.springbootshiropractice.util.SpringContextUtils;
/**
* 自定义的URL拦截器
*
* @author Administrator
* */
public class CustomisedURLPathMatchingFilter extends PathMatchingFilter {
@Autowired
UserService userService;
@Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue)
throws Exception {
if(null==userService)
userService = SpringContextUtils.getContext().getBean(UserService.class);
// 获取请求的地址URI
String requestURI = getPathWithinApplication(request);
if (requestURI == null || requestURI.trim().equals(""))
return false;
// 截取掉URI中的“/”
requestURI = requestURI.substring(1, requestURI.length());
Subject subject = SecurityUtils.getSubject();
/**
* 如果没有登录,就跳转到登录页面
*/
if (!subject.isAuthenticated()) {
WebUtils.issueRedirect(request, response, "/login");
return false;
}
/**
* 验证该路径有没有维护(即是数据库中是否配置了该权限),如果没有维护,一律放行(也可以改为一律不放行)
*/
boolean needInterceptor = userService.maintainable(requestURI);
if (!needInterceptor) {
return true;
} else {
boolean ownPrivilege = false;
String name = subject.getPrincipal().toString();
Set<String> currentUserPrivileges = userService.listPrivilege(name);
if (currentUserPrivileges == null)
return false;
for (String p : currentUserPrivileges) {
if (p.equals(requestURI)) {
ownPrivilege = true;
break;
}
}
if (ownPrivilege) {
return true;
} else {
UnauthorizedException ex = new UnauthorizedException("当前用户没有访问路径 " + requestURI + " 的权限");
subject.getSession().setAttribute("ex", ex);
WebUtils.issueRedirect(request, response, "/unauthorized");
return false;
}
}
}
}
解释
- 拦截用户请求
- 判断是否登录,否,返回到登录的地址‘/login’
- 已登录,请求的地址是否在维护,没有维护–放行(也可不放行,自己根据需要设置,这里允许 放行)
- 已登录,请求的地址在维护,查询当前登录的用户是否有该权限(也就是请求地址),有 --放行,无–不放行(跳转到无权限地址“/unauthorized”)
需要编写工具类SpringContextUtils以实例化UserService
package com.xl.practice.springbootshiropractice.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringContextUtils implements ApplicationContextAware {
private static ApplicationContext context;
public void setApplicationContext(ApplicationContext context)
throws BeansException {
SpringContextUtils.context = context;
}
public static ApplicationContext getContext(){
return context;
}
}
在Shrio中配置自定义拦截器
需要在ShrioConfiguration.java中的shirFilter方法中配置
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面。
shiroFilterFactoryBean.setLoginUrl("/notLogin");
//未授权访问,跳转的地址setUnauthorizedUrl方法只针对部分过滤器有效,有些过滤器是无效的
// shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
/**
* 自定义拦截器
*/
Map<String, Filter> customisedFilter = new HashMap<>();
customisedFilter.put("url", new CustomisedURLPathMatchingFilter());
// 配置映射关系
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/**", "authc");
filterChainDefinitionMap.put("/**", "url");
shiroFilterFactoryBean.setFilters(customisedFilter);
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
其中,/** * 自定义拦截器 */ Map<String, Filter> customisedFilter = new HashMap<>(); customisedFilter.put("url", new CustomisedURLPathMatchingFilter());
和filterChainDefinitionMap.put("/**", "url");
是添加的配置
新增方法
TestContorller.java
@RequestMapping("/DRINKING_WINE")
public String drinkWine() {
return "苍老师 有权限进来!drink_wine";
}
@RequestMapping("/SMOKING")
public String smoking() {
return "苍老师 有权限进来! smoking";
}
@RequestMapping("/PLAY_GAME")
public String game() {
return "小炮同学 有权限进来! play_game";
}
只需正常配置访问地址就行,这些地址如:DRINKING_WINE 、SMOKING、PLAY_GAME在数据库中通过权限表进行维护。而这些地址(权限)可动态分配给用户(可通过后台权限配置程序完成甚至也可以直接手动修改数据库来完成分配)。
/*
权限表
*/
DROP TABLE IF EXISTS privilege;
CREATE TABLE privilege (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT'自增id',
name VARCHAR(64) NOT NULL COMMENT'权限名称',
remark VARCHAR(64) COMMENT'备注'
) COMMENT = '权限表';
INSERT INTO privilege (name,remark) VALUES("DRINKING_WINE","喝酒");
INSERT INTO privilege (name,remark) VALUES("SMOKING","抽烟");
INSERT INTO privilege (name,remark) VALUES("PLAY_GAME","玩游戏");
UserService.java
/**
* 请求的URI是否在维护
* @param requestURI
* @return
*/
boolean maintainable(String requestURI);
UserServiceImpl.java
@Override
public boolean maintainable(String requestURI) {
Set<String> privileges = userMapper.getAllPrivileges();
if (privileges == null)
return false;
for (String p : privileges) {
if (p.equals(requestURI))
return true;
}
return false;
}
UserMapper.java
/**
* 查询所有的权限
* @return
*/
Set<String> getAllPrivileges();
UserMapper.xml
<!--查询所有的权限 -->
<select id="getAllPrivileges" resultType="string">
SELECT name FROM privilege;
</select>
验证
根据之前的权限配置情况可得,苍老师 (111) 拥有权限 DRINKING_WIN和SMOKING ; 小炮同学 (666)拥有权限 PLAY_GAME
- 用户 苍老师 访问 PLAY_GAME地址(权限)—无权限
- 用户苍老师访问 SMOKING地址(权限)—有权限
- 用户小炮同学访问PLAY_GAME地址(权限)—有权限
- 用户小炮同学访问SMOKING地址(权限)—无权限