0


RuoYi-Vue前后端分离版集成Cas单点登录

后端

1.添加CAS依赖

  • 在common模块pom添加spring-security-cas依赖:
<!-- spring security cas--><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-cas</artifactId></dependency>

2.修改配置文件

  • 在admin模块下的application.yml配置文件中添加:
# CAS 相关配置 start# CAS服务器配置cas:server:host:#CAS服务地址url: http://host:port/sso
      #CAS ticket 验证 服务地址ticket_validator_url: http://host:port/sso
      #CAS服务登录地址login_url: ${cas.server.host.url}/login
      #CAS服务登出地址logout_url: ${cas.server.host.url}/logout?service=${cas.server.host.url}/login?service=${app.server.host.url}#应用访问地址app:#项目名称name: Xxx
  #是否开启CAScasEnable:trueserver:host:#项目地址url: http://host:${server.port}#应用登录地址login_url: /
  #应用登出地址logout_url: /logout
  #前端回调地址callback_url: /cas/index
  #前端登录地址web_url: http://host:port/xxx_vue
# CAS 相关配置 end

3.修改LoginUser.java

  • 由于CAS认证需要authorities属性,此属性不能为空,此处为了方便直接new HashSet():
packagecom.ruoyi.common.core.domain.model;importjava.util.Collection;importjava.util.HashSet;importjava.util.Map;importjava.util.Set;importlombok.Data;importorg.springframework.security.core.GrantedAuthority;importorg.springframework.security.core.userdetails.UserDetails;importcom.alibaba.fastjson2.annotation.JSONField;importcom.ruoyi.common.core.domain.entity.SysUser;/**
 * 登录用户身份权限
 * 
 * @author ruoyi
 */@DatapublicclassLoginUserimplementsUserDetails{privatestaticfinallong serialVersionUID =1L;/**
     * 用户ID
     */privateLong userId;/**
     * 部门ID
     */privateLong deptId;/**
     * 用户唯一标识
     */privateString token;/**
     * 登录时间
     */privateLong loginTime;/**
     * 过期时间
     */privateLong expireTime;/**
     * 登录IP地址
     */privateString ipaddr;/**
     * 登录地点
     */privateString loginLocation;/**
     * 浏览器类型
     */privateString browser;/**
     * 操作系统
     */privateString os;/**
     * 权限列表
     */privateSet<String> permissions;/**
     * 用户信息
     */privateSysUser user;// CAS用户信息privateMap<String,Object> attributes;publicLoginUser(){}publicLoginUser(SysUser user,Set<String> permissions){this.user = user;this.permissions = permissions;}publicLoginUser(Long userId,Long deptId,SysUser user,Set<String> permissions){this.userId = userId;this.deptId = deptId;this.user = user;this.permissions = permissions;}publicLoginUser(Long userId,Long deptId,SysUser user,Set<String> permissions,Map<String,Object> attributes){this.userId = userId;this.deptId = deptId;this.user = user;this.permissions = permissions;this.attributes = attributes;}@JSONField(serialize =false)@OverridepublicStringgetPassword(){return user.getPassword();}@OverridepublicStringgetUsername(){return user.getUserName();}/**
     * 账户是否未过期,过期无法验证
     */@JSONField(serialize =false)@OverridepublicbooleanisAccountNonExpired(){returntrue;}/**
     * 指定用户是否解锁,锁定的用户无法进行身份验证
     * 
     * @return
     */@JSONField(serialize =false)@OverridepublicbooleanisAccountNonLocked(){returntrue;}/**
     * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
     * 
     * @return
     */@JSONField(serialize =false)@OverridepublicbooleanisCredentialsNonExpired(){returntrue;}/**
     * 是否可用 ,禁用的用户不能身份验证
     * 
     * @return
     */@JSONField(serialize =false)@OverridepublicbooleanisEnabled(){returntrue;}@OverridepublicCollection<?extendsGrantedAuthority>getAuthorities(){returnnewHashSet<>();}}

4.修改 Constants.java

  • 添加CAS认证成功标识:
// CAS登录成功后的后台标识publicstaticfinalStringCAS_TOKEN="cas_token";// CAS登录成功后的前台Cookie的KeypublicstaticfinalStringWEB_TOKEN_KEY="Admin-Token";

5.添加 CasProperties.java

  • 在framework模块下添加读取cas配置信息:
packagecom.ruoyi.framework.config.properties;importlombok.Data;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.stereotype.Component;/**
 * @author Wen先森
 * @description CAS的配置参数
 * @date 2024/7/26 16:58
 */@Data@ComponentpublicclassCasProperties{@Value("${cas.server.host.url}")privateString casServerUrl;@Value("${cas.server.host.ticket_validator_url}")privateString casServerTicketValidatorUrl;@Value("${cas.server.host.login_url}")privateString casServerLoginUrl;@Value("${cas.server.host.logout_url}")privateString casServerLogoutUrl;@Value("${app.casEnable}")privateboolean casEnable;@Value("${app.server.host.url}")privateString appServerUrl;@Value("${app.login_url}")privateString appLoginUrl;@Value("${app.logout_url}")privateString appLogoutUrl;@Value("${app.callback_url}")privateString callbackUrl;@Value("${app.web_url}")privateString webUrl;}

6.添加 UserDetailsServiceCasImpl

  • 在framework模块下添加:
