本文档只是针对Ruoyi前后端不分离项目与cas的集成,默认cas服务器已经安装部署好。好了,话不多说,直接开干。
** 小提示:
Shiro使用Pac4j集成CAS认证,原来shiro自带的shiro-cas已不推荐使用,在shiro-1.3.x以后的版本中,shiro-cas包里面的所有类都被标识为deprecated
1.3.x版本后。shiro接入pac4j,pac4j是一个支持多种支持多种协议的身份认证的Java客户端
org.pac4j
pac4j-cas
3.0.2
io.buji
buji-pac4j
4.0.0
shiro-web
org.apache.shiro
package com.ruoyi.framework.config;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.Filter;
import com.ruoyi.framework.shiro.realm.CasRealm;
import io.buji.pac4j.filter.CallbackFilter;
import io.buji.pac4j.filter.SecurityFilter;
import io.buji.pac4j.subject.Pac4jSubjectFactory;
import org.apache.commons.io.IOUtils;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.config.ConfigurationException;
import org.apache.shiro.io.ResourceUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.pac4j.core.config.Config;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.shiro.realm.UserRealm;
import com.ruoyi.framework.shiro.session.OnlineSessionDAO;
import com.ruoyi.framework.shiro.session.OnlineSessionFactory;
import io.buji.pac4j.filter.LogoutFilter;
//import com.ruoyi.framework.shiro.web.filter.LogoutFilter;
import com.ruoyi.framework.shiro.web.filter.captcha.CaptchaValidateFilter;
import com.ruoyi.framework.shiro.web.filter.kickout.KickoutSessionFilter;
import com.ruoyi.framework.shiro.web.filter.online.OnlineSessionFilter;
import com.ruoyi.framework.shiro.web.filter.sync.SyncOnlineSessionFilter;
import com.ruoyi.framework.shiro.web.session.OnlineWebSessionManager;
import com.ruoyi.framework.shiro.web.session.SpringSessionValidationScheduler;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
/**
* 权限配置加载
*
* @author ruoyi
*/
@Configuration
public class ShiroConfig
{
public static final String PREMISSION_STRING = "perms[\"{0}\"]";
/**
* Session超时时间,单位为毫秒(默认30分钟)
*/
@Value("${shiro.session.expireTime}")
private int expireTime;
/**
* 相隔多久检查一次session的有效性,单位毫秒,默认就是10分钟
*/
@Value("${shiro.session.validationInterval}")
private int validationInterval;
/**
* 同一个用户最大会话数
*/
@Value("${shiro.session.maxSession}")
private int maxSession;
/**
* 踢出之前登录的/之后登录的用户,默认踢出之前登录的用户
*/
@Value("${shiro.session.kickoutAfter}")
private boolean kickoutAfter;
/**
* 验证码开关
*/
@Value("${shiro.user.captchaEnabled}")
private boolean captchaEnabled;
/**
* 验证码类型
*/
@Value("${shiro.user.captchaType}")
private String captchaType;
/**
* 设置Cookie的域名
*/
@Value("${shiro.cookie.domain}")
private String domain;
/**
* 设置cookie的有效访问路径
*/
@Value("${shiro.cookie.path}")
private String path;
/**
* 设置HttpOnly属性
*/
@Value("${shiro.cookie.httpOnly}")
private boolean httpOnly;
/**
* 设置Cookie的过期时间,秒为单位
*/
@Value("${shiro.cookie.maxAge}")
private int maxAge;
/**
* 登录地址
*/
@Value("${shiro.user.loginUrl}")
private String loginUrl;
/**
* 权限认证失败地址
*/
@Value("${shiro.user.unauthorizedUrl}")
private String unauthorizedUrl;
/* liupsh cas begin*/
/** 项目工程路径 */
@Value("${cas.project.url}")
private String projectUrl;
/** 项目cas服务路径 */
@Value("${cas.server.url}")
private String casServerUrl;
/** 客户端名称 */
@Value("${cas.client-name}")
private String clientName;
/* liupsh cas end*/
/**
* 缓存管理器 使用Ehcache实现
*/
@Bean
public EhCacheManager getEhCacheManager()
{
net.sf.ehcache.CacheManager cacheManager = net.sf.ehcache.CacheManager.getCacheManager("ruoyi");
EhCacheManager em = new EhCacheManager();
if (StringUtils.isNull(cacheManager))
{
em.setCacheManager(new net.sf.ehcache.CacheManager(getCacheManagerConfigFileInputStream()));
return em;
}
else
{
em.setCacheManager(cacheManager);
return em;
}
}
/**
* 返回配置文件流 避免ehcache配置文件一直被占用,无法完全销毁项目重新部署
*/
protected InputStream getCacheManagerConfigFileInputStream()
{
String configFile = "classpath:ehcache/ehcache-shiro.xml";
InputStream inputStream = null;
try
{
inputStream = ResourceUtils.getInputStreamForPath(configFile);
byte[] b = IOUtils.toByteArray(inputStream);
InputStream in = new ByteArrayInputStream(b);
return in;
}
catch (IOException e)
{
throw new ConfigurationException(
"Unable to obtain input stream for cacheManagerConfigFile [" + configFile + "]", e);
}
finally
{
IOUtils.closeQuietly(inputStream);
}
}
/**
* 自定义Realm
*/
@Bean
public UserRealm userRealm(EhCacheManager cacheManager)
{
UserRealm userRealm = new UserRealm();
userRealm.setCacheManager(cacheManager);
return userRealm;
}
/**
* 自定义sessionDAO会话
*/
@Bean
public OnlineSessionDAO sessionDAO()
{
OnlineSessionDAO sessionDAO = new OnlineSessionDAO();
return sessionDAO;
}
/**
* 自定义sessionFactory会话
*/
@Bean
public OnlineSessionFactory sessionFactory()
{
OnlineSessionFactory sessionFactory = new OnlineSessionFactory();
return sessionFactory;
}
/**
* 会话管理器
*/
@Bean
public OnlineWebSessionManager sessionManager()
{
OnlineWebSessionManager manager = new OnlineWebSessionManager();
// 加入缓存管理器
manager.setCacheManager(getEhCacheManager());
// 删除过期的session
manager.setDeleteInvalidSessions(true);
// 设置全局session超时时间
manager.setGlobalSessionTimeout(expireTime * 60 * 1000);
// 去掉 JSESSIONID
manager.setSessionIdUrlRewritingEnabled(false);
// 定义要使用的无效的Session定时调度器
manager.setSessionValidationScheduler(SpringUtils.getBean(SpringSessionValidationScheduler.class));
// 是否定时检查session
manager.setSessionValidationSchedulerEnabled(true);
// 自定义SessionDao
manager.setSessionDAO(sessionDAO());
// 自定义sessionFactory
manager.setSessionFactory(sessionFactory());
return manager;
}
/**
* 安全管理器
*/
@Bean
public SecurityManager securityManager(CasRealm casRealm)
{
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(casRealm);
// 记住我
securityManager.setRememberMeManager(rememberMeManager());
// 注入缓存管理器;
securityManager.setCacheManager(getEhCacheManager());
// session管理器
securityManager.setSessionManager(sessionManager());
securityManager.setSubjectFactory(subjectFactory());
return securityManager;
}
/**
* 使用 pac4j 的 subjectFactory
* @return
*/
@Bean
public Pac4jSubjectFactory subjectFactory(){
return new Pac4jSubjectFactory();
}
@Bean
public CasRealm casRealm(){
CasRealm realm = new CasRealm();
// 使用自定义的realm
realm.setClientName(clientName);
realm.setCachingEnabled(false);
//暂时不使用缓存
realm.setAuthenticationCachingEnabled(false);
realm.setAuthorizationCachingEnabled(false);
//realm.setAuthenticationCacheName("authenticationCache");
//realm.setAuthorizationCacheName("authorizationCache");
return realm;
}
/**
* 加载shiroFilter权限控制规则(从数据库读取然后配置)
* @param shiroFilterFactoryBean
*/
private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean){
/*下面这些规则配置最好配置到配置文件中 */
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 对静态资源设置匿名访问
filterChainDefinitionMap.put("/favicon.ico**", "anon");
filterChainDefinitionMap.put("/ruoyi.png**", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/docs/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/ajax/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/ruoyi/**", "anon");
filterChainDefinitionMap.put("/druid/**", "anon");
filterChainDefinitionMap.put("/captcha/captchaImage**", "anon");
//内部接口调用
filterChainDefinitionMap.put("/hiapi/**", "anon");
filterChainDefinitionMap.put("/", "securityFilter");
filterChainDefinitionMap.put("/application/**", "securityFilter");
filterChainDefinitionMap.put("/index", "securityFilter");
filterChainDefinitionMap.put("/callback", "callbackFilter");
filterChainDefinitionMap.put("/logout", "logout");
// filterChainDefinitionMap.put("/**","anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
}
/**
* Shiro过滤器配置
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager , Config config)
{
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// Shiro的核心安全接口,这个属性是必须的
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 添加casFilter到shiroFilter中
loadShiroFilterChain(shiroFilterFactoryBean);
Map<String, Filter> filters = new HashMap<>(3);
//cas 资源认证拦截器
SecurityFilter securityFilter = new SecurityFilter();
securityFilter.setConfig(config);
securityFilter.setClients(clientName);
filters.put("securityFilter", securityFilter);
//cas 认证后回调拦截器
CallbackFilter callbackFilter = new CallbackFilter();
callbackFilter.setConfig(config);
callbackFilter.setDefaultUrl(projectUrl);
filters.put("callbackFilter", callbackFilter);
// 注销 拦截器
LogoutFilter logoutFilter = new LogoutFilter();
logoutFilter.setConfig(config);
logoutFilter.setCentralLogout(true);
logoutFilter.setLocalLogout(true);
logoutFilter.setDefaultUrl(projectUrl + "/callback?client_name=" + clientName);
filters.put("logout",logoutFilter);
shiroFilterFactoryBean.setFilters(filters);
return shiroFilterFactoryBean;
}
/**
* 自定义在线用户处理过滤器
*/
@Bean
public OnlineSessionFilter onlineSessionFilter()
{
OnlineSessionFilter onlineSessionFilter = new OnlineSessionFilter();
onlineSessionFilter.setLoginUrl(loginUrl);
return onlineSessionFilter;
}
/**
* 自定义在线用户同步过滤器
*/
@Bean
public SyncOnlineSessionFilter syncOnlineSessionFilter()
{
SyncOnlineSessionFilter syncOnlineSessionFilter = new SyncOnlineSessionFilter();
return syncOnlineSessionFilter;
}
/**
* 自定义验证码过滤器
*/
@Bean
public CaptchaValidateFilter captchaValidateFilter()
{
CaptchaValidateFilter captchaValidateFilter = new CaptchaValidateFilter();
captchaValidateFilter.setCaptchaEnabled(captchaEnabled);
captchaValidateFilter.setCaptchaType(captchaType);
return captchaValidateFilter;
}
/**
* cookie 属性设置
*/
public SimpleCookie rememberMeCookie()
{
SimpleCookie cookie = new SimpleCookie("rememberMe");
cookie.setDomain(domain);
cookie.setPath(path);
cookie.setHttpOnly(httpOnly);
cookie.setMaxAge(maxAge * 24 * 60 * 60);
return cookie;
}
/**
* 记住我
*/
public CookieRememberMeManager rememberMeManager()
{
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
cookieRememberMeManager.setCipherKey(Base64.decode("fCq+/xW488hMTCD+cmJ3aQ=="));
return cookieRememberMeManager;
}
/**
* 同一个用户多设备登录限制
*/
public KickoutSessionFilter kickoutSessionFilter()
{
KickoutSessionFilter kickoutSessionFilter = new KickoutSessionFilter();
kickoutSessionFilter.setCacheManager(getEhCacheManager());
kickoutSessionFilter.setSessionManager(sessionManager());
// 同一个用户最大的会话数,默认-1无限制;比如2的意思是同一个用户允许最多同时两个人登录
kickoutSessionFilter.setMaxSession(maxSession);
// 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;踢出顺序
kickoutSessionFilter.setKickoutAfter(kickoutAfter);
// 被踢出后重定向到的地址;
kickoutSessionFilter.setKickoutUrl("/login?kickout=1");
return kickoutSessionFilter;
}
/**
* thymeleaf模板引擎和shiro框架的整合
*/
@Bean
public ShiroDialect shiroDialect()
{
return new ShiroDialect();
}
/**
* 开启Shiro注解通知器
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
@Qualifier("securityManager") SecurityManager securityManager)
{
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
package com.ruoyi.framework.shiro.realm;
import com.ruoyi.common.exception.user.*;
import com.ruoyi.framework.shiro.service.SysLoginService;
import com.ruoyi.framework.util.ShiroUtils;
import com.ruoyi.system.domain.SysUser;
import com.ruoyi.system.service.ISysMenuService;
import com.ruoyi.system.service.ISysRoleService;
import io.buji.pac4j.realm.Pac4jRealm;
import io.buji.pac4j.subject.Pac4jPrincipal;
import io.buji.pac4j.token.Pac4jToken;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.pac4j.core.profile.CommonProfile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 认证与授权
**/
public class CasRealm extends Pac4jRealm {
private final static Logger log = LoggerFactory.getLogger(CasRealm.class);
private String clientName;
@Autowired
private ISysMenuService menuService;
@Autowired
private ISysRoleService roleService;
@Autowired
private SysLoginService loginService;
public String getClientName() {
return clientName;
}
public void setClientName(String clientName) {
this.clientName = clientName;
}
public ISysMenuService getMenuService() {
return menuService;
}
public void setMenuService(ISysMenuService menuService) {
this.menuService = menuService;
}
public ISysRoleService getRoleService() {
return roleService;
}
public void setRoleService(ISysRoleService roleService) {
this.roleService = roleService;
}
public SysLoginService getLoginService() {
return loginService;
}
public void setLoginService(SysLoginService loginService) {
this.loginService = loginService;
}
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
final Pac4jToken pac4jToken = (Pac4jToken) authenticationToken;
final List<CommonProfile> commonProfileList = pac4jToken.getProfiles();
final CommonProfile commonProfile = commonProfileList.get(0);
//todo
final Pac4jPrincipal principal = new Pac4jPrincipal(commonProfileList, getPrincipalNameAttribute());
final PrincipalCollection principalCollection = new SimplePrincipalCollection(principal, getName());
String username = commonProfile.getId();
SysUser user = null;
try
{
user = loginService.casLogin(username);
}
catch (CaptchaException e)
{
throw new AuthenticationException(e.getMessage(), e);
}
catch (UserNotExistsException e)
{
throw new UnknownAccountException(e.getMessage(), e);
}
catch (UserPasswordNotMatchException e)
{
throw new IncorrectCredentialsException(e.getMessage(), e);
}
catch (UserPasswordRetryLimitExceedException e)
{
throw new ExcessiveAttemptsException(e.getMessage(), e);
}
catch (UserBlockedException e)
{
throw new LockedAccountException(e.getMessage(), e);
}
catch (RoleBlockedException e)
{
throw new LockedAccountException(e.getMessage(), e);
}
catch (Exception e)
{
log.info("对用户[" + username + "]进行登录验证..验证未通过{}", e.getMessage());
throw new AuthenticationException(e.getMessage(), e);
}
return new SimpleAuthenticationInfo(user, commonProfileList.hashCode(), getName());
}
/**
* 授权/验权(todo 后续有权限在此增加)
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
SysUser user = ShiroUtils.getSysUser();
// 角色列表
Set<String> roles = new HashSet<String>();
// 功能列表
Set<String> menus = new HashSet<String>();
// 管理员拥有所有权限
if (user.isAdmin())
{
info.addRole("admin");
info.addStringPermission("*:*:*");
}
else
{
roles = roleService.selectRoleKeys(user.getUserId());
menus = menuService.selectPermsByUserId(user.getUserId());
// 角色加入AuthorizationInfo认证对象
info.setRoles(roles);
// 权限加入AuthorizationInfo认证对象
info.setStringPermissions(menus);
}
return info;
}
public static void main(String[] args) {
encryptPassword();
}
public static void encryptPassword(){
System.out.println(new Md5Hash("admin"+"admin123"+"111111").toHex());
}
}
package com.ruoyi.framework.config;
import com.ruoyi.framework.shiro.client.Pac4jCasClient;
import io.buji.pac4j.context.ShiroSessionStore;
import org.pac4j.cas.config.CasConfiguration;
import org.pac4j.cas.config.CasProtocol;
import org.pac4j.core.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Pac4jConfig {
/** 地址为:cas地址 */
@Value("${cas.server.url}")
private String casServerUrl;
/** 地址为:验证返回后的项目地址:http://localhost:8081 */
@Value("${cas.project.url}")
private String projectUrl;
/** 相当于一个标志,可以随意 */
@Value("${cas.client-name}")
private String clientName;
/**
* pac4j配置
* @param casClient
* @param shiroSessionStore
* @return
*/
@Bean("authcConfig")
public Config config(Pac4jCasClient casClient, ShiroSessionStore shiroSessionStore) {
Config config = new Config(casClient);
config.setSessionStore(shiroSessionStore);
return config;
}
/**
* 自定义存储
* @return
*/
@Bean
public ShiroSessionStore shiroSessionStore(){
return new ShiroSessionStore();
}
/**
* cas 客户端配置
* @param casConfig
* @return
*/
@Bean
public Pac4jCasClient casClient(CasConfiguration casConfig){
Pac4jCasClient casClient = new Pac4jCasClient(casConfig);
//客户端回调地址
casClient.setCallbackUrl(projectUrl + "/callback?client_name=" + clientName);
casClient.setName(clientName);
return casClient;
}
/**
* 请求cas服务端配置
*/
@Bean
public CasConfiguration casConfig(){
final CasConfiguration configuration = new CasConfiguration();
//CAS server登录地址
configuration.setLoginUrl(casServerUrl + "/login");
//CAS 版本,默认为 CAS30,我们使用的是 CAS20
configuration.setProtocol(CasProtocol.CAS20);
configuration.setAcceptAnyProxy(true);
configuration.setPrefixUrl(casServerUrl + "/");
return configuration;
}
}
package com.ruoyi.framework.shiro.client;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.pac4j.cas.config.CasConfiguration;
import org.pac4j.core.context.Pac4jConstants;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.context.session.SessionStore;
import org.pac4j.core.redirect.RedirectAction;
import org.pac4j.core.util.CommonHelper;
import org.pac4j.cas.client.CasClient;
public class Pac4jCasClient extends CasClient {
public Pac4jCasClient() {
super();
}
public Pac4jCasClient(CasConfiguration configuration) {
super(configuration);
}
/*
* (non-Javadoc)
* @see org.pac4j.core.client.IndirectClient#getRedirectAction(org.pac4j.core.context.WebContext)
*/
@Override
public RedirectAction getRedirectAction(WebContext context) {
this.init();
if (getAjaxRequestResolver().isAjax(context)) {
this.logger.info("AJAX request detected -> returning the appropriate action");
RedirectAction action = getRedirectActionBuilder().redirect(context);
this.cleanRequestedUrl(context);
return getAjaxRequestResolver().buildAjaxResponse(action.getLocation(), context);
} else {
final String attemptedAuth = (String)context.getSessionStore().get(context, this.getName() + ATTEMPTED_AUTHENTICATION_SUFFIX);
if (CommonHelper.isNotBlank(attemptedAuth)) {
this.cleanAttemptedAuthentication(context);
this.cleanRequestedUrl(context);
//这里按自己需求处理,默认是返回了401,我在这边改为跳转到cas登录页面
return this.getRedirectActionBuilder().redirect(context);
} else {
return this.getRedirectActionBuilder().redirect(context);
}
}
}
private void cleanRequestedUrl(WebContext context) {
SessionStore<WebContext> sessionStore = context.getSessionStore();
if (sessionStore.get(context, Pac4jConstants.REQUESTED_URL) != null) {
sessionStore.set(context, Pac4jConstants.REQUESTED_URL, "");
}
}
private void cleanAttemptedAuthentication(WebContext context) {
SessionStore<WebContext> sessionStore = context.getSessionStore();
if (sessionStore.get(context, this.getName() + ATTEMPTED_AUTHENTICATION_SUFFIX) != null) {
sessionStore.set(context, this.getName() + ATTEMPTED_AUTHENTICATION_SUFFIX, "");
}
}
}
/**
* cas登录
*/
public SysUser casLogin(String username)
{
// 查询用户信息
SysUser user = userService.selectUserByLoginName(username);
// AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
recordLoginInfo(user);
return user;
}
10:10:11.536 [restartedMain] INFO c.a.d.p.DruidDataSource - [close,2024] - {dataSource-0} closing ...
10:10:11.556 [restartedMain] ERROR o.s.b.d.LoggingFailureAnalysisReporter - [report,42] -
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
objectMapperConfigurer defined in class path resource [springfox/documentation/spring/web/SpringfoxWebMvcConfiguration.class]
↓
authorizationAttributeSourceAdvisor defined in class path resource [com/ruoyi/framework/config/ShiroConfig.class]
↓
shiroFilterFactoryBean defined in class path resource [com/ruoyi/framework/config/ShiroConfig.class]
↓
securityManager defined in class path resource [com/ruoyi/framework/config/ShiroConfig.class]
┌─────┐
| sessionManager defined in class path resource [com/ruoyi/framework/config/ShiroConfig.class]
↑ ↓
| springSessionValidationScheduler (field private org.apache.shiro.session.mgt.ValidatingSessionManager com.ruoyi.framework.shiro.web.session.SpringSessionValidationScheduler.sessionManager)
└─────┘
Disconnected from the target VM, address: '127.0.0.1:56974', transport: 'socket'
Process finished with exit code 0
发现直接BBQ了,翻车了,看一下报错,发现是循环依赖问题。
解决办法如下:
找到SpringSessionValidationScheduler类,找到成员属性private ValidatingSessionManager sessionManager;在他上面加上 @Lazy 注解即可。
@Component
public class SpringSessionValidationScheduler implements SessionValidationScheduler
{
private static final Logger log = LoggerFactory.getLogger(SpringSessionValidationScheduler.class);
public static final long DEFAULT_SESSION_VALIDATION_INTERVAL = DefaultSessionManager.DEFAULT_SESSION_VALIDATION_INTERVAL;
/**
* 定时器,用于处理超时的挂起请求,也用于连接断开时的重连。
*/
@Autowired
@Qualifier("scheduledExecutorService")
private ScheduledExecutorService executorService;
private volatile boolean enabled = false;
/**
* 会话验证管理器
*/
@Autowired
@Qualifier("sessionManager")
@Lazy
private ValidatingSessionManager sessionManager;