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

Wen先森 2024-10-22 08:03:02 阅读 75

后端

1.添加CAS依赖

在common模块pom添加spring-security-cas依赖:

<code><!-- 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

#是否开启CAS

casEnable: true

server:

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():

package com.ruoyi.common.core.domain.model;

import java.util.Collection;

import java.util.HashSet;

import java.util.Map;

import java.util.Set;

import lombok.Data;

import org.springframework.security.core.GrantedAuthority;

import org.springframework.security.core.userdetails.UserDetails;

import com.alibaba.fastjson2.annotation.JSONField;

import com.ruoyi.common.core.domain.entity.SysUser;

/**

* 登录用户身份权限

*

* @author ruoyi

*/

@Data

public class LoginUser implements UserDetails

{

private static final long serialVersionUID = 1L;

/**

* 用户ID

*/

private Long userId;

/**

* 部门ID

*/

private Long deptId;

/**

* 用户唯一标识

*/

private String token;

/**

* 登录时间

*/

private Long loginTime;

/**

* 过期时间

*/

private Long expireTime;

/**

* 登录IP地址

*/

private String ipaddr;

/**

* 登录地点

*/

private String loginLocation;

/**

* 浏览器类型

*/

private String browser;

/**

* 操作系统

*/

private String os;

/**

* 权限列表

*/

private Set<String> permissions;

/**

* 用户信息

*/

private SysUser user;

// CAS用户信息

private Map<String, Object> attributes;

public LoginUser()

{

}

public LoginUser(SysUser user, Set<String> permissions)

{

this.user = user;

this.permissions = permissions;

}

public LoginUser(Long userId, Long deptId, SysUser user, Set<String> permissions)

{

this.userId = userId;

this.deptId = deptId;

this.user = user;

this.permissions = permissions;

}

public LoginUser(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)

@Override

public String getPassword()

{

return user.getPassword();

}

@Override

public String getUsername()

{

return user.getUserName();

}

/**

* 账户是否未过期,过期无法验证

*/

@JSONField(serialize = false)

@Override

public boolean isAccountNonExpired()

{

return true;

}

/**

* 指定用户是否解锁,锁定的用户无法进行身份验证

*

* @return

*/

@JSONField(serialize = false)

@Override

public boolean isAccountNonLocked()

{

return true;

}

/**

* 指示是否已过期的用户的凭据(密码),过期的凭据防止认证

*

* @return

*/

@JSONField(serialize = false)

@Override

public boolean isCredentialsNonExpired()

{

return true;

}

/**

* 是否可用 ,禁用的用户不能身份验证

*

* @return

*/

@JSONField(serialize = false)

@Override

public boolean isEnabled()

{

return true;

}

@Override

public Collection<? extends GrantedAuthority> getAuthorities() {

return new HashSet<>();

}

}

4.修改 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.添加 CasProperties.java

在framework模块下添加读取cas配置信息:

package com.ruoyi.framework.config.properties;

import lombok.Data;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.stereotype.Component;

/**

* @author Wen先森

* @description CAS的配置参数

* @date 2024/7/26 16:58

*/

@Data

@Component

public class CasProperties {

@Value("${cas.server.host.url}")

private String casServerUrl;

@Value("${cas.server.host.ticket_validator_url}")

private String casServerTicketValidatorUrl;

@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.callback_url}")

private String callbackUrl;

@Value("${app.web_url}")

private String webUrl;

}

6.添加 UserDetailsServiceCasImpl

在framework模块下添加:

package com.ruoyi.framework.web.service.impl;

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.framework.web.service.SysPermissionService;

import com.ruoyi.system.service.ISysUserService;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

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;

import java.util.Map;

/**

* @author Wen先森

* @description 用于加载用户信息,实现UserDetailsService接口,或者实现AuthenticationUserDetailsService接口。

* @date 2024/7/26 17:02

*/

@Service

public class UserDetailsServiceCasImpl implements AuthenticationUserDetailsService<CasAssertionAuthenticationToken> {

private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceCasImpl.class);

private final ISysUserService userService;

private final SysPermissionService permissionService;

public UserDetailsServiceCasImpl(ISysUserService userService, SysPermissionService permissionService) {

this.userService = userService;

this.permissionService = permissionService;

}

@Override

public 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, token.getAssertion().getPrincipal().getAttributes());

}