packagecom.ruoyi.framework.web.service.impl;importcom.ruoyi.common.core.domain.entity.SysUser;importcom.ruoyi.common.core.domain.model.LoginUser;importcom.ruoyi.common.enums.UserStatus;importcom.ruoyi.common.exception.ServiceException;importcom.ruoyi.common.utils.StringUtils;importcom.ruoyi.framework.web.service.SysPermissionService;importcom.ruoyi.system.service.ISysUserService;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.security.cas.authentication.CasAssertionAuthenticationToken;importorg.springframework.security.core.userdetails.AuthenticationUserDetailsService;importorg.springframework.security.core.userdetails.UserDetails;importorg.springframework.security.core.userdetails.UsernameNotFoundException;importorg.springframework.stereotype.Service;importjava.util.Map;/**
 * @author Wen先森
 * @description 用于加载用户信息,实现UserDetailsService接口,或者实现AuthenticationUserDetailsService接口。
 * @date 2024/7/26 17:02
 */@ServicepublicclassUserDetailsServiceCasImplimplementsAuthenticationUserDetailsService<CasAssertionAuthenticationToken>{privatestaticfinalLogger log =LoggerFactory.getLogger(UserDetailsServiceCasImpl.class);privatefinalISysUserService userService;privatefinalSysPermissionService permissionService;publicUserDetailsServiceCasImpl(ISysUserService userService,SysPermissionService permissionService){this.userService = userService;this.permissionService = permissionService;}@OverridepublicUserDetailsloadUserDetails(CasAssertionAuthenticationToken token)throwsUsernameNotFoundException{// 获取用户名String username = token.getName();// 通过用户名查询用户SysUser user = userService.selectUserByUserName(username);if(StringUtils.isNull(user)){
            log.info("登录用户:{} 不存在。", username);thrownewServiceException("登录用户:"+ username +" 不存在。");}elseif(UserStatus.DELETED.getCode().equals(user.getDelFlag())){
            log.info("登录用户:{} 已被删除。", username);thrownewServiceException("对不起,您的账号:"+ username +" 已被删除。");}elseif(UserStatus.DISABLE.getCode().equals(user.getStatus())){
            log.info("登录用户:{} 已被停用。", username);thrownewServiceException("对不起,您的账号:"+ username +" 已停用。");}returncreateLoginUser(user, token.getAssertion().getPrincipal().getAttributes());}publicUserDetailscreateLoginUser(SysUser user,Map<String,Object> attributes){returnnewLoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user), attributes);}}

7.添加 CasAuthenticationSuccessHandler.java

  • 在framework模块下添加:
packagecom.ruoyi.framework.security.handle;importcom.ruoyi.common.constant.CacheConstants;importcom.ruoyi.common.constant.Constants;importcom.ruoyi.common.core.domain.model.LoginUser;importcom.ruoyi.common.core.redis.RedisCache;importcom.ruoyi.framework.config.properties.CasProperties;importcom.ruoyi.framework.web.service.TokenService;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.security.core.Authentication;importorg.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;importorg.springframework.security.web.savedrequest.HttpSessionRequestCache;importorg.springframework.security.web.savedrequest.RequestCache;importorg.springframework.stereotype.Service;importorg.springframework.util.StringUtils;importjavax.servlet.ServletException;importjavax.servlet.http.Cookie;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjavax.servlet.http.HttpSession;importjava.io.IOException;importjava.util.concurrent.TimeUnit;/**
 * @author Wen先森
 * @description CAS认证中心
 * @date 2024/7/26 17:05
 */@ServicepublicclassCasAuthenticationSuccessHandlerextendsSavedRequestAwareAuthenticationSuccessHandler{privatestaticfinalLogger log =LoggerFactory.getLogger(CasAuthenticationSuccessHandler.class);privatestaticfinalRequestCache requestCache =newHttpSessionRequestCache();privatefinalRedisCache redisCache;privatefinalTokenService tokenService;privatefinalCasProperties casProperties;// 令牌有效期(默认30分钟)@Value("${token.expireTime}")privateint expireTime;publicCasAuthenticationSuccessHandler(RedisCache redisCache,TokenService tokenService,CasProperties casProperties){this.redisCache = redisCache;this.tokenService = tokenService;this.casProperties = casProperties;}@OverridepublicvoidonAuthenticationSuccess(HttpServletRequest request,HttpServletResponse response,Authentication authentication)throwsServletException,IOException{String targetUrlParameter =getTargetUrlParameter();if(isAlwaysUseDefaultTargetUrl()||(targetUrlParameter !=null&&StringUtils.hasText(request.getParameter(targetUrlParameter)))){
            requestCache.removeRequest(request, response);super.onAuthenticationSuccess(request, response, authentication);return;}clearAuthenticationAttributes(request);LoginUser userDetails =(LoginUser) authentication.getPrincipal();String token = tokenService.createToken(userDetails);// 打印日志
        log.debug("CAS认证中心的ticket:"+authentication.getCredentials().toString());// 往Redis中设置token
        redisCache.setCacheObject(CacheConstants.LOGIN_TICKET_KEY+authentication.getCredentials().toString(), token, expireTime,TimeUnit.MINUTES);// 往Cookie中设置tokenCookie casCookie =newCookie(Constants.WEB_TOKEN_KEY, token);
        casCookie.setMaxAge(expireTime *60);// TODO: 设置 cookie path 为 根目录(解决前端 cookie 丢失问题), 不确定 是否合理
        casCookie.setPath("/");
        response.addCookie(casCookie);// 设置后端认证成功标识HttpSession httpSession = request.getSession();
        httpSession.setAttribute(Constants.CAS_TOKEN, token);
        httpSession.setMaxInactiveInterval(expireTime *60);// 登录成功后跳转到前端登录页面getRedirectStrategy().sendRedirect(request, response, casProperties.getWebUrl());}}

8.修改 SecurityConfig

  • 添加cas的处理逻辑:
packagecom.ruoyi.framework.config;importcom.ruoyi.framework.config.properties.CasProperties;importcom.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter;importcom.ruoyi.framework.security.filter.SingleSignOutTokenFilter;importcom.ruoyi.framework.security.handle.CasAuthenticationSuccessHandler;importcom.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;importcom.ruoyi.framework.web.service.impl.UserDetailsServiceCasImpl;importorg.jasig.cas.client.session.SingleSignOutHttpSessionListener;importorg.jasig.cas.client.validation.Cas30ServiceTicketValidator;importorg.springframework.boot.web.servlet.ServletListenerRegistrationBean;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.core.Ordered;importorg.springframework.http.HttpMethod;importorg.springframework.security.authentication.AuthenticationManager;importorg.springframework.security.cas.ServiceProperties;importorg.springframework.security.cas.authentication.CasAuthenticationProvider;importorg.springframework.security.cas.web.CasAuthenticationEntryPoint;importorg.springframework.security.cas.web.CasAuthenticationFilter;importorg.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;importorg.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;importorg.springframework.security.config.http.SessionCreationPolicy;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;importorg.springframework.security.web.authentication.logout.LogoutFilter;importorg.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;importorg.springframework.web.filter.CorsFilter;/**
 * spring security配置
 *
 * @author ruoyi
 */@Configuration@EnableGlobalMethodSecurity(prePostEnabled =true, securedEnabled =true)publicclassSecurityConfigextendsWebSecurityConfigurerAdapter{privatefinalCasProperties casProperties;privatefinalUserDetailsServiceCasImpl userDetailsServiceCasImpl;privatefinalCasAuthenticationSuccessHandler casAuthenticationSuccessHandler;// 跨域过滤器privatefinalCorsFilter corsFilter;// 自定义用户认证逻辑privatefinalUserDetailsService userDetailsService;// 自定义用户退出处理类privatefinalLogoutSuccessHandlerImpl logoutSuccessHandler;// 自定义用户token认证过滤器privatefinalJwtAuthenticationTokenFilter authenticationTokenFilter;publicSecurityConfig(CasProperties casProperties,UserDetailsServiceCasImpl userDetailsServiceCasImpl,CasAuthenticationSuccessHandler casAuthenticationSuccessHandler,CorsFilter corsFilter,UserDetailsService userDetailsService,LogoutSuccessHandlerImpl logoutSuccessHandler,JwtAuthenticationTokenFilter authenticationTokenFilter){this.casProperties = casProperties;this.userDetailsServiceCasImpl = userDetailsServiceCasImpl;this.casAuthenticationSuccessHandler = casAuthenticationSuccessHandler;this.corsFilter = corsFilter;this.userDetailsService = userDetailsService;this.logoutSuccessHandler = logoutSuccessHandler;this.authenticationTokenFilter = authenticationTokenFilter;}/**
     * 解决无法直接注入AuthenticationManager
     *
     * @return AuthenticationManager
     */@Bean@OverridepublicAuthenticationManagerauthenticationManagerBean()throwsException{returnsuper.authenticationManagerBean();}/**
     * anyRequest          |   匹配所有请求路径
     * access              |   SpringEl表达式结果为true时可以访问
     * anonymous           |   匿名可以访问
     * denyAll             |   用户不能访问
     * fullyAuthenticated  |   用户完全认证可以访问(非RememberMe下自动登录)
     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
     * permitAll           |   用户可以任意访问
     * rememberMe          |   允许通过RememberMe登录的用户访问
     * authenticated       |   用户登录后可访问
     */@Overrideprotectedvoidconfigure(HttpSecurity httpSecurity)throwsException{
        httpSecurity
                // CSRF禁用,因为不使用session.csrf().disable()// 基于token,所以不需要session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()// 过滤请求.authorizeRequests()// 对于登录login,注册register,验证码captchaImage可以任意访问// .antMatchers("/login", "/register", "/captchaImage").permitAll()// 静态资源可以任意访问.antMatchers(HttpMethod.GET,"/","/*.html","/**/*.html","/**/*.css","/**/*.js","/profile/**").permitAll().antMatchers("/swagger-ui.html").permitAll().antMatchers("/swagger-resources/**").permitAll().antMatchers("/webjars/**").permitAll().antMatchers("/*/api-docs").permitAll().antMatchers("/druid/**").permitAll()// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated().and().headers().frameOptions().disable();// 单点登录登出
        httpSecurity.logout().permitAll().logoutSuccessHandler(logoutSuccessHandler);// 添加CAS filter
        httpSecurity.addFilter(casAuthenticationFilter())// 请求单点退出过滤器// .addFilterBefore(casLogoutFilter(), LogoutFilter.class)// token认证过滤器.addFilterBefore(authenticationTokenFilter,CasAuthenticationFilter.class)// 单点登出过滤器.addFilterBefore(singleSignOutTokenFilter(),CasAuthenticationFilter.class).exceptionHandling()// 认证失败.authenticationEntryPoint(casAuthenticationEntryPoint());// 添加CORS filter
        httpSecurity.addFilterBefore(corsFilter,JwtAuthenticationTokenFilter.class);
        httpSecurity.addFilterBefore(corsFilter,LogoutFilter.class);// 禁用缓存
        httpSecurity.headers().cacheControl();}/**
     * 强散列哈希加密实现注册
     *
     * @return 强散列哈希加密实现
     */@BeanpublicBCryptPasswordEncoderbCryptPasswordEncoder(){returnnewBCryptPasswordEncoder();}/**
     * 身份认证接口
     *
     * @param auth 身份认证
     * @throws Exception 异常抛出
     */@Overrideprotectedvoidconfigure(AuthenticationManagerBuilder auth)throwsException{if(casProperties.isCasEnable()){super.configure(auth);
            auth.authenticationProvider(casAuthenticationProvider());}else{
            auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());}}/**
     * CAS认证的入口注册
     *
     * @return CAS认证的入口
     */@BeanpublicCasAuthenticationEntryPointcasAuthenticationEntryPoint(){CasAuthenticationEntryPoint casAuthenticationEntryPoint =newCasAuthenticationEntryPoint();
        casAuthenticationEntryPoint.setLoginUrl(casProperties.getCasServerLoginUrl());
        casAuthenticationEntryPoint.setServiceProperties(serviceProperties());return casAuthenticationEntryPoint;}/**
     * 指定service相关信息注册
     *
     * @return 指定service相关信息
     */@BeanpublicServicePropertiesserviceProperties(){ServiceProperties serviceProperties =newServiceProperties();
        serviceProperties.setService(casProperties.getAppServerUrl()+ casProperties.getAppLoginUrl());
        serviceProperties.setAuthenticateAllArtifacts(true);return serviceProperties;}/**
     * CAS认证过滤器注册
     *
     * @return CAS认证过滤器
     * @throws Exception 异常抛出
     */@BeanpublicCasAuthenticationFiltercasAuthenticationFilter()throwsException{CasAuthenticationFilter casAuthenticationFilter =newCasAuthenticationFilter();
        casAuthenticationFilter.setAuthenticationManager(authenticationManager());
        casAuthenticationFilter.setFilterProcessesUrl(casProperties.getAppLoginUrl());
        casAuthenticationFilter.setAuthenticationSuccessHandler(casAuthenticationSuccessHandler);return casAuthenticationFilter;}/**
     * CAS认证Provider注册
     *
     * @return CAS认证Provider
     */@BeanpublicCasAuthenticationProvidercasAuthenticationProvider(){CasAuthenticationProvider casAuthenticationProvider =newCasAuthenticationProvider();
        casAuthenticationProvider.setAuthenticationUserDetailsService(userDetailsServiceCasImpl);
        casAuthenticationProvider.setServiceProperties(serviceProperties());
        casAuthenticationProvider.setTicketValidator(cas30ServiceTicketValidator());
        casAuthenticationProvider.setKey("casAuthenticationProviderKey");return casAuthenticationProvider;}/**
     * CAS服务票据验证器注册
     *
     * @return CAS服务票据验证器
     */@BeanpublicCas30ServiceTicketValidatorcas30ServiceTicketValidator(){returnnewCas30ServiceTicketValidator(casProperties.getCasServerTicketValidatorUrl());}/**
     * 单点登出过滤器注册
     *
     * @return 单点登出过滤器
     */@BeanpublicSingleSignOutTokenFiltersingleSignOutTokenFilter(){SingleSignOutTokenFilter singleSignOutTokenFilter =newSingleSignOutTokenFilter();
        singleSignOutTokenFilter.setIgnoreInitConfiguration(true);return singleSignOutTokenFilter;}/**
     * 单点登出监听器注册
     *
     * @return 单点登出监听器
     */@BeanpublicServletListenerRegistrationBean<SingleSignOutHttpSessionListener>singleSignOutHttpSessionListenerBean(){ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> listenerRegistrationBean =newServletListenerRegistrationBean<>();
        listenerRegistrationBean.setListener(newSingleSignOutHttpSessionListener());
        listenerRegistrationBean.setEnabled(true);
        listenerRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);return listenerRegistrationBean;}/**
     * 请求单点退出过滤器注册
     *
     * @return 单点退出过滤器
     */@BeanpublicLogoutFiltercasLogoutFilter(){LogoutFilter logoutFilter =newLogoutFilter(casProperties.getCasServerLogoutUrl(),newSecurityContextLogoutHandler());
        logoutFilter.setFilterProcessesUrl(casProperties.getAppLogoutUrl());return logoutFilter;}}

