这篇文章分享一下:当一个服务提供者整合了shiro安全框架来实现权限访问控制时,服务消费者通过feign请求服务提供者的接口时的鉴权不通过问题。
问题描述
博主有一个项目pms(权限管理系统),使用了shiro框架来实现鉴权功能,使用的是shiro内置的perms过滤器。
而服务消费者端,通过feign访问RPC接口,很显然,由于是不同的项目,鉴权肯定是失败的。
package cn.edu.sgu.www.cms.feign.impl;
import cn.edu.sgu.www.cms.dto.PermissionInitDTO;
import cn.edu.sgu.www.cms.entity.Permission;
import cn.edu.sgu.www.cms.entity.User;
import cn.edu.sgu.www.cms.feign.FeignService;
import cn.edu.sgu.www.cms.restful.JsonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
import java.util.Set;
/**
* 权限平台feign接口
* @author heyunlin
* @version 1.0
*/
@FeignClient(name = "pms")
public interface PmsFeignService extends FeignService {
/**
* 根据用户名查询用户信息
* @param username 用户名
* @param service 应用名
* @return JsonResult<User>
*/
@RequestMapping(value = "/user/selectByUsername", method = RequestMethod.GET)
JsonResult<User> selectByUsername(
@RequestParam("username") String username,
@RequestParam("service") String service
);
/**
* 权限数据初始化
* @param permissionInitDTO 权限信息
* @return JsonResult<Void>
*/
@RequestMapping(value = "/permission/resources", method = RequestMethod.POST)
JsonResult<Void> resources(PermissionInitDTO permissionInitDTO);
/**
* 查询应用的非匿名子权限
* @param service 应用名
* @return JsonResult<List<Permission>>
*/
@RequestMapping(value = "/permission/selectPermissions", method = RequestMethod.GET)
JsonResult<List<Permission>> selectPermissions(@RequestParam("service") String service);
/**
* 查询应用的匿名子权限
* @param service 应用名
* @return JsonResult<List<String>>
*/
@RequestMapping(value = "/permission/selectAnonymityPermissions", method = RequestMethod.GET)
JsonResult<List<String>> selectAnonymityPermissions(@RequestParam("service") String service);
/**
* 通过用户名查询用户权限
* @param username 用户名
* @param service 应用名
* @return JsonResult<Set<String>>
*/
@RequestMapping(value = "/permission/selectUserPermissions", method = RequestMethod.GET)
JsonResult<Set<String>> selectUserPermissions(
@RequestParam("username") String username,
@RequestParam("service") String service
);
}
而在服务消费者启动过程中,需要向pms查询当前应用的权限,所以导致feign请求报错,项目无法启动问题。
package cn.edu.sgu.www.cms.config;
import cn.edu.sgu.www.cms.entity.Permission;
import cn.edu.sgu.www.cms.feign.impl.PmsFeignService;
import cn.edu.sgu.www.cms.restful.JsonResult;
import cn.edu.sgu.www.cms.shiro.UserRealm;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Shiro配置类
* @author heyunlin
* @version 1.0
*/
@Configuration
public class ShiroConfig {
@Value("${spring.application.name}")
private String service;
/**
* 登录页面地址
*/
private String loginPage = "/login.html";
/**
* 未授权的URL
*/
private String unauthorizedUrl = "/user/unauthorized";
private final PmsFeignService feignService;
@Autowired
public ShiroConfig(PmsFeignService feignService) {
this.feignService = feignService;
}
/**
* 配置安全管理器
* @param userRealm UserRealm
* @return DefaultWebSecurityManager
*/
@Bean
public DefaultWebSecurityManager securityManager(UserRealm userRealm, CacheManager cacheManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置缓存管理器
securityManager.setCacheManager(cacheManager);
// 把UserRealm注册到安全管理器
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 配置Shiro过滤器工厂
* @param securityManager 安全管理器
* @return ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 注册安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
/*
* 设置登录页面的地址
* 当用户访问认证资源的时候,如果用户没有登录,就会跳转到指定的页面
*/
shiroFilterFactoryBean.setLoginUrl(loginPage);
/*
* 设置访问未授权资源时重定向的地址
* 当用户访问需要授权才能访问资源的时候,如果用户没有该权限,就会跳转到指定的地址
*/
shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
// 定义资源访问规则
Map<String, String> filterMap = new LinkedHashMap<>();
/*
* 定义需要认证才能访问的资源
*/
filterMap.put("/", "authc");
filterMap.put("/index.html", "authc");
filterMap.put("/html/*.html", "authc");
// knife4j
filterMap.put("/doc.html", "authc");
filterMap.put("/v2/api-docs", "authc");
filterMap.put("/swagger-resources", "authc");
/*
* 定义不需要认证就能访问的资源
*/
filterMap.put(loginPage, "anon");
filterMap.put("/user/login", "anon");
filterMap.put(unauthorizedUrl, "anon");
// 查询当前服务的所有匿名子权限
JsonResult<List<String>> jsonResult = feignService.selectAnonymityPermissions(service);
if (jsonResult.isSuccess()) {
List<String> data = jsonResult.getData();
for (String url : data) {
filterMap.put(url, "anon");
}
}
/*
* 定义需要指定权限才能访问的资源
*/
// 查询当前服务的所有非匿名子权限
JsonResult<List<Permission>> result = feignService.selectPermissions(service);
if (result.isSuccess()) {
List<Permission> list = result.getData();
for (Permission permission : list) {
filterMap.put(permission.getUrl(), "perms[" + permission.getValue() + "]");
}
}
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
}
之前就遇到了这个问题,但是一直没有管,因为当时还没有想到什么解决的办法。
今天写项目,又遇上了,真气人~
解决方案
博主想到的是通过设置一个请求头,通过过滤器鉴权时先判断请求头是否正确,正确的话直接返回true,不再进行后续的操作。
于是在feign的官网看一下有没有能设置请求头的方法,果然,找到一个请求拦截器可以达到这个效果~
添加请求头
定义一个RequestInterceptor,给请求设置一个请求头perms。
package cn.edu.sgu.www.cms.feign;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;
/**
* @author heyunlin
* @version 1.0
*/
@Component
public class RequestHeaderInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
template.header("perms", "12345");
}
}
自定义过滤器
在服务生产者项目pms中定义一个过滤器PermsFilter,名字见名知义,就是shiro的perms过滤器。
重写shiro的perms过滤器(PermissionsAuthorizationFilter)的鉴权方法,先判断请求头是否为指定的值,如果是就跳过鉴权,直接返回true。
这样就避免了其他应用访问本应用的接口导致的鉴权失败问题了~
package cn.edu.sgu.www.pms.filter;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* 定义PermsFilter过滤器(覆盖shiro的perms过滤器)
* @author heyunlin
* @version 1.0
*/
@Slf4j
public class PermsFilter extends PermissionsAuthorizationFilter {
@Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
String perms = ((HttpServletRequest) request).getHeader("perms");
if (perms != null && perms.equals("12345")) {
log.debug("发现请求头perms,当前请求为搜权应用发起的请求,跳过鉴权~");
return true;
}
return super.isAccessAllowed(request, response, mappedValue);
}
}
注册过滤器到shiro
将刚刚创建的过滤器注册到shiro,博主直接把原来的perms过滤器覆盖掉了。
// 自定义过滤器
Map<String, Filter> filterMap = new HashMap<>();
filterMap.put("perms", new PermsFilter());
shiroFilterFactoryBean.setFilters(filterMap);
好了,文章就分享到这里了,希望看完之后对你有所帮助~