public UserDetails createLoginUser(SysUser user, Map<String, Object> attributes) {

return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user), attributes);

}

}

7.添加 CasAuthenticationSuccessHandler.java

在framework模块下添加:

package com.ruoyi.framework.security.handle;

import com.ruoyi.common.constant.CacheConstants;

import com.ruoyi.common.constant.Constants;

import com.ruoyi.common.core.domain.model.LoginUser;

import com.ruoyi.common.core.redis.RedisCache;

import com.ruoyi.framework.config.properties.CasProperties;

import com.ruoyi.framework.web.service.TokenService;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

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;

import java.util.concurrent.TimeUnit;

/**

* @author Wen先森

* @description CAS认证中心

* @date 2024/7/26 17:05

*/

@Service

public class CasAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

private static final Logger log = LoggerFactory.getLogger(CasAuthenticationSuccessHandler.class);

private static final RequestCache requestCache = new HttpSessionRequestCache();

private final RedisCache redisCache;

private final TokenService tokenService;

private final CasProperties casProperties;

// 令牌有效期(默认30分钟)

@Value("${token.expireTime}")

private int expireTime;

public CasAuthenticationSuccessHandler(RedisCache redisCache, TokenService tokenService, CasProperties casProperties) {

this.redisCache = redisCache;

this.tokenService = tokenService;

this.casProperties = casProperties;

}

@Override

public 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);

// 打印日志

log.debug("CAS认证中心的ticket:"+authentication.getCredentials().toString());

// 往Redis中设置token

redisCache.setCacheObject(CacheConstants.LOGIN_TICKET_KEY+authentication.getCredentials().toString(), token, expireTime, TimeUnit.MINUTES);

// 往Cookie中设置token

Cookie casCookie = new Cookie(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的处理逻辑:

package com.ruoyi.framework.config;

import com.ruoyi.framework.config.properties.CasProperties;

import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter;

import com.ruoyi.framework.security.filter.SingleSignOutTokenFilter;

import com.ruoyi.framework.security.handle.CasAuthenticationSuccessHandler;

import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;

import com.ruoyi.framework.web.service.impl.UserDetailsServiceCasImpl;

import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;

import org.jasig.cas.client.validation.Cas30ServiceTicketValidator;

import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.core.Ordered;

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.http.SessionCreationPolicy;

import org.springframework.security.core.userdetails.UserDetailsService;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

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

*/

@Configuration

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)