9.添加 SingleSignOutTokenFilter

  • 单点退出过滤器
packagecom.ruoyi.framework.security.filter;importcom.ruoyi.framework.security.handle.SingleSignOutHandlerImpl;importorg.jasig.cas.client.configuration.ConfigurationKeys;importorg.jasig.cas.client.session.SessionMappingStorage;importorg.jasig.cas.client.util.AbstractConfigurationFilter;importjavax.servlet.*;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;importjava.util.concurrent.atomic.AtomicBoolean;/**
 * @author Wen先森
 * @description 单点退出过滤器
 * @date 2024/8/1 20:59
 */publicfinalclassSingleSignOutTokenFilterextendsAbstractConfigurationFilter{privatestaticfinalSingleSignOutHandlerImplHANDLER=newSingleSignOutHandlerImpl();privatefinalAtomicBoolean handlerInitialized =newAtomicBoolean(false);@Overridepublicvoidinit(finalFilterConfig filterConfig)throwsServletException{super.init(filterConfig);if(!isIgnoreInitConfiguration()){setArtifactParameterName(getString(ConfigurationKeys.ARTIFACT_PARAMETER_NAME));setLogoutParameterName(getString(ConfigurationKeys.LOGOUT_PARAMETER_NAME));setRelayStateParameterName(getString(ConfigurationKeys.RELAY_STATE_PARAMETER_NAME));setLogoutCallbackPath(getString(ConfigurationKeys.LOGOUT_CALLBACK_PATH));HANDLER.setArtifactParameterOverPost(getBoolean(ConfigurationKeys.ARTIFACT_PARAMETER_OVER_POST));HANDLER.setEagerlyCreateSessions(getBoolean(ConfigurationKeys.EAGERLY_CREATE_SESSIONS));}HANDLER.init();
        handlerInitialized.set(true);}publicvoidsetArtifactParameterName(finalString name){HANDLER.setArtifactParameterName(name);}publicvoidsetLogoutParameterName(finalString name){HANDLER.setLogoutParameterName(name);}publicvoidsetRelayStateParameterName(finalString name){HANDLER.setRelayStateParameterName(name);}publicvoidsetLogoutCallbackPath(finalString logoutCallbackPath){HANDLER.setLogoutCallbackPath(logoutCallbackPath);}publicvoidsetSessionMappingStorage(finalSessionMappingStorage storage){HANDLER.setSessionMappingStorage(storage);}@OverridepublicvoiddoFilter(finalServletRequest servletRequest,finalServletResponse servletResponse,finalFilterChain filterChain)throwsIOException,ServletException{finalHttpServletRequest request =(HttpServletRequest) servletRequest;finalHttpServletResponse response =(HttpServletResponse) servletResponse;if(!this.handlerInitialized.getAndSet(true)){HANDLER.init();}if(HANDLER.process(request, response)){
            filterChain.doFilter(servletRequest, servletResponse);}}@Overridepublicvoiddestroy(){}privatestaticSingleSignOutHandlerImplgetSingleSignOutHandler(){returnHANDLER;}}

10.添加 SingleSignOutHandlerImpl

  • 单点退出过滤器实现类
