若依框架(前后端分离)集成cas5.5
一、后端配置
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
cas:server:host:#CAS服务地址url: http://localhost:8080/cas-server#CAS服务登录地址login_url: ${cas.server.host.url}/login#CAS服务登出地址logout_url: ${cas.server.host.url}/logout?service=${app.server.host.url}# 应用访问地址
app:#开启cascasEnable: trueserver:host:url: http://localhost:${server.port}#应用登录地址login_url: /#应用登出地址logout_url: /logout#前端登录地址web_url: http://localhost/index
3、修改com.ruoyi.common.core.domain.model.LoginUser.java
由于CAS认证需要authorities属性,此属性不能为空:
@Override
public Collection<? extends GrantedAuthority> getAuthorities()
{//由于CAS认证需要authorities属性,此属性不能为空,此处为了方便直接new HashSet()return new HashSet<>();
}
4、修改com.ruoyi.common.constant.Constants.java
添加CAS认证成功标识:
/*** CAS登录成功后的后台标识*/
public static final String CAS_TOKEN = "cas_token";/*** CAS登录成功后的前台Cookie的Key*/
public static final String WEB_TOKEN_KEY = "Admin-Token";
5、修改com.ruoyi.framework.web.service.TokenService.java
增加删除用户登录信息方法:
/*** cas 删除用户身份信息*/
public void delClaimsLoginUser(String token)
{if (StringUtils.isNotEmpty(token)){Claims claims = parseToken(token);// 解析对应的权限以及用户信息String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);String userKey = getTokenKey(uuid);redisCache.deleteObject(userKey);}
}
6、在com.ruoyi.framework.config.properties下添加CasProperties.java
读取cas配置信息:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;/*** CAS的配置参数*/
@Component
public class CasProperties {@Value("${cas.server.host.url}")private String casServerUrl;@Value("${cas.server.host.login_url}")private String casServerLoginUrl;@Value("${cas.server.host.logout_url}")private String casServerLogoutUrl;@Value("${app.casEnable}")private boolean casEnable;@Value("${app.server.host.url}")private String appServerUrl;@Value("${app.login_url}")private String appLoginUrl;@Value("${app.logout_url}")private String appLogoutUrl;@Value("${app.web_url}")private String webUrl;public String getWebUrl() {return webUrl;}public String getCasServerUrl() {return casServerUrl;}public void setCasServerUrl(String casServerUrl) {this.casServerUrl = casServerUrl;}public String getCasServerLoginUrl() {return casServerLoginUrl;}public void setCasServerLoginUrl(String casServerLoginUrl) {this.casServerLoginUrl = casServerLoginUrl;}public String getCasServerLogoutUrl() {return casServerLogoutUrl;}public void setCasServerLogoutUrl(String casServerLogoutUrl) {this.casServerLogoutUrl = casServerLogoutUrl;}public boolean isCasEnable() {return casEnable;}public void setCasEnable(boolean casEnable) {this.casEnable = casEnable;}public String getAppServerUrl() {return appServerUrl;}public void setAppServerUrl(String appServerUrl) {this.appServerUrl = appServerUrl;}public String getAppLoginUrl() {return appLoginUrl;}public void setAppLoginUrl(String appLoginUrl) {this.appLoginUrl = appLoginUrl;}public String getAppLogoutUrl() {return appLogoutUrl;}public void setAppLogoutUrl(String appLogoutUrl) {this.appLogoutUrl = appLogoutUrl;}
}
7、在framework.web.service包下添加CasUserDetailsService.java
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.UserStatus;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;/*** 用于加载用户信息 实现UserDetailsService接口,或者实现AuthenticationUserDetailsService接口*/@Service
public class CasUserDetailsService implements AuthenticationUserDetailsService<CasAssertionAuthenticationToken> {private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);@Autowiredprivate ISysUserService userService;@Autowiredprivate SysPermissionService permissionService;@Overridepublic UserDetails loadUserDetails(CasAssertionAuthenticationToken token) throws UsernameNotFoundException {String username = token.getName();SysUser user = userService.selectUserByUserName(username);if (StringUtils.isNull(user)) {log.info("登录用户:{} 不存在.", username);throw new ServiceException("登录用户:" + username + " 不存在");} else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {log.info("登录用户:{} 已被删除.", username);throw new ServiceException("对不起,您的账号:" + username + " 已被删除");} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {log.info("登录用户:{} 已被停用.", username);throw new ServiceException("对不起,您的账号:" + username + " 已停用");}return createLoginUser(user);}public UserDetails createLoginUser(SysUser user) {return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));}
}
8、在framework.security.handle包下添加CasAuthenticationSuccessHandler.java
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.framework.config.properties.CasProperties;
import com.ruoyi.framework.web.service.TokenService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;@Service
public class CasAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {protected final Log logger = LogFactory.getLog(this.getClass());private RequestCache requestCache = new HttpSessionRequestCache();@Autowiredprivate TokenService tokenService;@Autowiredprivate CasProperties casProperties;/*** 令牌有效期(默认30分钟)*/@Value("${token.expireTime}")private int expireTime;@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,Authentication authentication) throws ServletException, 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);Cookie casCookie = new Cookie(Constants.WEB_TOKEN_KEY, token);casCookie.setMaxAge(expireTime * 60);casCookie.setPath("/");response.addCookie(casCookie);//设置后端认证成功标识HttpSession httpSession = request.getSession();httpSession.setAttribute(Constants.CAS_TOKEN, token);//登录成功后跳转到前端访问页面String url = request.getParameter("redirect");getRedirectStrategy().sendRedirect(request, response, url);}
}
9、在framework.security.entrypoint包下添加CustomCasAuthenticationEntryPoint.java
import com.fch.common.utils.StringUtils;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class CustomCasAuthenticationEntryPoint extends CasAuthenticationEntryPoint {private String serviceUrlBak=null;@Overrideprotected String createServiceUrl(HttpServletRequest request, HttpServletResponse response) {if (serviceUrlBak==null) {serviceUrlBak = getServiceProperties().getService();}//将前端登录成功后跳转页面加入CAS请求中if(serviceUrlBak!=null){String queryString=request.getQueryString();if (StringUtils.isNotNull(queryString)) {String serviceUrl = "";if (queryString.contains("redirect")) {if (StringUtils.isNotBlank(queryString)) {serviceUrl = "?" + queryString;}}getServiceProperties().setService(serviceUrlBak + serviceUrl);}}return super.createServiceUrl(request, response);}
}
10、在framework.security.entrypoint包下添加CustomSessionMappingStorage.java
import com.ruoyi.common.constant.Constants;
import com.ruoyi.framework.web.service.TokenService;
import org.apache.catalina.session.StandardSessionFacade;
import org.jasig.cas.client.session.SessionMappingStorage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;@Component
public class CustomSessionMappingStorage implements SessionMappingStorage {private final Map<String, HttpSession> MANAGED_SESSIONS = new HashMap();private final Map<String, String> ID_TO_SESSION_KEY_MAPPING = new HashMap();private final Logger logger = LoggerFactory.getLogger(this.getClass());@Autowiredprivate TokenService tokenService;public CustomSessionMappingStorage() {}@Overridepublic synchronized void addSessionById(String mappingId, HttpSession session) {this.ID_TO_SESSION_KEY_MAPPING.put(session.getId(), mappingId);this.MANAGED_SESSIONS.put(mappingId, session);}@Overridepublic synchronized void removeBySessionById(String sessionId) {this.logger.debug("Attempting to remove Session=[{}]", sessionId);String key = (String)this.ID_TO_SESSION_KEY_MAPPING.get(sessionId);if (this.logger.isDebugEnabled()) {if (key != null) {this.logger.debug("Found mapping for session. Session Removed.");} else {this.logger.debug("No mapping for session found. Ignoring.");}}this.MANAGED_SESSIONS.remove(key);this.ID_TO_SESSION_KEY_MAPPING.remove(sessionId);}/*** 根据CAS发送的id,查找后端用户session中的token,并删除* @param mappingId* @return*/@Overridepublic synchronized HttpSession removeSessionByMappingId(String mappingId) {StandardSessionFacade session = (StandardSessionFacade) this.MANAGED_SESSIONS.get(mappingId);if (session != null) {this.removeBySessionById(session.getId());try {String token = (String) session.getAttribute(Constants.CAS_TOKEN);tokenService.delClaimsLoginUser(token);} catch (IllegalStateException e) {this.logger.error("已成功登出");}}return session;}
}
11、修改SecurityConfig.java,添加cas的处理逻辑:
import com.ruoyi.framework.config.properties.CasProperties;
import com.ruoyi.framework.config.properties.PermitAllUrlProperties;
import com.ruoyi.framework.security.entrypoint.CustomCasAuthenticationEntryPoint;
import com.ruoyi.framework.security.entrypoint.CustomSessionMappingStorage;
import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter;
import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl;
import com.ruoyi.framework.security.handle.CasAuthenticationSuccessHandler;
import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;
import com.ruoyi.framework.web.service.CasUserDetailsService;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.web.filter.CorsFilter;/*** spring security配置** @author ruoyi*/
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{/*** 自定义用户认证逻辑*/@Autowiredprivate UserDetailsService userDetailsService;/*** 认证失败处理类*/@Autowiredprivate AuthenticationEntryPointImpl unauthorizedHandler;/*** 退出处理类*/@Autowiredprivate LogoutSuccessHandlerImpl logoutSuccessHandler;/*** token认证过滤器*/@Autowiredprivate JwtAuthenticationTokenFilter authenticationTokenFilter;/*** 跨域过滤器*/@Autowiredprivate CorsFilter corsFilter;/*** 允许匿名访问的地址*/@Autowiredprivate PermitAllUrlProperties permitAllUrl;/*** cas*/@Autowiredprivate CasProperties casProperties;@Autowiredprivate CasUserDetailsService customUserDetailsService;@Autowiredprivate CasAuthenticationSuccessHandler casAuthenticationSuccessHandler;@Autowiredprivate CustomSessionMappingStorage customSessionMappingStorage;/*** 解决 无法直接注入 AuthenticationManager** @return* @throws Exception*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception{return super.authenticationManagerBean();}/*** anyRequest | 匹配所有请求路径* access | SpringEl表达式结果为true时可以访问* anonymous | 匿名可以访问* denyAll | 用户不能访问* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问* hasRole | 如果有参数,参数表示角色,则其角色可以访问* permitAll | 用户可以任意访问* rememberMe | 允许通过remember-me登录的用户访问* authenticated | 用户登录后可访问*/@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception{// 注解标记允许匿名访问的urlExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());if (!casProperties.isCasEnable()) {httpSecurity// CSRF禁用,因为不使用session.csrf().disable()// 认证失败处理类.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()// 基于token,所以不需要session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()// 过滤请求.authorizeRequests()// 对于登录login 注册register 验证码captchaImage 允许匿名访问.antMatchers("/login", "/register", "/captchaImage", "/updatePassword").anonymous()// 静态资源,可匿名访问.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll().antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated().and().headers().frameOptions().disable();// 添加Logout filterhttpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);// 添加JWT filterhttpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);// 添加CORS filterhttpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);}//开启casif (casProperties.isCasEnable()) {httpSecurity// CSRF禁用,因为不使用session.csrf().disable()// 基于token,所以不需要session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()// 过滤请求.authorizeRequests()// 对于登录login 验证码captchaImage 允许匿名访问//.antMatchers("/login", "/captchaImage").anonymous().antMatchers(HttpMethod.GET,"/*.html","/**/*.html","/**/*.css","/**/*.js").permitAll()/*.antMatchers("/profile/**").anonymous().antMatchers("/common/download**").anonymous().antMatchers("/common/download/resource**").anonymous()*/.antMatchers("/swagger-ui.html").anonymous().antMatchers("/swagger-resources/**").anonymous().antMatchers("/webjars/**").anonymous().antMatchers("/*/api-docs").anonymous().antMatchers("/druid/**").anonymous()/*.antMatchers("/websocket/**").anonymous().antMatchers("/magic/web/**").anonymous()*/// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated().and().headers().frameOptions().disable();//单点登录登出httpSecurity.logout().permitAll().logoutSuccessHandler(logoutSuccessHandler);// Custom JWT based security filterhttpSecurity.addFilter(casAuthenticationFilter()).addFilterBefore(authenticationTokenFilter, CasAuthenticationFilter.class).addFilterBefore(casLogoutFilter(), CasAuthenticationFilter.class).addFilterBefore(singleSignOutFilter(), JwtAuthenticationTokenFilter.class).exceptionHandling()//认证失败.authenticationEntryPoint(casAuthenticationEntryPoint());// 添加CORS filterhttpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);// disable page cachinghttpSecurity.headers().cacheControl();}}/*** 强散列哈希加密实现*/@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder(){return new BCryptPasswordEncoder();}/*** 身份认证接口*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception{if (!casProperties.isCasEnable()) {auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());}// casif (casProperties.isCasEnable()) {super.configure(auth);auth.authenticationProvider(casAuthenticationProvider());}}/*** 认证的入口*/@Beanpublic CasAuthenticationEntryPoint casAuthenticationEntryPoint() {CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CustomCasAuthenticationEntryPoint();casAuthenticationEntryPoint.setLoginUrl(casProperties.getCasServerLoginUrl());casAuthenticationEntryPoint.setServiceProperties(serviceProperties());return casAuthenticationEntryPoint;}/*** 指定service相关信息*/@Beanpublic ServiceProperties serviceProperties() {ServiceProperties serviceProperties = new ServiceProperties();serviceProperties.setService(casProperties.getAppServerUrl()+casProperties.getAppLoginUrl());serviceProperties.setAuthenticateAllArtifacts(true);return serviceProperties;}/*** CAS认证过滤器*/@Beanpublic CasAuthenticationFilter casAuthenticationFilter() throws Exception {CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();casAuthenticationFilter.setAuthenticationManager(authenticationManager());casAuthenticationFilter.setFilterProcessesUrl(casProperties.getAppLoginUrl());casAuthenticationFilter.setAuthenticationSuccessHandler(casAuthenticationSuccessHandler);return casAuthenticationFilter;}/*** cas 认证 Provider*/@Beanpublic CasAuthenticationProvider casAuthenticationProvider() {CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();casAuthenticationProvider.setAuthenticationUserDetailsService(customUserDetailsService);casAuthenticationProvider.setServiceProperties(serviceProperties());casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());casAuthenticationProvider.setKey("casAuthenticationProviderKey");return casAuthenticationProvider;}@Beanpublic Cas20ServiceTicketValidator cas20ServiceTicketValidator() {return new Cas20ServiceTicketValidator(casProperties.getCasServerUrl());}/*** 单点登出过滤器* 使用customSessionMappingStorage处理cas发送的登出请求*/@Beanpublic SingleSignOutFilter singleSignOutFilter() {SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();//singleSignOutFilter.setLogoutCallbackPath(casProperties.getWebUrl());singleSignOutFilter.setSessionMappingStorage(customSessionMappingStorage);singleSignOutFilter.setIgnoreInitConfiguration(true);return singleSignOutFilter;}/*** 请求单点退出过滤器*/@Beanpublic LogoutFilter casLogoutFilter() {LogoutFilter logoutFilter = new LogoutFilter(casProperties.getCasServerLogoutUrl(),new SecurityContextLogoutHandler());logoutFilter.setFilterProcessesUrl(casProperties.getAppLogoutUrl());return logoutFilter;}@Beanpublic ServletListenerRegistrationBean singleSignOutHttpSessionListener() {ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean();bean.setListener(new SingleSignOutHttpSessionListener());
// bean.setName(""); //默认为bean namebean.setEnabled(true);//bean.setOrder(Ordered.HIGHEST_PRECEDENCE); //设置优先级return bean;}
}
二、前端配置
1、修改settings.js
添加cas登录和登出地址:
/*** 开启cas*/casEnable: true,/*** 单点登录url,前面为后端地址,后面为前端地址*/casloginUrl: 'http://localhost:8688/?redirect=http://localhost:8689',/*** 单点登出url,前面为cas服务器地址,后面为前端地址*/caslogoutUrl: 'http://localhost:8080/cas-server/logout?service=http://localhost:8689'
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'
//引入setting.js
import defaultSettings from '@/settings'NProgress.configure({ showSpinner: false })const whiteList = ['/auth-redirect', '/bind', '/register']router.beforeEach((to, from, next) => {NProgress.start()if (getToken()) {/* has token*/if (to.path === '/login') {next({ path: '/' })NProgress.done()} else {if (store.getters.roles.length === 0) {// 判断当前用户是否已拉取完user_info信息store.dispatch('GetInfo').then(() => {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) {if (to.path === '/login' && defaultSettings.casEnable) {// 否则全部重定向到访问页window.location.href = defaultSettings.casloginUrl + to.fullPath} else {// 在免登录白名单,直接进入next()}} else {//casif (!defaultSettings.casEnable) {next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页}if (defaultSettings.casEnable) {// 否则全部重定向到访问页window.location.href = defaultSettings.casloginUrl + to.fullPath}NProgress.done()}}
})router.afterEach(() => {NProgress.done()
})
3、修改request.js
修改utils目录下request.js文件“响应拦截器”,登出后不做响应:
//引入setting.js
import defaultSettings from '@/settings'// 响应拦截器
service.interceptors.response.use(res => {// 未设置状态码则默认成功状态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;ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {confirmButtonText: '重新登录',cancelButtonText: '取消',type: 'warning'}).then(() => {isRelogin.show = false;useUserStore().logOut().then(() => {//casif (!defaultSettings.casEnable) {location.href = '/index';}})}).catch(() => {isRelogin.show = false;});}return Promise.reject('无效的会话,或者会话已过期,请重新登录。')} else if (code === 500) {ElMessage({message: msg,type: 'error'})return Promise.reject(new Error(msg))} else if (code !== 200) {ElNotification.error({title: msg})return Promise.reject('error')} else {return Promise.resolve(res.data)}
},error => {console.log('err' + error)let { message } = error;if (message == "Network Error") {message = "后端接口连接异常";}else if (message.includes("timeout")) {message = "系统接口请求超时";}else if (message.includes("Request failed with status code")) {message = "系统接口" + message.substr(message.length - 3) + "异常";}ElMessage({message: message,type: 'error',duration: 5 * 1000})return Promise.reject(error)}
)
4、修改Navbar.vue
修改layout/components目录下Navbar.vue文件的logout()方法,登出后不做响应:
//引入setting.js
import defaultSettings from '@/settings'//退出登录
async logout() {this.$confirm('确定注销并退出系统吗?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => {this.$store.dispatch('LogOut').then(() => {//casif (!defaultSettings .casEnable) {location.href = '/index';}})})}
5、修改user.js
修改store/modules目录下user.js的logOut()方法:
//引入setting.js
import defaultSettings from '@/settings'// 退出系统LogOut({ commit, state }) {return new Promise((resolve, reject) => {logout(state.token).then(() => {commit('SET_TOKEN', '')commit('SET_ROLES', [])commit('SET_PERMISSIONS', [])removeToken()resolve()//casif (defaultSettings.casEnable) {window.location.href = defaultSettings.caslogoutUrl}}).catch(error => {reject(error)})})},
至此,已完成若依前后端集成CAS单点登录功能。
相关文章:
若依框架(前后端分离)集成cas5.5
一、后端配置 1、添加cas依赖 在common模块pom添加spring-security-cas依赖: <!-- spring security cas--> <dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-cas</artifactId> &l…...
区块链方向学习路线
学习路线图 下面是登链社区给出的区块链开发者的学习路线图 学习路线建议 对于一个区块链方向的学习者而言,首先要了解的是区块链理论知识,当你了解了区块链的理论知识之后,下面有三个方向来学习,可以通俗的理解为区块链方向的后…...
【从零开始入门unity游戏开发之——C#篇40】C#特性(Attributes)和自定义特性
文章目录 前言一、特性(Attributes)基本概念二、自定义特性1、自定义特性代码示例:2、应用自定义特性:3、解释3.1 **AttributeUsage 特性**3.2 特性的命名3.3 **构造函数**:3.4 **属性**: 4、使用反射获取特…...
Unity2D无限地图的实现(简单好抄)
说明:本教程实现的是在2D游戏中玩家在游戏中上下左右移动的时候自动进行地图拼接的功能,如果你只想实现左右移动的无限地图,那么这篇博客也能起到一定参考作用。 思路 第一步: 创建一个10*10的2D游戏对象当做地图 第二步创建一个…...
OSPF特殊区域(open shortest path first LSA Type7)
一、区域介绍 1、Stub区域 Stub区域是一种可选的配置属性。通常来说,Stub区域位于自治系统的边界,例如,只有一 个ABR的非骨干区域。在这些区域中,设备的路由表规模以及路由信息传递的数量都会大量减少。 kill 4 5类type 传递1 …...
工程师了解的Lua语言
1、关于lua语言 lua语言是用于嵌入式领域当中的一门脚本语言,其实在大学期间,我也没有接触过这门语言,但是在未来的发展之路当中,需要用到这门语言,所以在我的知识库当中添加这门语言知识是必要而且重要的,…...
每日一学——日志管理工具(Graylog)
5.2 Graylog 5.2.1 输入输出配置 嘿,小伙伴们!今天我们要介绍的是Graylog——一款功能强大的日志管理工具。Graylog不仅可以帮助我们收集、存储和搜索日志数据,还可以通过丰富的插件系统进行扩展。让我们一起来看看如何配置Graylog来收集日…...
SQL Server中最大并行度详解
在 SQL Server 中,MAXDOP(Maximum Degree of Parallelism,最大并行度)是一个用于控制并行查询执行中最大可用 CPU 核心数的配置参数。通过设置 MAXDOP,你可以管理 SQL Server 在执行并行查询时使用多少个处理器核心&am…...
2453.学习周刊-2024年53周
封面 不要站在问题一边打败孩子,而是站在孩子一边打败问题,多从孩子的角度思考问题,帮助孩子一起解决问题 ✍优秀博文 SQL中历史数据处理实践指南新领导上任了,老员工该如何适应?主动接纳还是我行我素? ✍…...
硬件-射频-PCB-常见天线分类-ESP32实例
文章目录 一:常见天线1.1 PCB天线①蓝牙模块的蛇形走线-天线②倒F天线-IFA:③蛇形倒F天线-MIFA④立体的倒F天线-PIFA 1.2 实例示意图1.21 对数周期天线(LPDA):1.22 2.4GHZ的八木天线:1.23 陶瓷天线:1.24 外接天线: 二&…...
Golang的容器编排实践
Golang的容器编排实践 一、Golang中的容器编排概述 作为一种高效的编程语言,其在容器编排领域也有着广泛的运用。容器编排是指利用自动化工具对容器化的应用进行部署、管理和扩展的过程,典型的容器编排工具包括Docker Swarm、Kubernetes等。在Golang中&a…...
关于大一上的总结
大一上总结 前言 源于学长们都喜欢写总结,今晚也正好听见一首有点触动心灵的歌,深有感慨,故来此写下这篇总结 正文 1.暑假前的准备 暑假之前姑且还是学习了基本的C语法,大概是到了结构体的地方,进度很慢࿰…...
SpringBoot教程(三十二) SpringBoot集成Skywalking链路跟踪
SpringBoot教程(三十二) | SpringBoot集成Skywalking链路跟踪 一、Skywalking是什么?二、Skywalking与JDK版本的对应关系三、Skywalking下载四、Skywalking 数据存储五、Skywalking 的启动六、部署探针 前提: Agents 8.9.0 放入 …...
iOS 11 中的 HEIF 图像格式 - 您需要了解的内容
HEIF,也称为高效图像格式,是iOS 11 之后发布的新图像格式,以能够在不压缩图像质量的情况下以较小尺寸保存照片而闻名。换句话说,HEIF 图像格式可以具有相同或更好的照片质量,同时比 JPEG、PNG、GIF、TIFF 占用更少的设…...
Python爬虫入门(1)
在互联网时代,数据成为了最宝贵的资源之一。Python作为一种功能强大的编程语言,因其简洁的语法和丰富的库支持,成为了编写网络爬虫的首选。本文将带你入门Python爬虫技术,让你能够从互联网上自动获取数据。 什么是爬虫࿱…...
GIT 企业级开发学习 1
本节主要命令: git init ls 不能列出 .git ls -a 列出 .git 1. 初始化 Git 仓库 git init • 初始化一个新的 Git 仓库,在当前目录下生成一个 .git 隐藏文件夹,用于存储版本控制信息。 2. 查看隐藏文件 ls -a • 使用 ls -a 显示隐藏文件…...
C# _ 数字分隔符的使用
总目录 一、数字分隔符是什么? _ 用作数字分隔符。可以将数字分隔符用于所有类型(二进制,十进制,十六进制)的数字文本。数字分隔符 _ 在编译时是被编译器忽略的,因此在语义上对数字结果没有任何影响。 二…...
迟来的前端面试经验
最近也是在换工作,小公司和大厂(虾皮、腾讯)都有面试。几次面试收获还是比较大的,了解许多自己的短板,当然也拿到了合适的offer。本文主要整理下面试遇到的问题和知识点,希望对准备找工作的掘友有所帮助。 …...
深度解析 LDA 与聚类结合的文本主题分析实战
🌟作者简介:热爱数据分析,学习Python、Stata、SPSS等统计语言的小高同学~🍊个人主页:小高要坚强的博客🍓当前专栏:《Python之文本分析》🍎本文内容:深度解析 LDA 与聚类结合的文本主题分析实战🌸作者“三要”格言:要坚强、要努力、要学习 目录 引言 技术框架…...
国内Ubuntu环境Docker部署CosyVoice
国内Ubuntu环境Docker部署CosyVoice 本文旨在记录在 国内 CosyVoice项目在 Ubuntu 环境下如何使用 dockermin-conda进行一键部署。 源项目地址: https://github.com/FunAudioLLM/CosyVoice 如果想要使用 dockerpython 进行部署,可以参考我另一篇博客中的…...
Oracle sql developer and Toad for Oracle set start DBMS output
Oracle sql developer Toad for Oracle...
ArcGIS JSAPI 高级教程 - 通过RenderNode实现视频融合效果(不借助三方工具)
ArcGIS JSAPI 高级教程 - 通过RenderNode实现视频融合效果(不借助三方工具) 核心代码完整代码在线示例地球中展示视频可以通过替换纹理的方式实现,但是随着摄像头和无人机的流行,需要视频和场景深度融合,简单的实现方式则不能满足需求。 三维视频融合技术将视频资源与三维…...
关于IDE的相关知识之二【插件推荐】
成长路上不孤单😊😊😊😊😊😊 【14后😊///计算机爱好者😊///持续分享所学😊///如有需要欢迎收藏转发///😊】 今日分享关于ide插件推荐的相关内容!…...
Spring Boot日志处理
文章目录 Spring Boot日志处理1. 日志存入数据库(AOP)2. 日志控制台打印与写入文件(logback) Spring Boot日志处理 1. 日志存入数据库(AOP) 引入aop依赖 <dependency><groupId>org.springfram…...
node.js之---子线程(child_process)模块
为什么需要子线程(child_process)模块 Worker Threads 的基本概念 如何使用 Worker Threads Worker Threads 的性能 Worker 线程的优势和限制 进阶用法:共享内存 为什么需要子线程(child_process)模块 在 Node.js…...
什么是打流,怎么用iperf3打流
什么是打流 在网络安全和黑灰产领域,“打流”具有不同的含义,常用于形容通过技术手段制造流量假象或发起流量攻击。 流量攻击(DDoS)中的“打流”: “打流”指向目标服务器或网络发起 大规模的数据请求,造…...
Java 可变参数、Collections工具类
一. 可变参数 1. 可变参数:就是一种特殊形参,定义方法、构造器的形参列表里,格式:数据类型...参数名称 2. 特点:可以不传数据;可以穿一个或多个数据;也可以传一个数组 3. 好处:常常用…...
day30-awk进阶
awk模式种类 awk的模式分为这几种 正则表达式 基本正则扩展正则比较表达式范围表达式特殊模式 BEGINEND awk比较运算符(语法) 关系运算符解释示例<小于x<y<小于等于x<y等于xy!不等于x!y>大于等于x>y>大于x>y~匹配正则x~/正则…...
cka考试-03-k8s版本升级
一、原题 二、解答 [root@master ~]# kubectl get node NAME STATUS ROLES AGE VERSION master Ready control-plane,master 25h v1.22.12 node1 Ready worker 25h v1.22.12 node2 Ready worker …...
前后端分离项目部署到云服务器、宝塔(前端vue、后端springboot)详细教程
一、部署介绍 部署的环境是宝塔(宝塔9.0.0)、阿里云服务器(centos 7.6);前端是Vue3项目、后端是springboot3x、jdk11、数据库有redis、mysql;搜索采用的是es。 由于宝塔面板中可以快速进行环境的配置&…...
vue面试题|[2025-1-3]
1.v-if和v-show的区别? 都是可以控制元素的显示和隐藏 1.v-show是控制元素的display值来让元素显示和隐藏;v-if显示(隐藏)时会把整个DOM元素添加(删除) 2.v-show只是简单的css切换;v-if有一个局…...
微信小程序中的 storage(本地存储)和内存是两个完全不同的存储区域
这是一个非常关键且容易混淆的概念 既然 this.globalData.appId appId 是将 appId 存储在内存中,为什么微信小程序中的 wx.getStorage 和 wx.setStorage(本地存储)中没有 appId,并且您提出了一个非常重要的疑问:stor…...
时序优化方法
1.rtl级 1.1避免组合逻辑级数过深 当组合逻辑级数过深时,如果时序允许,可以通过插入时序逻辑来打断组合逻辑链。 1.2寄存器复制 如果是由于fanout过大,可以通过寄存器复制,来减小扇出。 1.3逻辑展平,消除优先级 …...
网络游戏之害
网络游戏之害: 网络游戏于今之世风靡四方,其娱人耳目、畅人心怀之效,固为人知,然所藏之害,若隐伏之暗潮,汹涌而至时,足以覆舟,尤以青年为甚,今且缕析其害,以…...
被催更了,2025元旦源码继续免费送
“时间从来不会停下,它只会匆匆流逝。抓住每一刻,我们才不会辜负自己。” 联系作者免费领💖源💖码。 三联支持:点赞👍收藏⭐️留言📝欢迎留言讨论 更多内容敬请期待。如有需要源码可以联系作者免…...
Springboot - Web
Spring Boot 是一个用于简化 Spring 应用程序配置和部署的框架。它提供了一种快速开发的方式,通过默认配置、自动化配置等特性,使得开发者能够更快捷地构建和部署基于 Spring 的应用。 Spring Boot Web 是 Spring Boot 的一个子模块,它专注于…...
Flutter中的网络请求图片存储为缓存,与定制删除本地缓存
Flutter中的网络请求图片存储为缓存,与定制删除本地缓存 1:封装请求图片函数 2:访问的图片都会转为本地缓存,当相同的请求url,会在本地调用图片 3:本地缓存管理【windows与andriod已经测试】【有页面】【有…...
智汇厦门:苏哒智能携其智能化产品亮相文心中国行现场
2025年1月2日,文心中国行再次踏足美丽的鹭岛厦门。 本次的文心中国行活动不仅有来自政府、高校及企业的精英专家将齐聚一堂,分享AI与大模型的最新研究成果,还正式揭牌百度飞桨(厦门)人工智能产业赋能中心,…...
SQL 分析函数与聚合函数的组合应用
目标:掌握 SQL 中分析函数(窗口函数)与聚合函数的组合使用,通过实际案例实现复杂业务需求,如同比、环比和趋势分析。 1. 分析函数与聚合函数的区别 聚合函数(Aggregate Functions):…...
【Elasticsearch入门到落地】5、安装IK分词器
接上篇《4、Elasticsearch的安装》 上一篇我们进行了Elasticsearch以及Kibana的环境准备及软件安装,本篇我们安装最后一个支持软件IK分词器。 一、IK分词器概念 我们再来回顾一下上一张IK分词器的概念: IK分词器(IK Analyzer)是…...
8、RAG论文笔记(Retrieval-Augmented Generation检索增强生成)
RAG论文笔记 1、 **研究背景与动机**2、方法概述3、RAG 模型架构3.1总体架构3.2 Generator(生成器)3.3 检索器(Retriever)3.4训练(Training)3.5**解码方法**(求近似 )3.6微调的参数 …...
【论文笔记】Contrastive Learning for Sign Language Recognition and Translation
🍎个人主页:小嗷犬的个人主页 🍊个人网站:小嗷犬的技术小站 🥭个人信条:为天地立心,为生民立命,为往圣继绝学,为万世开太平。 基本信息 标题: Contrastive Learning for…...
《C++设计模式》策略模式
文章目录 1、引言1.1 什么是策略模式1.2 策略模式的应用场景1.3 本文结构概览 2、策略模式的基本概念2.1 定义与结构2.2 核心角色解析2.2.1 策略接口(Strategy)2.2.2 具体策略实现(ConcreteStrategy)2.2.3 上下文(Cont…...
细说STM32F407单片机轮询方式CAN通信
目录 一、项目介绍 二、项目配置 1、时钟、DEBUG、USART6、NVIC、GPIO、CodeGenerator 2、CAN1 (1)Bit Timings Parameters组,位时序参数 (2)Basic Parameters组,基本参数 (3)…...
perf:对hutool的BeanUtil工具类做补充
分享一个自定义的BeanUtil,继承的是hutool的工具类,然后自己扩充了几个方法; 1、实现了两个对象覆盖非空属性的功能(经常使用),不需要设置CopyOptions; 2、两个对象,对指定前缀的属…...
【数据结构】栈与队列(FIFO)
在阅读该篇文章之前,可以先了解一下堆栈寄存器和栈帧的运作原理:<【操作系统】堆栈寄存器sp详解以及栈帧>。 栈(FILO) 特性: 栈区的存储遵循着先进后出的原则。 例子: 枪的弹夹,最先装进去的子弹最后射出来,最后装入的子弹…...
02.01、移除重复节点
02.01、[简单] 移除重复节点 1、题目描述 编写代码,移除未排序链表中的重复节点。保留最开始出现的节点。 2、解题思路 为了实现这一目标,我们可以使用一个哈希表(或集合)来记录已经遇到的节点值,逐步遍历链表并删…...
Spring thymeleaf 的快速默认搭建使用
Spring thymeleaf 的快速默认搭建使用 thymeleaf 的搭建Pom 文件 thymeleaf 的使用Controller返回参数String资源文件路径访问端点显示HTML页面 thymeleaf 的搭建 Pom 文件 Pom 文件引入 spring-boot-starter-thymeleaf 依赖 <dependency><groupId>org.springfra…...
unity学习3:如何从github下载开源的unity项目
目录 1 网上别人提供的一些github的unity项目 2 如何下载github上的开源项目呢? 2.1.0 下载工具 2.1.1 下载方法1 2.1.2 下载方法2(适合内部项目) 2.1.3 第1个项目 和第4项目 的比较 第1个项目 第2个项目 第3个项目 2.1.4 下载方法…...
印象笔记07——试一试PDF标注
印象笔记07——试一试PDF标注 [!CAUTION] 根据第六期,我再次查询了资料,印象笔记还是有一些可圈可点的功能的(当然部分有平替),针对会员作用,开发使用场景虽然是逆向的,但我坚信这是一部分人的现…...