最近项目上使用shiro作为权限框架,因为是前后端分离的springboot项目,使用传统的shiro-spring包完全不能体现springboot的自动配置和简化配置,优化开发,所有深入探究了一下shiro-spring-boot-web-starter的使用,在此对于项目中使用到的进行记录,并对和shiro整合spring的包进行比较。
传统的shiroconfig文件
@Configuration
public class ShiroConfig {
/**
* ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意:初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
* Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截
* @param securityManager
* @return
*/
@Bean(name = "shirFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//必须设置 SecurityManager,Shiro的核心安全接口
shiroFilterFactoryBean.setSecurityManager(securityManager);
//这里的/login是后台的接口名,非页面,如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
//这里的/index是后台的接口名,非页面,登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
//未授权界面,该配置无效,并不会进行页面跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
//自定义拦截器限制并发人数,参考博客:
//LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>();
//限制同一帐号同时在线的个数
//filtersMap.put("kickout", kickoutSessionControlFilter());
//shiroFilterFactoryBean.setFilters(filtersMap);
// 配置访问权限 必须是LinkedHashMap,因为它必须保证有序
// 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 一定要注意顺序,否则就不好使了
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//配置不登录可以访问的资源,anon 表示资源都可以匿名访问
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/druid/**", "anon");
//logout是shiro提供的过滤器
filterChainDefinitionMap.put("/logout", "logout");
//此时访问/userInfo/del需要del权限,在自定义Realm中为用户授权。
//filterChainDefinitionMap.put("/userInfo/del", "perms[\"userInfo:del\"]");
//其他资源都需要认证 authc 表示需要认证才能进行访问
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 配置核心安全事务管理器
* @param shiroRealm
* @return
*/
@Bean(name="securityManager")
public SecurityManager securityManager(@Qualifier("shiroRealm") ShiroRealm shiroRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置自定义realm.
securityManager.setRealm(shiroRealm);
//配置记住我 参考博客:
//securityManager.setRememberMeManager(rememberMeManager());
//配置 redis缓存管理器 参考博客:
//securityManager.setCacheManager(getEhCacheManager());
//配置自定义session管理,使用redis 参考博客:
//securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* 配置Shiro生命周期处理器
* @return
*/
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 身份认证realm; (这个需要自己写,账号密码校验;权限等)
* @return
*/
@Bean
public ShiroRealm shiroRealm(){
ShiroRealm shiroRealm = new ShiroRealm();
return shiroRealm;
}
/**
* 必须(thymeleaf页面使用shiro标签控制按钮是否显示)
* 未引入thymeleaf包,Caused by: java.lang.ClassNotFoundException: org.thymeleaf.dialect.AbstractProcessorDialect
* @return
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
非自动配置的shiro必须向IOC容器注入自定义的Rleam,ShiroFilterFactoryBean,以及SecurityManager。才能进行基本的使用,更别说使用一块新功能都需要在配置文件注入一个。
现在我们用shiro-starter如何呢,我们先来看看他jar包中自动配置是怎么注入的。
直接找到jar包的Spring.factories
shiro-spring-boot-starter中的自配配置
public class ShiroBeanAutoConfiguration extends AbstractShiroBeanConfiguration {
public ShiroBeanAutoConfiguration() {
}
//生命周期后置处理器
@Bean
@ConditionalOnMissingBean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return super.lifecycleBeanPostProcessor();
}
@Bean
@ConditionalOnMissingBean
protected EventBus eventBus() {
return super.eventBus();
}
@Bean
@ConditionalOnMissingBean
public ShiroEventBusBeanPostProcessor shiroEventBusAwareBeanPostProcessor() {
return super.shiroEventBusAwareBeanPostProcessor();
}
}
----------------------------------------------
public class ShiroAutoConfiguration extends AbstractShiroConfiguration {
public ShiroAutoConfiguration() {
}
//认证策略
@Bean
@ConditionalOnMissingBean
protected AuthenticationStrategy authenticationStrategy() {
return super.authenticationStrategy();
}
@Bean
@ConditionalOnMissingBean
protected Authenticator authenticator() {
return super.authenticator();
}
//授权者
@Bean
@ConditionalOnMissingBean
protected Authorizer authorizer() {
return super.authorizer();
}
@Bean
@ConditionalOnMissingBean
protected SubjectDAO subjectDAO() {
return super.subjectDAO();
}
@Bean
@ConditionalOnMissingBean
protected SessionStorageEvaluator sessionStorageEvaluator() {
return super.sessionStorageEvaluator();
}
@Bean
@ConditionalOnMissingBean
protected SubjectFactory subjectFactory() {
return super.subjectFactory();
}
@Bean
@ConditionalOnMissingBean
protected SessionFactory sessionFactory() {
return super.sessionFactory();
}
@Bean
@ConditionalOnMissingBean
protected SessionDAO sessionDAO() {
return super.sessionDAO();
}
@Bean
@ConditionalOnMissingBean
protected SessionManager sessionManager() {
return super.sessionManager();
}
//SessionsSecurityManager
@Bean
@ConditionalOnMissingBean
protected SessionsSecurityManager securityManager(List<Realm> realms) {
return super.securityManager(realms);
}
@Bean
@ConditionalOnResource(
resources = {"classpath:shiro.ini"}
)
protected Realm iniClasspathRealm() {
return this.iniRealmFromLocation("classpath:shiro.ini");
}
@Bean
@ConditionalOnResource(
resources = {"classpath:META-INF/shiro.ini"}
)
//Realm
protected Realm iniMetaInfClasspathRealm() {
return this.iniRealmFromLocation("classpath:META-INF/shiro.ini");
}
@Bean
@ConditionalOnMissingBean({Realm.class})
protected Realm missingRealm() {
throw new NoRealmBeanConfiguredException();
}
}
---------------------------------------
public class ShiroAnnotationProcessorAutoConfiguration extends AbstractShiroAnnotationProcessorConfiguration {
public ShiroAnnotationProcessorAutoConfiguration() {
}
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
return super.defaultAdvisorAutoProxyCreator();
}
@Bean
@ConditionalOnMissingBean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
return super.authorizationAttributeSourceAdvisor(securityManager);
}
}
------------------------------------
ublic class ShiroNoRealmConfiguredFailureAnalyzer extends AbstractFailureAnalyzer<NoRealmBeanConfiguredException> {
public ShiroNoRealmConfiguredFailureAnalyzer() {
}
protected FailureAnalysis analyze(Throwable rootFailure, NoRealmBeanConfiguredException cause) {
return new FailureAnalysis("No bean of type 'org.apache.shiro.realm.Realm' found.", "Please create bean of type 'Realm' or add a shiro.ini in the root classpath (src/main/resources/shiro.ini) or in the META-INF folder (src/main/resources/META-INF/shiro.ini).", cause);
}
}
这里看到大多数属性都进行了自动注入。但是都不是最关键的组件
现在来看看shiro-spring-boot-web-starter
public class ShiroWebAutoConfiguration extends AbstractShiroWebConfiguration {
public ShiroWebAutoConfiguration() {
}
//认证策略
@Bean
@ConditionalOnMissingBean
protected AuthenticationStrategy authenticationStrategy() {
return super.authenticationStrategy();
}
//认证器
@Bean
@ConditionalOnMissingBean
protected Authenticator authenticator() {
return super.authenticator();
}
//授权者,这里注意也是shiro-spring-boot-web-starter的坑点,后面会讲到
@Bean
@ConditionalOnMissingBean
protected Authorizer authorizer() {
return super.authorizer();
}
//DAO
@Bean
@ConditionalOnMissingBean
protected SubjectDAO subjectDAO() {
return super.subjectDAO();
}
//session存储策略
@Bean
@ConditionalOnMissingBean
protected SessionStorageEvaluator sessionStorageEvaluator() {
return super.sessionStorageEvaluator();
}
//主角工厂
@Bean
@ConditionalOnMissingBean
protected SubjectFactory subjectFactory() {
return super.subjectFactory();
}
//session工厂
@Bean
@ConditionalOnMissingBean
protected SessionFactory sessionFactory() {
return super.sessionFactory();
}
//sessionDAO
@Bean
@ConditionalOnMissingBean
protected SessionDAO sessionDAO() {
return super.sessionDAO();
}
//SessionManager
@Bean
@ConditionalOnMissingBean
protected SessionManager sessionManager() {
return super.sessionManager();
}
//SessionsSecurityManager,这里可以看到自动注入是他已经带着参数将Realm注入进去了,非常重要
@Bean
@ConditionalOnMissingBean
protected SessionsSecurityManager securityManager(List<Realm> realms) {
return super.securityManager(realms);
}
//sessionCookieTemplate的cookie
@Bean
@ConditionalOnMissingBean(
name = {"sessionCookieTemplate"}
)
protected Cookie sessionCookieTemplate() {
return super.sessionCookieTemplate();
}
//rememberMeManager
@Bean
@ConditionalOnMissingBean
protected RememberMeManager rememberMeManager() {
return super.rememberMeManager();
}
//rememberMeCookieTemplate的cookie
@Bean
@ConditionalOnMissingBean(
name = {"rememberMeCookieTemplate"}
)
protected Cookie rememberMeCookieTemplate() {
return super.rememberMeCookieTemplate();
}
//shiro过滤链
@Bean
@ConditionalOnMissingBean
protected ShiroFilterChainDefinition shiroFilterChainDefinition() {
return super.shiroFilterChainDefinition();
}
}
=---------------------------------
public class ShiroWebFilterConfiguration extends AbstractShiroWebFilterConfiguration {
public ShiroWebFilterConfiguration() {
}
//shiro过滤工厂类
@Bean
@ConditionalOnMissingBean
protected ShiroFilterFactoryBean shiroFilterFactoryBean() {
return super.shiroFilterFactoryBean();
}
//过滤器注册Bean
@Bean(
name = {"filterShiroFilterRegistrationBean"}
)
@ConditionalOnMissingBean
protected FilterRegistrationBean filterShiroFilterRegistrationBean() throws Exception {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, new DispatcherType[]{DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ERROR});
filterRegistrationBean.setFilter((AbstractShiroFilter)this.shiroFilterFactoryBean().getObject());
filterRegistrationBean.setOrder(1);
return filterRegistrationBean;
}
}
看我源码我们大概了解到了,shiro-spring-boot-starter给我们自动配置了大多数组件,三大组件也注入了IOC容器中,可是security他给我注入的是SessionSecurityManager,并且已经将Realm待参进行构建,这一点很重要,因为我们必须定义自己的认证授权规则,所以必须创建继承 AuthorizingRealm的Realm类,那么为什么不适用@Compant注解将其注入到IOC容器中,而是@Bean将Rleam加入呢,一点在于必须将Rleam注入到SecurityManager中,所以shiro团队也因为进行了改进。所以我们使用是可以使用@Autowire自动注入SessionSecurityManager到ShiroConfig里,而不需要@Bean再往IOC里面注入,如果你要使用其他类型的SecurityManager,可以自己@Bean配置。而其他的如
ShiroFilterFactoryBean 并没有进行配置,所以需要手动配置。
那么现在看看我自己使用的ShiroConfig
@Configuration
public class ShiroConfigurer {
//直接取出已经注入到IOC容器的SessionsSecurityManager
@Autowired
protected SessionsSecurityManager securityManager;
/**
* @return realm实现了Authorizer接口,所以autoconfig不好自动注入
*/
@Bean
protected Authorizer authorizer() {
return new ModularRealmAuthorizer();
}
/**
* 拦截规则
*/
@Bean
protected LinkedHashMap<String, String> chainDefinitionMap() {
return new LinkedHashMap<String, String>(25) {{
//swagger-ui doc放行
this.put("/swagger-ui.html", "anon");
this.put("/swagger-resources/**", "anon");
this.put("/v2/api-docs", "anon");
this.put("/webjars/**", "anon");
this.put("/doc.html", "anon");
this.put("/user/vercode", "anon");//验证码
this.put("/error", "anon");//错误
//静态资源
this.put("/css/**", "anon");
this.put("/fonts/**", "anon");
this.put("/img/**", "anon");
this.put("/static/js/**", "anon");
this.put("/favicon.ico", "anon");
//自定义的过滤器。
this.put("/**/*", "jsonRemember"); //其余的资源都必须认证之后才能访问
}};
}
/**
* 加入自定义的filter
*/
@Bean
protected ShiroFilterFactoryBean shiroFilterFactoryBean(
@Qualifier("userId_loginTime_cache") Cache userId_loginTime_cache,
@Qualifier("chainDefinitionMap") Map<String, String> chainDefinitionMap) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setLoginUrl("/user/login");
//自定义过滤链用于权限验证
factoryBean.getFilters().put("jsonRemember", new JsonRememberFilter(userId_loginTime_cache));
factoryBean.setSecurityManager(securityManager);
factoryBean.setFilterChainDefinitionMap(chainDefinitionMap);
return factoryBean;
}
/**
* shiro-ehcache(授权信息)
*/
@Bean
protected EhCacheManager cacheManager() {
EhCacheManager ehCacheManager = new EhCacheManager();
ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
return ehCacheManager;
}
}
这里可以看到我们只进行了对于ShiroFilterFactoryBean ,chainDefinitionMap进行了配置,ehcacheManager的配置还是因为我们要使用其业务,至于Authorizer可以看这篇博文
。目前我们就已经进行好了基础配置,接下来我就配置
自定义的Realm认证授权规则
@Component
public class VercodeRealm extends AuthorizingRealm implements Serializable {
@Resource
private Cache vercodeCache;
/**
* 查看源码发现,不能自动注入CredentialsMatcher,但是能自己设置CacheManager
*/
public VercodeRealm() {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher("MD5");
matcher.setHashIterations(4);
this.setCredentialsMatcher(matcher);
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
VercodeToken token = (VercodeToken) authenticationToken;
Element element = vercodeCache.get(SecurityUtils.getSubject().getSession().getId());
{
if (element == null)
throw new VercodeExpiryException();//验证码过期
if (!token.getVerCode().equalsIgnoreCase((String) element.getObjectValue()))
throw new VercodeMismatchedException();//验证码不匹配
}
{
String scNoOrPhone = ((String) token.getPrincipal());
//查询顺序,学号-电话
Wrapper<User> wrapper;
if (scNoOrPhone.startsWith("20")) { //201721456
wrapper = Wrappers.<User>lambdaQuery().eq(User::getScNo, scNoOrPhone);
} else if (scNoOrPhone.startsWith("1")) {//13965971234
wrapper = Wrappers.<User>lambdaQuery().eq(User::getPhone, scNoOrPhone);
} else {
throw new UnknownAccountException();//无此用户名
}
User user = User.sqlInstance().selectOne(wrapper);
//账户被锁
// if (principal.isLocked()) {
// throw new LockedAccountException();//账户被锁
// }
String hashedPassword = user.getPassword();
ByteSource salt = ByteSource.Util.bytes(user.getSalt());
return new SimpleAuthenticationInfo(user, hashedPassword, salt, this.getName());
}
}
/**
* @param principals 授权
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorInfo = new SimpleAuthorizationInfo();
User user = ((User) principals.getPrimaryPrincipal());
if (user.getIsAdmin())
authorInfo.setRoles(Collections.singleton("admin"));
else
authorInfo.setRoles(Collections.singleton("user"));
return authorInfo;
}
}
如果你不是前后端分离的业务,也对系统效率也没要求,已经可以使用这一套配置进行开发了。
可以shiro-spring-boot-web-starter并没对前后端分离进行支持,它的认证过滤器会在进行认证后进行跳转,这不是我们想要的,前后端分离后,前端接管了跳转,而后端只负责处理数据,返回数据,返回状态等,所以我们必须改造它的过滤链。
Shiro内置的FilterChain
anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.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);
我们知道shiro执行过滤器的顺序是
- 加载 DefaultFilter 中的默认 Filter;
- 加载自定义 Filter;
- 加载 FFilterChainDefinitionMap;
弄清楚了这 Filter 的加载与注册,那这与我们要解决的问题有何关系呢?首先我们怀疑这里获取的 Filter 是异常的,调试打个断点看看。
所以我会按照FormAuthenticationFilter的基础进行改造,看了FormAuthenticationFilter的源码,它继承AuthenticatingFilter,所以我们也相应的继承AuthenticatingFilter
接下来是我的Filter
public class JsonRememberFilter extends AuthenticatingFilter {
private final String COOKIE_NAME = "LOGIN_TIME";
private final Cache userId_loginTime_cache;
public JsonRememberFilter(Cache userId_loginTime_cache) {
this.userId_loginTime_cache = userId_loginTime_cache;
}
/**
* 根据jsonText生成token
*/
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws IOException {
//一个流最多只能用一次getReader()
UserLoginDto user = new ObjectMapper().readValue(request.getReader(), UserLoginDto.class);
return new VercodeToken(user.getAccount(), user.getPassword(),
user.isRememberMe(), request.getRemoteHost(), user.getVercode());
}
/**
* 是登陆url,并且为post请求判断为登陆请求
*/
@Override
protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {
return "POST".equals(((HttpServletRequest) request).getMethod()) && pathsMatch(getLoginUrl(), request);
}
/**
* 保证一个账号只能有一次登陆,如果cookie里面的登录时间和缓存里的登录时间不同,清除cookie,没有cookie,那么就不能生成 principal
*/
@SneakyThrows
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
Subject subject = SecurityUtils.getSubject();
User presentPrincipal = (User) subject.getPrincipal();
if (presentPrincipal == null || isLoginRequest(request, response)) { //放行重复的登录请求
return false;
} else {
Element element = userId_loginTime_cache.get(presentPrincipal.getId());
if (element == null) //缓存中该用户的登录态已经被剔除
return false;
//根据cookie的登录时间判断是否为同一个人登录,若不存在该cookie,不存在登出,进入onAccessDenied()
Cookie[] cookies = ((HttpServletRequest) request).getCookies();
for (Cookie cookie : cookies) {
Object loginTimeInCache = element.getObjectValue();
String cookieName = cookie.getName();
String cookieVal = cookie.getValue();
boolean isValidated = COOKIE_NAME.equals(cookieName) && cookieVal.equals(loginTimeInCache);
if (isValidated) return true; //相等表示就是当前用户,跳出循环
}
return false; //进入onAccessDenied()
}
}
/**
* 登陆请求:登陆
* 非登陆请求: 有principal放行(记住我)
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse rsp = (HttpServletResponse) response;
String remoteAddr = req.getRemoteAddr();
String requestURL = req.getRequestURL().toString();
//是登陆请求 并且是Post方法 (isLoginSubmission实现post方法的判断)
if (isLoginRequest(request, response)) {
log.debug("检测到登录请求,ip = {},正在登陆", remoteAddr);
return executeLogin(request, response);
} else {
//写出401
rsp.setStatus(401);
log.warn("检测到非法 ip = {} ,url = {}", remoteAddr, requestURL);
return false;
}
}
/**
* 登陆成功返回200
*
* @return 是否放行到过滤器链
*/
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
User user = ((User) subject.getPrincipal());
//设置登录时间的cookie
String cookieVal = Long.toString(System.currentTimeMillis());
String cookieName = COOKIE_NAME;
Cookie cookie = new Cookie(cookieName, cookieVal);
cookie.setMaxAge(3600 * 24 * 30); //30天
cookie.setHttpOnly(true); //安全性
cookie.setPath("/");
//写入cookie
((HttpServletResponse) response).addCookie(cookie);
//写入缓存
userId_loginTime_cache.put(new Element(user.getId(), cookieVal));
log.debug("ip = {} , userId = {} , name = {} 登陆成功", request.getRemoteAddr(), user.getId(), user.getName());
//直接写出 1 ,不放行请求
response.setContentType("application/json;charset=utf-8");
new ObjectMapper().writeValue(response.getWriter(), RespBody.success());
return false;
}
/**
* 登陆失败根据 AuthenticationException的类型,返回不同的message
*
* @return 是否放行到过滤器链
*/
@SneakyThrows
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
log.debug("ip = {} 登陆失败", request.getRemoteAddr());
SecurityUtils.getSubject().logout();
String message;
if (e instanceof VercodeMismatchedException)
message = "验证码错误,请重新输入";
else if (e instanceof VercodeExpiryException)
message = "验证码过期,请重新获取";
else if (e instanceof UnknownAccountException)
message = "账号不存在";
else if (e instanceof IncorrectCredentialsException)
message = "密码错误";
else if (e instanceof LockedAccountException)
message = "账户被锁";
else
message = "请重新登陆";
response.setContentType("application/json;charset=utf-8");
new ObjectMapper().writeValue(response.getWriter(), RespBody.error(null).setMessage(message));
return false;
}
值得注意的是,如果将shiro的filter放入spring ioc,shiro将该filter视为spring的filter不会注入path*/
具体参考这篇博文
http://www.hillfly.com/2017/179.html
问题解决
眼下我暂时有两种办法去解决这个问题:
- 修改 AccessTokenFilter,在 Filter 内部加入 path match 方法对需要验证 token 的路径进行过滤。
- 将咱们的自定义 Filter 注册到 Shiro,不注册到 ApplicationFilterChain。
显然方案一是不可取的,这样修改范围过大,得不偿失了。那我们怎么去实现第二个方法呢?SpringBoot 提供了 FilterRegistrationBean 方便我们对 Filter 进行管理。
@Bean
public FilterRegistrationBean registration(AccessTokenFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
将不需要注册的 Filter 注入方法即可。这时候再启动项目进行测试,就可以发现 filters 已经不存在咱们的自定义 Filter 了。
还有个办法不需要使用到 FilterRegistrationBean,因为我们将 AccessTokenFilter 注册为了 Bean 交给 Spring 托管了,所以它会被自动注册到 FilterChain 中,那我们如果不把它注册为 Bean 就可以避免这个问题了。
/**
* 不需要显示注册Bean了
@Bean
public AccessTokenFilter accessTokenFilter(){}
**/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager,
IUrlFilterService urlFilterService) {
//省略
filterMap.put("hasToken", new AccessTokenFilter());
//省略
}
原文:使用shiro-starter在前后端分离的Springboot项目_Jaymeng8848的博客-CSDN博客_shiro starter
作者: Jaymeng8848