packagecom.ruoyi.framework.security.handle;importcom.alibaba.fastjson2.JSON;importcom.ruoyi.common.constant.CacheConstants;importcom.ruoyi.common.constant.Constants;importcom.ruoyi.common.constant.HttpStatus;importcom.ruoyi.common.core.domain.AjaxResult;importcom.ruoyi.common.core.domain.model.LoginUser;importcom.ruoyi.common.core.redis.RedisCache;importcom.ruoyi.common.utils.ServletUtils;importcom.ruoyi.common.utils.StringUtils;importcom.ruoyi.common.utils.spring.SpringUtils;importcom.ruoyi.framework.manager.AsyncManager;importcom.ruoyi.framework.manager.factory.AsyncFactory;importcom.ruoyi.framework.web.service.TokenService;importio.jsonwebtoken.Claims;importio.jsonwebtoken.Jwts;importorg.jasig.cas.client.Protocol;importorg.jasig.cas.client.configuration.ConfigurationKeys;importorg.jasig.cas.client.session.HashMapBackedSessionMappingStorage;importorg.jasig.cas.client.session.SessionMappingStorage;importorg.jasig.cas.client.util.CommonUtils;importorg.jasig.cas.client.util.XmlUtils;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.config.YamlMapFactoryBean;importorg.springframework.core.io.ClassPathResource;importjavax.servlet.ServletException;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjavax.servlet.http.HttpSession;importjavax.xml.bind.DatatypeConverter;importjava.nio.charset.StandardCharsets;importjava.util.*;importjava.util.zip.Inflater;/**
 * @author Wen先森
 * @description 单点退出过滤器实现类
 * @date 2024/8/1 21:00
 */publicfinalclassSingleSignOutHandlerImpl{privatefinalstaticintDECOMPRESSION_FACTOR=10;privatefinalLogger logger =LoggerFactory.getLogger(getClass());privateSessionMappingStorage sessionMappingStorage =newHashMapBackedSessionMappingStorage();privateString artifactParameterName =Protocol.CAS2.getArtifactParameterName();privateString logoutParameterName =ConfigurationKeys.LOGOUT_PARAMETER_NAME.getDefaultValue();privateString relayStateParameterName =ConfigurationKeys.RELAY_STATE_PARAMETER_NAME.getDefaultValue();privateString logoutCallbackPath;privateboolean artifactParameterOverPost =false;privateboolean eagerlyCreateSessions =true;privateList<String> safeParameters;privatefinalLogoutStrategy logoutStrategy =isServlet30()?newServlet30LogoutStrategy():newServlet25LogoutStrategy();publicvoidsetSessionMappingStorage(finalSessionMappingStorage storage){this.sessionMappingStorage = storage;}publicvoidsetArtifactParameterOverPost(finalboolean artifactParameterOverPost){this.artifactParameterOverPost = artifactParameterOverPost;}publicSessionMappingStoragegetSessionMappingStorage(){returnthis.sessionMappingStorage;}publicvoidsetArtifactParameterName(finalString name){this.artifactParameterName = name;}publicvoidsetLogoutParameterName(finalString name){this.logoutParameterName = name;}publicvoidsetLogoutCallbackPath(finalString logoutCallbackPath){this.logoutCallbackPath = logoutCallbackPath;}publicvoidsetRelayStateParameterName(finalString name){this.relayStateParameterName = name;}publicvoidsetEagerlyCreateSessions(finalboolean eagerlyCreateSessions){this.eagerlyCreateSessions = eagerlyCreateSessions;}publicsynchronizedvoidinit(){if(this.safeParameters ==null){CommonUtils.assertNotNull(this.artifactParameterName,"artifactParameterName cannot be null.");CommonUtils.assertNotNull(this.logoutParameterName,"logoutParameterName cannot be null.");CommonUtils.assertNotNull(this.sessionMappingStorage,"sessionMappingStorage cannot be null.");CommonUtils.assertNotNull(this.relayStateParameterName,"relayStateParameterName cannot be null.");if(this.artifactParameterOverPost){this.safeParameters =Arrays.asList(this.logoutParameterName,this.artifactParameterName);}else{this.safeParameters =Collections.singletonList(this.logoutParameterName);}}}privatebooleanisTokenRequest(finalHttpServletRequest request){returnCommonUtils.isNotBlank(CommonUtils.safeGetParameter(request,this.artifactParameterName,this.safeParameters));}privatebooleanisLogoutRequest(finalHttpServletRequest request){if("POST".equalsIgnoreCase(request.getMethod())){return!isMultipartRequest(request)&&pathEligibleForLogout(request)&&CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request,this.logoutParameterName,this.safeParameters));}if("GET".equalsIgnoreCase(request.getMethod())){returnCommonUtils.isNotBlank(CommonUtils.safeGetParameter(request,this.logoutParameterName,this.safeParameters));}returnfalse;}privatebooleanpathEligibleForLogout(finalHttpServletRequest request){return logoutCallbackPath ==null|| logoutCallbackPath.equals(getPath(request));}privateStringgetPath(finalHttpServletRequest request){return request.getServletPath()+CommonUtils.nullToEmpty(request.getPathInfo());}publicbooleanprocess(finalHttpServletRequest request,finalHttpServletResponse response){if(isTokenRequest(request)){
            logger.trace("Received a token request");recordSession(request);returntrue;}if(isLogoutRequest(request)){
            logger.trace("Received a logout request");destroySession(request, response);returnfalse;}
        logger.trace("Ignoring URI for logout: {}", request.getRequestURI());returntrue;}privatevoidrecordSession(finalHttpServletRequest request){finalHttpSession session = request.getSession(this.eagerlyCreateSessions);if(session ==null){
            logger.debug("No session currently exists (and none created).  Cannot record session information for single sign out.");return;}finalString token =CommonUtils.safeGetParameter(request,this.artifactParameterName,this.safeParameters);
        logger.debug("用户登录认证的ticket:"+token);
        logger.debug("Recording session for token {}", token);try{this.sessionMappingStorage.removeBySessionById(session.getId());}catch(finalException ignored){}
        sessionMappingStorage.addSessionById(token, session);}privateStringuncompressLogoutMessage(finalString originalMessage){finalbyte[] binaryMessage =DatatypeConverter.parseBase64Binary(originalMessage);Inflater decompresser =null;try{
            decompresser =newInflater();
            decompresser.setInput(binaryMessage);finalbyte[] result =newbyte[binaryMessage.length *DECOMPRESSION_FACTOR];finalint resultLength = decompresser.inflate(result);returnnewString(result,0, resultLength,StandardCharsets.UTF_8);}catch(finalException e){
            logger.error("Unable to decompress logout message", e);thrownewRuntimeException(e);}finally{if(decompresser !=null){
                decompresser.end();}}}@SuppressWarnings("unchecked")privatevoiddestroySession(finalHttpServletRequest request,finalHttpServletResponse response){String logoutMessage =CommonUtils.safeGetParameter(request,this.logoutParameterName,this.safeParameters);if(CommonUtils.isBlank(logoutMessage)){
            logger.error("Could not locate logout message of the request from {}",this.logoutParameterName);return;}if(!logoutMessage.contains("SessionIndex")){
            logoutMessage =uncompressLogoutMessage(logoutMessage);}

        logger.trace("Logout request: {}", logoutMessage);finalString token =XmlUtils.getTextForElement(logoutMessage,"SessionIndex");
        logger.debug("用户退出系统的ticket:"+token);// 字符串非空判断if(CommonUtils.isNotBlank(token)){// 获取Spring的Bean实例RedisCache redisCache =SpringUtils.getBean("redisCache");TokenService tokenService =SpringUtils.getBean("tokenService");// 获取Redis中jwt生成的tokenString loginToken = redisCache.getCacheObject(CacheConstants.LOGIN_TICKET_KEY+token);// 字符串非空判断if(StringUtils.isNotEmpty(loginToken)){// 删除Redis中jwt生成的token
                redisCache.deleteObject(CacheConstants.LOGIN_TICKET_KEY+token);// 新建实例YamlMapFactoryBean yamlMapFb =newYamlMapFactoryBean();// 读取文件
                yamlMapFb.setResources(newClassPathResource("application.yml"));// 获取配置String secret =(String)((Map<String,Object>)Objects.requireNonNull(yamlMapFb.getObject()).get("token")).get("secret");try{// 解密jwt生成的tokenClaims claims =Jwts.parser().setSigningKey(secret).parseClaimsJws(loginToken).getBody();// 解析对应的权限以及用户信息String uuid =(String) claims.get(Constants.LOGIN_USER_KEY);// 获取Redis的keyString userKey =CacheConstants.LOGIN_TOKEN_KEY+ uuid;// 获取Redis中登录用户的信息LoginUser loginUser = redisCache.getCacheObject(userKey);// 对象非空判断if(StringUtils.isNotNull(loginUser)){// 用户账号String userName = loginUser.getUsername();// 删除用户缓存记录
                        tokenService.delLoginUser(loginUser.getToken());// 记录用户退出日志AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName,Constants.LOGOUT,"退出成功"));}// 将字符串渲染到客户端ServletUtils.renderString(response,JSON.toJSONString(AjaxResult.error(HttpStatus.SUCCESS,"退出成功")));}catch(Exception e){
                    e.printStackTrace();}}finalHttpSession session =this.sessionMappingStorage.removeSessionByMappingId(token);if(session !=null){finalString sessionID = session.getId();
                logger.debug("Invalidating session [{}] for token [{}]", sessionID, token);try{
                    session.invalidate();}catch(finalIllegalStateException e){
                    logger.debug("Error invalidating session.", e);}this.logoutStrategy.logout(request);}}}privatebooleanisMultipartRequest(finalHttpServletRequest request){return request.getContentType()!=null&& request.getContentType().toLowerCase().startsWith("multipart");}privatestaticbooleanisServlet30(){try{returnHttpServletRequest.class.getMethod("logout")!=null;}catch(finalNoSuchMethodException e){returnfalse;}}privateinterfaceLogoutStrategy{voidlogout(HttpServletRequest request);}privatestaticclassServlet25LogoutStrategyimplementsLogoutStrategy{@Overridepublicvoidlogout(finalHttpServletRequest request){}}privateclassServlet30LogoutStrategyimplementsLogoutStrategy{@Overridepublicvoidlogout(finalHttpServletRequest request){try{
                request.logout();}catch(finalServletException e){
                logger.debug("Error performing request.logout.");}}}}

