文章目录
- 背景
- 解决方案
- JAVA后台变更:
- 前端变更:
- 源码地址:
背景
昨天被网安大队扫描安全漏铜,提到有个致命的安全漏洞:数据接口平行越权,要求半个月内整改完毕。数据平行越权用大白话说就是:我先用超级管理员登录后台系统,然后我记住了菜单A对应的URL。我在用普通管理员登录,但是没有菜单A的权限,但是我可以直接通过菜单A的URL进行访问。
数据平行越权的漏洞最根本问题是,系统没有针对接口级别的权限控制。好多系统都是所有接口做个拦截器,只校验了是否登录,并没有把用户、角色权限和接口给关联起来。
下图为网安大队安全漏洞报告截图:
以下讲下java web框架的解决思路。
解决方案
JAVA后台变更:
- pom.xml 引入shiro对应的jar包:
<!-- 对shiro的支持 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!-- shiro对redis的支持 -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>2.4.2.1-RELEASE</version>
</dependency>
- 需要权限控制的接口加入以下注解:
@RequiresPermissions("student:getStudent")
@RequestMapping(value = "getStudent",method= RequestMethod.POST)
public ModelAndView getStudent()
{
return new ModelAndView("/student");
}
- application.yml 配置shiro 和redis 信息:
spring:
redis:
shiro :
host: 127.0.0.1
port: 6379
timeout : 0
password:
- 统一异常处理类:MyExceptionHandler.java
@Slf4j
public class MyExceptionHandler implements HandlerExceptionResolver {
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception ex) {
ex.printStackTrace();
ModelAndView mv = new ModelAndView();
FastJsonJsonView view = new FastJsonJsonView();
Map<String, Object> attributes = new HashMap<String, Object>();
if (ex instanceof UnauthenticatedException) {
attributes.put("sucess", "false");
attributes.put("info", "token错误");
} else if (ex instanceof UnauthorizedException) {
attributes.put("sucess", "false");
attributes.put("info", "用户无权限");
} else {
attributes.put("sucess", "false");
attributes.put("info", ex.getMessage());
log.error("发生未处理的异常={}",ex.getMessage(),ex);
}
view.setAttributesMap(attributes);
mv.setView(view);
return mv;
}
}
- 新建权限控制:MyShiroRealm.java
public class MyShiroRealm extends AuthorizingRealm {
@Resource
private SysUserService sysUserService;
@Autowired
private SysRoleService sysRoleService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
{
//获取username
SysUser sysUser=(SysUser) SecurityUtils.getSubject().getSession().getAttribute("sysUserLogin");
List<SysPrivilege> sysPrivilegeList=sysPrivilegeList=sysRoleService.getSysPrivilegeByRoid(sysUser.getRoleId());
// 用户权限列表
Set<String> permsSet = new HashSet<>();
for (SysPrivilege perms : sysPrivilegeList)
{
if (StringUtils.isBlank(perms.getShiroID()))
{
continue;
}
permsSet.addAll(Arrays.asList(perms.getShiroID().trim().split(",")));
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(permsSet);
return info;
}
@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分钟内不会重复执行该方法
SysUser userInfo = sysUserService.getByUserCode(username);
// System.out.println("----->>userInfo="+userInfo);
if (userInfo == null) {
return null;
}
if (Integer.parseInt(userInfo.getStates()) == 1) { //账户冻结
throw new LockedAccountException();
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
token.getCredentials(), //用户名
userInfo.getPassword(),//salt=username+salt
getName() //realm name
);
return authenticationInfo;
}
}
- 新建shiro 管理类:MySessionManager.java
public class MySessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "Authorization";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public MySessionManager() {
super();
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
//适合于安卓,ios的或者前后端分离情况,直接在head头中传入sessionId的值(sessionId的键名为Authorization)
if (!StringUtils.isEmpty(id)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
} else {
//适用于浏览器访问,否则按默认规则从cookie取sessionId
return super.getSessionId(request, response);
}
}
}
- 新建shiro 配置规则类:ShiroConfig.java
@Configuration
public class ShiroConfig {
@Value("${spring.redis.shiro.host}")
private String host;
@Value("${spring.redis.shiro.port}")
private int port;
@Value("${spring.redis.shiro.timeout}")
private int timeout;
@Value("${spring.redis.shiro.password}")
private String password;
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
//注意过滤器配置顺序 不能颠倒
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了,登出后跳转配置的loginUrl
filterChainDefinitionMap.put("/loginout", "logout");
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/jqueryEasyui
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
return hashedCredentialsMatcher;
}
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
// 自定义session管理 使用redis
securityManager.setSessionManager(sessionManager());
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
return securityManager;
}
//自定义sessionManager
@Bean
public SessionManager sessionManager() {
MySessionManager mySessionManager = new MySessionManager();
mySessionManager.setSessionDAO(redisSessionDAO());
return mySessionManager;
}
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setExpire(1800);// 配置缓存过期时间(登录是失效时间,0代表永不失效)
redisManager.setTimeout(timeout);
redisManager.setPassword(password);
return redisManager;
}
@Bean
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean(name = "exceptionHandler")
public HandlerExceptionResolver handlerExceptionResolver() {
return new MyExceptionHandler();
}
}
前端变更:
- 前端需要根据自己的框架,作统一的底层判断然后跳到统一的权限异常页面。
- 后台界面配置也需要支持对shiroid的管理。如下效果:
源码地址:
获取完整源码地址: https://download.csdn.net/download/penggerhe/11670196