public class SecurityConfig extends WebSecurityConfigurerAdapter {

private final CasProperties casProperties;

private final UserDetailsServiceCasImpl userDetailsServiceCasImpl;

private final CasAuthenticationSuccessHandler casAuthenticationSuccessHandler;

// 跨域过滤器

private final CorsFilter corsFilter;

// 自定义用户认证逻辑

private final UserDetailsService userDetailsService;

// 自定义用户退出处理类

private final LogoutSuccessHandlerImpl logoutSuccessHandler;

// 自定义用户token认证过滤器

private final JwtAuthenticationTokenFilter authenticationTokenFilter;

public SecurityConfig(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

@Override

public AuthenticationManager authenticationManagerBean() throws Exception {

return super.authenticationManagerBean();

}

/**

* anyRequest | 匹配所有请求路径

* access | SpringEl表达式结果为true时可以访问

* anonymous | 匿名可以访问

* denyAll | 用户不能访问

* fullyAuthenticated | 用户完全认证可以访问(非RememberMe下自动登录)

* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问

* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问

* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问

* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问

* hasRole | 如果有参数,参数表示角色,则其角色可以访问

* permitAll | 用户可以任意访问

* rememberMe | 允许通过RememberMe登录的用户访问

* authenticated | 用户登录后可访问

*/

@Override

protected void configure(HttpSecurity httpSecurity) throws Exception {

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 强散列哈希加密实现

*/

@Bean

public BCryptPasswordEncoder bCryptPasswordEncoder() {

return new BCryptPasswordEncoder();

}

/**

* 身份认证接口

*

* @param auth 身份认证

* @throws Exception 异常抛出

*/

@Override

protected void configure(AuthenticationManagerBuilder auth) throws Exception {

if (casProperties.isCasEnable()) {

super.configure(auth);

auth.authenticationProvider(casAuthenticationProvider());

} else {

auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());

}

}

/**

* CAS认证的入口注册

*

* @return CAS认证的入口

*/

@Bean

public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {

CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint();

casAuthenticationEntryPoint.setLoginUrl(casProperties.getCasServerLoginUrl());

casAuthenticationEntryPoint.setServiceProperties(serviceProperties());

return casAuthenticationEntryPoint;

}

/**

* 指定service相关信息注册

*

* @return 指定service相关信息

*/

@Bean

public ServiceProperties serviceProperties() {

ServiceProperties serviceProperties = new ServiceProperties();

serviceProperties.setService(casProperties.getAppServerUrl() + casProperties.getAppLoginUrl());

serviceProperties.setAuthenticateAllArtifacts(true);

return serviceProperties;

}

/**

* CAS认证过滤器注册

*

* @return CAS认证过滤器

* @throws Exception 异常抛出

*/

@Bean

public CasAuthenticationFilter casAuthenticationFilter() throws Exception {

CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();

casAuthenticationFilter.setAuthenticationManager(authenticationManager());

casAuthenticationFilter.setFilterProcessesUrl(casProperties.getAppLoginUrl());

casAuthenticationFilter.setAuthenticationSuccessHandler(casAuthenticationSuccessHandler);

return casAuthenticationFilter;

}

/**

* CAS认证Provider注册

*

* @return CAS认证Provider

*/

@Bean

public CasAuthenticationProvider casAuthenticationProvider() {

CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();

casAuthenticationProvider.setAuthenticationUserDetailsService(userDetailsServiceCasImpl);

casAuthenticationProvider.setServiceProperties(serviceProperties());

casAuthenticationProvider.setTicketValidator(cas30ServiceTicketValidator());

casAuthenticationProvider.setKey("casAuthenticationProviderKey");

return casAuthenticationProvider;

}

/**

* CAS服务票据验证器注册

*

* @return CAS服务票据验证器

*/

@Bean

public Cas30ServiceTicketValidator cas30ServiceTicketValidator() {

return new Cas30ServiceTicketValidator(casProperties.getCasServerTicketValidatorUrl());

}

/**

* 单点登出过滤器注册

*

* @return 单点登出过滤器

*/

@Bean

public SingleSignOutTokenFilter singleSignOutTokenFilter() {

SingleSignOutTokenFilter singleSignOutTokenFilter = new SingleSignOutTokenFilter();

singleSignOutTokenFilter.setIgnoreInitConfiguration(true);

return singleSignOutTokenFilter;

}

/**

* 单点登出监听器注册

*

* @return 单点登出监听器

*/

@Bean

public ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> singleSignOutHttpSessionListenerBean() {

ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> listenerRegistrationBean = new ServletListenerRegistrationBean<>();

listenerRegistrationBean.setListener(new SingleSignOutHttpSessionListener());

listenerRegistrationBean.setEnabled(true);

listenerRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);

return listenerRegistrationBean;

}

/**

* 请求单点退出过滤器注册

*

* @return 单点退出过滤器

*/

@Bean

public LogoutFilter casLogoutFilter() {

LogoutFilter logoutFilter = new LogoutFilter(casProperties.getCasServerLogoutUrl(), new SecurityContextLogoutHandler());

logoutFilter.setFilterProcessesUrl(casProperties.getAppLogoutUrl());

return logoutFilter;

}

}

9.添加 SingleSignOutTokenFilter

单点退出过滤器

package com.ruoyi.framework.security.filter;

import com.ruoyi.framework.security.handle.SingleSignOutHandlerImpl;

import org.jasig.cas.client.configuration.ConfigurationKeys;

import org.jasig.cas.client.session.SessionMappingStorage;

import org.jasig.cas.client.util.AbstractConfigurationFilter;

import javax.servlet.*;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

import java.util.concurrent.atomic.AtomicBoolean;

/**

* @author Wen先森

* @description 单点退出过滤器

* @date 2024/8/1 20:59

*/

public final class SingleSignOutTokenFilter extends AbstractConfigurationFilter {

private static final SingleSignOutHandlerImpl HANDLER = new SingleSignOutHandlerImpl();

private final AtomicBoolean handlerInitialized = new AtomicBoolean(false);

@Override

public void init(final FilterConfig filterConfig) throws ServletException {

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);

}

public void setArtifactParameterName(final String name) {

HANDLER.setArtifactParameterName(name);

}