前端

1.修改 settings.js

/**
   * 开启cas
   */casEnable:true,/**
   * 单点url
   */casUrl:'http://host:port/sso/login',/**
   * 后台登录url
   */apploginUrl: process.env.VUE_APP_FRONT_END_HOST_AND_PORT+ process.env.VUE_APP_BASE_API+'/cas/index',/**
   * 单点登录url
   */casloginUrl:'http://host:port/sso/login?service='+ process.env.VUE_APP_FRONT_END_HOST_AND_PORT+ process.env.VUE_APP_PUBLIC_PATH+'/index',/**
   * 单点登出url
   */caslogoutUrl:'http://host:port/sso/logout?service=http://host:port/sso/login?service='+ process.env.VUE_APP_FRONT_END_HOST_AND_PORT+ process.env.VUE_APP_PUBLIC_PATH+'/index',

2.修改 permission.js

  • 判断没有token时访问cas登录页面:
import router from'./router'import store from'./store'import{ Message }from'element-ui'import NProgress from'nprogress'import'nprogress/nprogress.css'import{ getToken }from'@/utils/auth'import{ isRelogin }from'@/utils/request'import defaultSettings from'@/settings'

NProgress.configure({showSpinner:false})const whiteList =['/login','/auth-redirect','/bind','/register']

