1.拦截器配置
Shiro默认提供了13个拦截器
public enum DefaultFilter {
anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
authcBearer(BearerHttpAuthenticationFilter.class),
logout(LogoutFilter.class),
noSessionCreation(NoSessionCreationFilter.class),
perms(PermissionsAuthorizationFilter.class),
port(PortFilter.class),
rest(HttpMethodPermissionFilter.class),
roles(RolesAuthorizationFilter.class),
ssl(SslFilter.class),
user(UserFilter.class),
invalidRequest(InvalidRequestFilter.class);
}
使用时,需要自己重写一个拦截器实现自己的拦截方式,本项目使用了Jwt进行拦截,所以重写一个JwtFilter,里面重写3个方法:
isAccessAllowed 登录验证
executeLogin 具体执行方法
preHandle 访问前处理
2.路径配置
然后是对拦截路径进行配置,Shiro拦截器初始化是在ShiroFilterFactoryBean这个工厂bean中实现,
* @see org.springframework.web.filter.DelegatingFilterProxy DelegatingFilterProxy
* @since 1.0
public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor
所以在项目中需编写一个ShiroConfig配置类,注入这个工厂bean,本项目中代码简写如下,
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
//构造一个工厂实例
ShiroFilterFactoryBean shiroFilterFactoryBean =new ShiroFilterFactoryBean();
//设置安全处理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截器
Map filterChainDefinitionMap =new LinkedHashMap();
//排除配置文件中不需要过滤的url
if(oConvertUtils.isNotEmpty(excludeUrls)){
String[] permissionUrl =excludeUrls.split(",");
for(String url : permissionUrl){
filterChainDefinitionMap.put(url,"anon");
}
}
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/sys/cas/client/validateLogin", "anon"); //cas验证登录
filterChainDefinitionMap.put("/sys/randomImage/**", "anon"); //登录验证码接口排除
filterChainDefinitionMap.put("/sys/checkCaptcha", "anon"); //登录验证码接口排除
......
// 添加自己的过滤器并且取名为jwt
Map filterMap =new HashMap(1);
filterMap.put("jwt", new JwtFilter());
shiroFilterFactoryBean.setFilters(filterMap);
// <!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边
filterChainDefinitionMap.put("/**", "jwt");
// 未授权界面返回JSON
shiroFilterFactoryBean.setUnauthorizedUrl("/sys/common/403");
shiroFilterFactoryBean.setLoginUrl("/sys/common/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
配置完之后,Spring在启动时会调用ShiroFilterFactoryBean的getObject()方法,
public Object getObject()throws Exception {
if (instance ==null) {
instance = createInstance();
}
return instance;
}
然后调用创建实例方法
protected AbstractShiroFilter createInstance()throws Exception {
SecurityManager securityManager = getSecurityManager();
if (securityManager ==null) {
String msg ="SecurityManager property must be set.";
throw new BeanInitializationException(msg);
}
if (!(securityManagerinstanceof WebSecurityManager)) {
String msg ="The security manager does not implement the WebSecurityManager interface.";
throw new BeanInitializationException(msg);
}
FilterChainManager manager = createFilterChainManager();
PathMatchingFilterChainResolver chainResolver =new PathMatchingFilterChainResolver();
chainResolver.setFilterChainManager(manager);
return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}
我们进入到加粗的createFilterChainManager()方法中,
protected FilterChainManager createFilterChainManager() {
DefaultFilterChainManager manager =new DefaultFilterChainManager();
//获取Shio自定义的那13个过滤器
Map defaultFilters = manager.getFilters();
for (Filter filter : defaultFilters.values()) {
applyGlobalPropertiesIfNecessary(filter);
}
//获取自己定义的过滤器JwtFilter
Map filters = getFilters();
if (!CollectionUtils.isEmpty(filters)) {
for (Map.Entry entry : filters.entrySet()) {
String name = entry.getKey();
Filter filter = entry.getValue();
applyGlobalPropertiesIfNecessary(filter);
if (filterinstanceof Nameable) {
((Nameable) filter).setName(name);
}
manager.addFilter(name, filter, false);
}
}
// set the global filters
manager.setGlobalFilters(this.globalFilters);
//这边是对定义的路径进行配置
Map chains = getFilterChainDefinitionMap();
if (!CollectionUtils.isEmpty(chains)) {
for (Map.Entry entry : chains.entrySet()) {
String url = entry.getKey();
String chainDefinition = entry.getValue();
manager.createChain(url, chainDefinition);
}
}
// create the default chain, to match anything the path matching would have missed
manager.createDefaultChain("/**");
return manager;
}
我们重点看加粗的createChain方法,
public void createChain(String chainName, String chainDefinition) {
if (!StringUtils.hasText(chainName)) {
throw new NullPointerException("chainName cannot be null or empty.");
}else if (!StringUtils.hasText(chainDefinition)) {
throw new NullPointerException("chainDefinition cannot be null or empty.");
}else {
if (log.isDebugEnabled()) {
log.debug("Creating chain [" + chainName +"] with global filters " +this.globalFilterNames +" and from String definition [" + chainDefinition +"]");
}
if (!CollectionUtils.isEmpty(this.globalFilterNames)) {
this.globalFilterNames.stream().forEach((filterName) -> {
this.addToChain(chainName, filterName);
});
}
String[] filterTokens =this.splitChainDefinition(chainDefinition);
String[] var4 = filterTokens;
int var5 = filterTokens.length;
for(int var6 =0; var6 < var5; ++var6) {
String token = var4[var6];
String[] nameConfigPair =this.toNameConfigPair(token);
this.addToChain(chainName, nameConfigPair[0], nameConfigPair[1]);
}
}
}
上面那几个报错判断我们忽略,我们看重点的addToChain方法,这里有2个这个方法,上面一个是全局过滤器对过滤路径进行处理,下面那个是自定义过滤器对过滤路径进行的处理,自定义的会覆盖全局的,我们进入这个方法中,
public void addToChain(String chainName, String filterName) {
this.addToChain(chainName, filterName, (String)null);
}
public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) {
if (!StringUtils.hasText(chainName)) {
throw new IllegalArgumentException("chainName cannot be null or empty.");
}else {
Filter filter =this.getFilter(filterName);
if (filter ==null) {
throw new IllegalArgumentException("There is no filter with name '" + filterName +"' to apply to chain [" + chainName +"] in the pool of available Filters. Ensure a filter with that name/path has first been registered with the addFilter method(s).");
}else {
this.applyChainConfig(chainName, filter, chainSpecificFilterConfig);
NamedFilterList chain =this.ensureChain(chainName);
chain.add(filter);
}
}
}
这里最关键的是applyChainConfig这个方法,继续进入这个方法,
protected void applyChainConfig(String chainName, Filter filter, String chainSpecificFilterConfig) {
if (log.isDebugEnabled()) {
log.debug("Attempting to apply path [" + chainName +"] to filter [" + filter +"] with config [" + chainSpecificFilterConfig +"]");
}
if (filterinstanceof PathConfigProcessor) {
((PathConfigProcessor)filter).processPathConfig(chainName, chainSpecificFilterConfig);
}else if (StringUtils.hasText(chainSpecificFilterConfig)) {
String msg ="chainSpecificFilterConfig was specified, but the underlying Filter instance is not an 'instanceof' " + PathConfigProcessor.class.getName() +". This is required if the filter is to accept chain-specific configuration.";
throw new ConfigurationException(msg);
}
}
继续进入processPathConfig这个方法,
public abstract class PathMatchingFilterextends AdviceFilterimplements PathConfigProcessor {
private static final String DEFAULT_PATH_SEPARATOR ="/";
protected PatternMatcher pathMatcher =new AntPathMatcher();
protected Map appliedPaths =new LinkedHashMap();
public PathMatchingFilter() {}
public Filter processPathConfig(String path, String config) {
String[] values =null;
if (config !=null) {
values = StringUtils.split(config);
}
this.appliedPaths.put(path, values);
return this;
}
我们发现Shiro最终是把路径全部放入PathMatchingFilter这个类里面的appliedPaths这个Map变量里
3.请求路径拦截
当请求进入系统时,会首先进入我们自定义的JwtFilter的preHandle方法中,我们交给父类去执行,super.preHandle(request, response),可以看到他的父类就是PathMatchingFilter,
protected boolean preHandle(ServletRequest request, ServletResponse response)throws Exception {
if (this.appliedPaths !=null && !this.appliedPaths.isEmpty()) {
Iterator var3 =this.appliedPaths.keySet().iterator();
String path;
do {
if (!var3.hasNext()) {
return true;
}
path = (String)var3.next();
}while(!this.pathsMatch(path, request));
Object config =this.appliedPaths.get(path);
return this.isFilterChainContinued(request, response, path, config);
}else {
if (log.isTraceEnabled()) {
log.trace("appliedPaths property is null or empty. This Filter will passthrough immediately.");
}
return true;
}
}
我们发现在这个方法里将之前保存的那些路径和当前请求的路径进行了一一的比对,从而实现了拦截效果