public void setLogoutParameterName(final String name) {

HANDLER.setLogoutParameterName(name);

}

public void setRelayStateParameterName(final String name) {

HANDLER.setRelayStateParameterName(name);

}

public void setLogoutCallbackPath(final String logoutCallbackPath) {

HANDLER.setLogoutCallbackPath(logoutCallbackPath);

}

public void setSessionMappingStorage(final SessionMappingStorage storage) {

HANDLER.setSessionMappingStorage(storage);

}

@Override

public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {

final HttpServletRequest request = (HttpServletRequest) servletRequest;

final HttpServletResponse response = (HttpServletResponse) servletResponse;

if (!this.handlerInitialized.getAndSet(true)) {

HANDLER.init();

}

if (HANDLER.process(request, response)) {

filterChain.doFilter(servletRequest, servletResponse);

}

}

@Override

public void destroy() {

}

private static SingleSignOutHandlerImpl getSingleSignOutHandler() {

return HANDLER;

}

}

10.添加 SingleSignOutHandlerImpl

单点退出过滤器实现类

package com.ruoyi.framework.security.handle;

import com.alibaba.fastjson2.JSON;

import com.ruoyi.common.constant.CacheConstants;

import com.ruoyi.common.constant.Constants;

import com.ruoyi.common.constant.HttpStatus;

import com.ruoyi.common.core.domain.AjaxResult;

import com.ruoyi.common.core.domain.model.LoginUser;

import com.ruoyi.common.core.redis.RedisCache;

import com.ruoyi.common.utils.ServletUtils;

import com.ruoyi.common.utils.StringUtils;

import com.ruoyi.common.utils.spring.SpringUtils;

import com.ruoyi.framework.manager.AsyncManager;

import com.ruoyi.framework.manager.factory.AsyncFactory;

import com.ruoyi.framework.web.service.TokenService;

import io.jsonwebtoken.Claims;

import io.jsonwebtoken.Jwts;

import org.jasig.cas.client.Protocol;

import org.jasig.cas.client.configuration.ConfigurationKeys;

import org.jasig.cas.client.session.HashMapBackedSessionMappingStorage;

import org.jasig.cas.client.session.SessionMappingStorage;

import org.jasig.cas.client.util.CommonUtils;

import org.jasig.cas.client.util.XmlUtils;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.config.YamlMapFactoryBean;

import org.springframework.core.io.ClassPathResource;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;

import javax.xml.bind.DatatypeConverter;

import java.nio.charset.StandardCharsets;

import java.util.*;

import java.util.zip.Inflater;

/**

* @author Wen先森

* @description 单点退出过滤器实现类

* @date 2024/8/1 21:00

*/

public final class SingleSignOutHandlerImpl {

private final static int DECOMPRESSION_FACTOR = 10;

private final Logger logger = LoggerFactory.getLogger(getClass());

private SessionMappingStorage sessionMappingStorage = new HashMapBackedSessionMappingStorage();

private String artifactParameterName = Protocol.CAS2.getArtifactParameterName();

private String logoutParameterName = ConfigurationKeys.LOGOUT_PARAMETER_NAME.getDefaultValue();

private String relayStateParameterName = ConfigurationKeys.RELAY_STATE_PARAMETER_NAME.getDefaultValue();

private String logoutCallbackPath;

private boolean artifactParameterOverPost = false;

private boolean eagerlyCreateSessions = true;

private List<String> safeParameters;

private final LogoutStrategy logoutStrategy = isServlet30() ? new Servlet30LogoutStrategy() : new Servlet25LogoutStrategy();

public void setSessionMappingStorage(final SessionMappingStorage storage) {

this.sessionMappingStorage = storage;

}

public void setArtifactParameterOverPost(final boolean artifactParameterOverPost) {

this.artifactParameterOverPost = artifactParameterOverPost;

}

public SessionMappingStorage getSessionMappingStorage() {

return this.sessionMappingStorage;

}

public void setArtifactParameterName(final String name) {

this.artifactParameterName = name;

}

public void setLogoutParameterName(final String name) {

this.logoutParameterName = name;

}

public void setLogoutCallbackPath(final String logoutCallbackPath) {

this.logoutCallbackPath = logoutCallbackPath;

}

public void setRelayStateParameterName(final String name) {

this.relayStateParameterName = name;

}

public void setEagerlyCreateSessions(final boolean eagerlyCreateSessions) {

this.eagerlyCreateSessions = eagerlyCreateSessions;

}

public synchronized void init() {

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);

}

}

}