router.beforeEach((to, from, next)=>{
  NProgress.start()debuggeralert('beforeEach getToken')if(getToken()){debuggeralert('getToken in')

    to.meta.title && store.dispatch('settings/setTitle', to.meta.title)/* has token*/if(to.path ==='/login'){next({path:'/'})
      NProgress.done()}else{if(store.getters.roles.length ===0){
        isRelogin.show =true// 判断当前用户是否已拉取完user_info信息
        store.dispatch('GetInfo').then(()=>{
          isRelogin.show =false
          store.dispatch('GenerateRoutes').then(accessRoutes=>{// 根据roles权限生成可访问的路由表
            router.addRoutes(accessRoutes)// 动态添加可访问路由表next({...to,replace:true})// hack方法 确保addRoutes已完成})}).catch(err=>{
            store.dispatch('LogOut').then(()=>{
              Message.error(err)next({path:'/'})})})}else{next()}}}else{// 没有tokenif(whiteList.indexOf(to.path)!==-1){// 在免登录白名单,直接进入next()}else{if(!defaultSettings.casEnable){next(`/login?redirect=${to.fullPath}`)// 否则全部重定向到登录页}//开启casif(defaultSettings.casEnable){alert('defaultSettings.apploginUrl:'+ defaultSettings.apploginUrl);

        window.location.href = defaultSettings.apploginUrl // 否则全部重定向到登录页}
      NProgress.done()}}})

router.afterEach(()=>{
  NProgress.done()})

3.修改 request.js、Navbar.vue

  • request.js
import axios from'axios'import{ Notification, MessageBox, Message, Loading }from'element-ui'import store from'@/store'import{ getToken, removeAllCookie }from'@/utils/auth'import errorCode from'@/utils/errorCode'import{ tansParams, blobValidate }from"@/utils/ruoyi";import cache from'@/plugins/cache'import{ saveAs }from'file-saver'import defaultSettings from'@/settings'let downloadLoadingInstance;// 是否显示重新登录exportlet isRelogin ={show:false};

axios.defaults.headers['Content-Type']='application/json;charset=utf-8'// 创建axios实例const service = axios.create({// axios中请求配置有baseURL选项,表示请求URL公共部分baseURL: process.env.VUE_APP_BASE_API,// 超时timeout:10000})// request拦截器
service.interceptors.request.use(config=>{// 是否需要设置 tokenconst isToken =(config.headers ||{}).isToken ===false// 是否需要防止数据重复提交const isRepeatSubmit =(config.headers ||{}).repeatSubmit ===falseif(getToken()&&!isToken){
    config.headers['Authorization']='Bearer '+getToken()// 让每个请求携带自定义token 请根据实际情况自行修改}// get请求映射params参数if(config.method ==='get'&& config.params){let url = config.url +'?'+tansParams(config.params);
    url = url.slice(0,-1);
    config.params ={};
    config.url = url;}if(!isRepeatSubmit &&(config.method ==='post'|| config.method ==='put')){const requestObj ={url: config.url,data:typeof config.data ==='object'?JSON.stringify(config.data): config.data,time:newDate().getTime()}const sessionObj = cache.session.getJSON('sessionObj')if(sessionObj ===undefined|| sessionObj ===null|| sessionObj ===''){
      cache.session.setJSON('sessionObj', requestObj)}else{const s_url = sessionObj.url;// 请求地址const s_data = sessionObj.data;// 请求数据const s_time = sessionObj.time;// 请求时间const interval =1000;// 间隔时间(ms),小于此时间视为重复提交if(s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url){const message ='数据正在处理,请勿重复提交';
        console.warn(`[${s_url}]: `+ message)return Promise.reject(newError(message))}else{
        cache.session.setJSON('sessionObj', requestObj)}}}return config
},error=>{
    console.log(error)
    Promise.reject(error)})// 响应拦截器
service.interceptors.response.use(res=>{// 单点重定向判断if(res.status ===200&& res.request.responseURL.indexOf(defaultSettings.casUrl)>-1){removeAllCookie()alert('defaultSettings.casloginUrl:'+ defaultSettings.casloginUrl);

      window.location.href = defaultSettings.casloginUrl
    }// 未设置状态码则默认成功状态const code = res.data.code ||200;// 获取错误信息const msg = errorCode[code]|| res.data.msg || errorCode['default']// 二进制数据则直接返回if(res.request.responseType ==='blob'|| res.request.responseType ==='arraybuffer'){return res.data
    }if(code ===401){if(!isRelogin.show){
        isRelogin.show =true;
        MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录','系统提示',{confirmButtonText:'重新登录',cancelButtonText:'取消',type:'warning'}).then(()=>{
        isRelogin.show =false;
        store.dispatch('LogOut').then(()=>{
          location.href ='/index';})}).catch(()=>{
        isRelogin.show =false;});}return Promise.reject('无效的会话,或者会话已过期,请重新登录。')}elseif(code ===500){Message({message: msg,type:'error'})return Promise.reject(newError(msg))}elseif(code ===302){removeAllCookie()alert('defaultSettings.casloginUrl:'+ defaultSettings.casloginUrl);

      window.location.href = defaultSettings.casloginUrl
    }elseif(code !==200){
      Notification.error({title: msg
      })return Promise.reject('error')}else{return res.data
    }},error=>{
    console.log('err'+ error)let{ message }= error;if(message =="Network Error"){
      message ="后端接口连接异常";}elseif(message.includes("timeout")){
      message ="系统接口请求超时";}elseif(message.includes("Request failed with status code")){
      message ="系统接口"+ message.substr(message.length -3)+"异常";}Message({message: message,type:'error',duration:5*1000})return Promise.reject(error)})// 通用下载方法exportfunctiondownload(url, params, filename){
  downloadLoadingInstance = Loading.service({text:"正在下载数据,请稍候",spinner:"el-icon-loading",background:"rgba(0, 0, 0, 0.7)",})return service.post(url, params,{transformRequest:[(params)=>{returntansParams(params)}],headers:{'Content-Type':'application/x-www-form-urlencoded'},responseType:'blob'}).then(async(data)=>{const isLogin =awaitblobValidate(data);if(isLogin){const blob =newBlob([data])saveAs(blob, filename)}else{const resText =await data.text();const rspObj =JSON.parse(resText);const errMsg = errorCode[rspObj.code]|| rspObj.msg || errorCode['default']
      Message.error(errMsg);}
    downloadLoadingInstance.close();}).catch((r)=>{
    console.error(r)
    Message.error('下载文件出现错误,请联系管理员!')
    downloadLoadingInstance.close();})}exportdefault service
  • Navbar.vue
<template><divclass="navbar"><hamburgerid="hamburger-container":is-active="sidebar.opened"class="hamburger-container"@toggleClick="toggleSideBar"/><breadcrumbid="breadcrumb-container"class="breadcrumb-container"v-if="!topNav"/><top-navid="topmenu-container"class="topmenu-container"v-if="topNav"/><divclass="right-menu"><templatev-if="device!=='mobile'"><searchid="header-search"class="right-menu-item"/><el-tooltipcontent="源码地址"effect="dark"placement="bottom"><ruo-yi-gitid="ruoyi-git"class="right-menu-item hover-effect"/></el-tooltip><el-tooltipcontent="文档地址"effect="dark"placement="bottom"><ruo-yi-docid="ruoyi-doc"class="right-menu-item hover-effect"/></el-tooltip><screenfullid="screenfull"class="right-menu-item hover-effect"/><el-tooltipcontent="布局大小"effect="dark"placement="bottom"><size-selectid="size-select"class="right-menu-item hover-effect"/></el-tooltip></template><el-dropdownclass="avatar-container right-menu-item hover-effect"trigger="click"><divclass="avatar-wrapper"><img:src="avatar"class="user-avatar"><iclass="el-icon-caret-bottom"/></div><el-dropdown-menuslot="dropdown"><router-linkto="/user/profile"><el-dropdown-item>个人中心</el-dropdown-item></router-link><[email protected]="setting = true"><span>布局设置</span></el-dropdown-item><[email protected]="logout"><span>退出登录</span></el-dropdown-item></el-dropdown-menu></el-dropdown></div></div></template><script>import{ mapGetters }from'vuex'import Breadcrumb from'@/components/Breadcrumb'import TopNav from'@/components/TopNav'import Hamburger from'@/components/Hamburger'import Screenfull from'@/components/Screenfull'import SizeSelect from'@/components/SizeSelect'import Search from'@/components/HeaderSearch'import RuoYiGit from'@/components/RuoYi/Git'import RuoYiDoc from'@/components/RuoYi/Doc'exportdefault{components:{
    Breadcrumb,
    TopNav,
    Hamburger,
    Screenfull,
    SizeSelect,
    Search,
    RuoYiGit,
    RuoYiDoc
  },computed:{...mapGetters(['sidebar','avatar','device']),setting:{get(){returnthis.$store.state.settings.showSettings
      },set(val){this.$store.dispatch('settings/changeSetting',{key:'showSettings',value: val
        })}},topNav:{get(){returnthis.$store.state.settings.topNav
      }}},methods:{toggleSideBar(){this.$store.dispatch('app/toggleSideBar')},asynclogout(){this.$confirm('确定注销并退出系统吗?','提示',{confirmButtonText:'确定',cancelButtonText:'取消',type:'warning'}).then(()=>{this.$store.dispatch('LogOut').then(()=>{// location.href = '/index';})}).catch(()=>{});}}}</script><stylelang="scss"scoped>.navbar{height: 50px;overflow: hidden;position: relative;background: #fff;box-shadow: 0 1px 4px rgba(0,21,41,.08);.hamburger-container{line-height: 46px;height: 100%;float: left;cursor: pointer;transition: background .3s;-webkit-tap-highlight-color:transparent;&:hover{background:rgba(0, 0, 0, .025)}}.breadcrumb-container{float: left;}.topmenu-container{position: absolute;left: 50px;}.errLog-container{display: inline-block;vertical-align: top;}.right-menu{float: right;height: 100%;line-height: 50px;&:focus{outline: none;}.right-menu-item{display: inline-block;padding: 0 8px;height: 100%;font-size: 18px;color: #5a5e66;vertical-align: text-bottom;&.hover-effect{cursor: pointer;transition: background .3s;&:hover{background:rgba(0, 0, 0, .025)}}}.avatar-container{margin-right: 30px;.avatar-wrapper{margin-top: 5px;position: relative;.user-avatar{cursor: pointer;width: 40px;height: 40px;border-radius: 10px;}.el-icon-caret-bottom{cursor: pointer;position: absolute;right: -20px;top: 25px;font-size: 12px;}}}}}</style>

4.修改 user.js

  • 登出后跳转到cas登出页面:
import defaultSettings from'@/settings'import{ login, logout, getInfo }from'@/api/login'import{ getToken, setToken, removeToken }from'@/utils/auth'const user ={state:{token:getToken(),name:'',avatar:'',roles:[],permissions:[]},mutations:{SET_TOKEN:(state, token)=>{
      state.token = token
    },SET_NAME:(state, name)=>{
      state.name = name
    },SET_AVATAR:(state, avatar)=>{
      state.avatar = avatar
    },SET_ROLES:(state, roles)=>{
      state.roles = roles
    },SET_PERMISSIONS:(state, permissions)=>{
      state.permissions = permissions
    }},actions:{// 登录Login({ commit }, userInfo){alert('Login');const username = userInfo.username.trim()const password = userInfo.password
      const code = userInfo.code
      const uuid = userInfo.uuid
      returnnewPromise((resolve, reject)=>{login(username, password, code, uuid).then(res=>{setToken(res.token)commit('SET_TOKEN', res.token)resolve()}).catch(error=>{reject(error)})})},// 获取用户信息GetInfo({ commit, state }){returnnewPromise((resolve, reject)=>{getInfo().then(res=>{const user = res.user
          const avatar =(user.avatar ==""|| user.avatar ==null)?require("@/assets/images/profile.jpg"): process.env.VUE_APP_BASE_API+ user.avatar;if(res.roles && res.roles.length >0){// 验证返回的roles是否是一个非空数组commit('SET_ROLES', res.roles)commit('SET_PERMISSIONS', res.permissions)}else{commit('SET_ROLES',['ROLE_DEFAULT'])}commit('SET_NAME', user.userName)commit('SET_AVATAR', avatar)resolve(res)}).catch(error=>{reject(error)})})},// 退出系统LogOut({ commit, state }){returnnewPromise((resolve, reject)=>{logout(state.token).then(()=>{commit('SET_TOKEN','')commit('SET_ROLES',[])commit('SET_PERMISSIONS',[])removeToken()resolve()
          location.href = defaultSettings.caslogoutUrl
        }).catch(error=>{reject(error)})})},// 前端 登出FedLogOut({ commit }){returnnewPromise(resolve=>{commit('SET_TOKEN','')removeToken()resolve()})}}}exportdefault user

5.修改 auth.js

import Cookies from'js-cookie'const TokenKey ='Admin-Token'const JsessionId ='JSESSIONID'exportfunctiongetToken(){return Cookies.get(TokenKey)}exportfunctionsetToken(token){return Cookies.set(TokenKey, token)}exportfunctionremoveToken(){debuggeralert('removeToken')return Cookies.remove(TokenKey)}exportfunctionremoveJsessionId(){return Cookies.remove(JsessionId)}exportfunctionremoveAllCookie(){removeToken()removeJsessionId()}

6.添加环境变量

# 智能招聘管理系统 - 写在 开发环境中 为了 不报错
VUE_APP_PUBLIC_PATH = '/xxx_vue'

#前端域名+端口
VUE_APP_FRONT_END_HOST_AND_PORT = 'http://host:port'

#后端域名+端口
VUE_APP_BACK_END_HOST_AND_PORT = 'http://host:port'

本文转载自: https://blog.csdn.net/qq_41596778/article/details/141124485
版权归原作者 Wen先森 所有, 如有侵权,请联系我们删除。

“RuoYi-Vue前后端分离版集成Cas单点登录”的评论:

还没有评论