private boolean isTokenRequest(final HttpServletRequest request) {

return CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.artifactParameterName, this.safeParameters));

}

private boolean isLogoutRequest(final HttpServletRequest 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())) {

return CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName, this.safeParameters));

}

return false;

}

private boolean pathEligibleForLogout(final HttpServletRequest request) {

return logoutCallbackPath == null || logoutCallbackPath.equals(getPath(request));

}

private String getPath(final HttpServletRequest request) {

return request.getServletPath() + CommonUtils.nullToEmpty(request.getPathInfo());

}

public boolean process(final HttpServletRequest request, final HttpServletResponse response) {

if (isTokenRequest(request)) {

logger.trace("Received a token request");

recordSession(request);

return true;

}

if (isLogoutRequest(request)) {

logger.trace("Received a logout request");

destroySession(request, response);

return false;

}

logger.trace("Ignoring URI for logout: {}", request.getRequestURI());

return true;

}

private void recordSession(final HttpServletRequest request) {

final HttpSession 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;

}

final String 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 (final Exception ignored) {

}

sessionMappingStorage.addSessionById(token, session);

}

private String uncompressLogoutMessage(final String originalMessage) {

final byte[] binaryMessage = DatatypeConverter.parseBase64Binary(originalMessage);

Inflater decompresser = null;

try {

decompresser = new Inflater();

decompresser.setInput(binaryMessage);

final byte[] result = new byte[binaryMessage.length * DECOMPRESSION_FACTOR];

final int resultLength = decompresser.inflate(result);

return new String(result, 0, resultLength, StandardCharsets.UTF_8);

} catch (final Exception e) {

logger.error("Unable to decompress logout message", e);

throw new RuntimeException(e);

} finally {

if (decompresser != null) {

decompresser.end();

}

}

}

@SuppressWarnings("unchecked")

private void destroySession(final HttpServletRequest request, final HttpServletResponse 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);

final String 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生成的token

String loginToken = redisCache.getCacheObject(CacheConstants.LOGIN_TICKET_KEY+token);

// 字符串非空判断

if (StringUtils.isNotEmpty(loginToken)) {

// 删除Redis中jwt生成的token

redisCache.deleteObject(CacheConstants.LOGIN_TICKET_KEY+token);

// 新建实例

YamlMapFactoryBean yamlMapFb = new YamlMapFactoryBean();

// 读取文件

yamlMapFb.setResources(new ClassPathResource("application.yml"));

// 获取配置

String secret = (String) ((Map<String, Object>) Objects.requireNonNull(yamlMapFb.getObject()).get("token")).get("secret");

try {

// 解密jwt生成的token

Claims claims = Jwts.parser()

.setSigningKey(secret)

.parseClaimsJws(loginToken)

.getBody();

// 解析对应的权限以及用户信息

String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);

// 获取Redis的key

String 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();

}

}

final HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token);

if (session != null) {

final String sessionID = session.getId();

logger.debug("Invalidating session [{}] for token [{}]", sessionID, token);

try {

session.invalidate();

} catch (final IllegalStateException e) {

logger.debug("Error invalidating session.", e);

}

this.logoutStrategy.logout(request);

}

}

}

private boolean isMultipartRequest(final HttpServletRequest request) {

return request.getContentType() != null && request.getContentType().toLowerCase().startsWith("multipart");

}

private static boolean isServlet30() {

try {

return HttpServletRequest.class.getMethod("logout") != null;

} catch (final NoSuchMethodException e) {

return false;

}

}

private interface LogoutStrategy {

void logout(HttpServletRequest request);

}

private static class Servlet25LogoutStrategy implements LogoutStrategy {

@Override

public void logout(final HttpServletRequest request) {

}

}

private class Servlet30LogoutStrategy implements LogoutStrategy {

@Override

public void logout(final HttpServletRequest request) {

try {

request.logout();

} catch (final ServletException 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',code>

/**

* 单点登出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',code>

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()

debugger

alert('beforeEach getToken')

if (getToken()) {

debugger

alert('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 {

// 没有token

if (whiteList.indexOf(to.path) !== -1) {

// 在免登录白名单,直接进入

next()

} else {

if (!defaultSettings.casEnable) {

next(`/login?redirect=${ to.fullPath}`) // 否则全部重定向到登录页

}

//开启cas

if (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;

// 是否显示重新登录

export let 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 => {

// 是否需要设置 token

const isToken = (config.headers || { }).isToken === false

// 是否需要防止数据重复提交

const isRepeatSubmit = (config.headers || { }).repeatSubmit === false

if (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: new Date().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(new Error(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('无效的会话,或者会话已过期,请重新登录。')

} else if (code === 500) {

Message({

message: msg,

type: 'error'

})

return Promise.reject(new Error(msg))

} else if (code === 302) {

removeAllCookie()

alert('defaultSettings.casloginUrl:' + defaultSettings.casloginUrl);

window.location.href = defaultSettings.casloginUrl

} else if (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 = "后端接口连接异常";

}

else if (message.includes("timeout")) {

message = "系统接口请求超时";

}

else if (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)

}

)

// 通用下载方法

export function download(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) => { return tansParams(params) }],

headers: { 'Content-Type': 'application/x-www-form-urlencoded' },

responseType: 'blob'

}).then(async (data) => {

const isLogin = await blobValidate(data);

if (isLogin) {

const blob = new Blob([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();

})

}

export default service

Navbar.vue

<template>

<div class="navbar">code>

<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />code>

<breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!topNav"/>code>

<top-nav id="topmenu-container" class="topmenu-container" v-if="topNav"/>code>

<div class="right-menu">code>

<template v-if="device!=='mobile'">code>

<search id="header-search" class="right-menu-item" />code>

<el-tooltip content="源码地址" effect="dark" placement="bottom">code>

<ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />code>

</el-tooltip>

<el-tooltip content="文档地址" effect="dark" placement="bottom">code>

<ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />code>

</el-tooltip>

<screenfull id="screenfull" class="right-menu-item hover-effect" />code>

<el-tooltip content="布局大小" effect="dark" placement="bottom">code>

<size-select id="size-select" class="right-menu-item hover-effect" />code>

</el-tooltip>

</template>

<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">code>

<div class="avatar-wrapper">code>

<img :src="avatar" class="user-avatar">code>

<i class="el-icon-caret-bottom" />code>

</div>

<el-dropdown-menu slot="dropdown">code>

<router-link to="/user/profile">code>

<el-dropdown-item>个人中心</el-dropdown-item>

</router-link>

<el-dropdown-item @click.native="setting = true">code>

<span>布局设置</span>

</el-dropdown-item>

<el-dropdown-item divided @click.native="logout">code>

<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'

export default {

components: {

Breadcrumb,

TopNav,

Hamburger,

Screenfull,

SizeSelect,

Search,

RuoYiGit,

RuoYiDoc

},

computed: {

...mapGetters([

'sidebar',

'avatar',

'device'

]),

setting: {

get() {

return this.$store.state.settings.showSettings

},

set(val) {

this.$store.dispatch('settings/changeSetting', {

key: 'showSettings',

value: val

})

}

},

topNav: {

get() {

return this.$store.state.settings.topNav

}

}

},

methods: {

toggleSideBar() {

this.$store.dispatch('app/toggleSideBar')

},

async logout() {

this.$confirm('确定注销并退出系统吗?', '提示', {

confirmButtonText: '确定',

cancelButtonText: '取消',

type: 'warning'

}).then(() => {

this.$store.dispatch('LogOut').then(() => {

// location.href = '/index';

})

}).catch(() => { });

}

}

}

</script>

<style lang="scss" scoped>code>

.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

return new Promise((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 }) {

return new Promise((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 }) {

return new Promise((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 }) {

return new Promise(resolve => {

commit('SET_TOKEN', '')

removeToken()

resolve()

})

}

}

}

export default user

5.修改 auth.js

import Cookies from 'js-cookie'

const TokenKey = 'Admin-Token'

const JsessionId = 'JSESSIONID'

export function getToken() {

return Cookies.get(TokenKey)

}

export function setToken(token) {

return Cookies.set(TokenKey, token)

}

export function removeToken() {

debugger

alert('removeToken')

return Cookies.remove(TokenKey)

}

export function removeJsessionId() {

return Cookies.remove(JsessionId)

}

export function removeAllCookie() {

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'



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。