后端
1.添加CAS依赖
在common模块pom添加spring-security-cas依赖:
< dependency>
< groupId> org.springframework.security</ groupId>
< artifactId> spring-security-cas</ artifactId>
</ dependency>
2.修改配置文件
在admin模块下的application.yml配置文件中添加:
cas :
server :
host :
url : http: //host: port/sso
ticket_validator_url : http: //host: port/sso
login_url : ${ cas.server.host.url} /login
logout_url : ${ cas.server.host.url} /logout? service=${ cas.server.host.url} /login? service=${ app.server.host.url}
app :
name : Xxx
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
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 ;
@Data
public class LoginUser implements UserDetails
{
private static final long serialVersionUID = 1L ;
private Long userId;
private Long deptId;
private String token;
private Long loginTime;
private Long expireTime;
private String ipaddr;
private String loginLocation;
private String browser;
private String os;
private Set < String > permissions;
private SysUser user;
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 ;
}
@JSONField ( serialize = false )
@Override
public boolean isAccountNonLocked ( )
{
return true ;
}
@JSONField ( serialize = false )
@Override
public boolean isCredentialsNonExpired ( )
{
return true ;
}
@JSONField ( serialize = false )
@Override
public boolean isEnabled ( )
{
return true ;
}
@Override
public Collection < ? extends GrantedAuthority > getAuthorities ( ) {
return new HashSet < > ( ) ;
}
}
4.修改 Constants.java
public static final String CAS_TOKEN = "cas_token" ;
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 ;
@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
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 ;
@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
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 ;
@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;
@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 ( ) ) ;
redisCache. setCacheObject ( CacheConstants . LOGIN_TICKET_KEY + authentication. getCredentials ( ) . toString ( ) , token, expireTime, TimeUnit . MINUTES ) ;
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) ;
httpSession. setMaxInactiveInterval ( expireTime * 60 ) ;
getRedirectStrategy ( ) . sendRedirect ( request, response, casProperties. getWebUrl ( ) ) ;
}
}
8.修改 SecurityConfig
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 ;
@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;
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;
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean ( ) throws Exception {
return super . authenticationManagerBean ( ) ;
}
@Override
protected void configure ( HttpSecurity httpSecurity) throws Exception {
httpSecurity
. csrf ( ) . disable ( )
. sessionManagement ( ) . sessionCreationPolicy ( SessionCreationPolicy . STATELESS ) . and ( )
. authorizeRequests ( )
. 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) ;
httpSecurity. addFilter ( casAuthenticationFilter ( ) )
. addFilterBefore ( authenticationTokenFilter, CasAuthenticationFilter . class )
. addFilterBefore ( singleSignOutTokenFilter ( ) , CasAuthenticationFilter . class ) . exceptionHandling ( )
. authenticationEntryPoint ( casAuthenticationEntryPoint ( ) ) ;
httpSecurity. addFilterBefore ( corsFilter, JwtAuthenticationTokenFilter . class ) ;
httpSecurity. addFilterBefore ( corsFilter, LogoutFilter . class ) ;
httpSecurity. headers ( ) . cacheControl ( ) ;
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder ( ) {
return new BCryptPasswordEncoder ( ) ;
}
@Override
protected void configure ( AuthenticationManagerBuilder auth) throws Exception {
if ( casProperties. isCasEnable ( ) ) {
super . configure ( auth) ;
auth. authenticationProvider ( casAuthenticationProvider ( ) ) ;
} else {
auth. userDetailsService ( userDetailsService) . passwordEncoder ( bCryptPasswordEncoder ( ) ) ;
}
}
@Bean
public CasAuthenticationEntryPoint casAuthenticationEntryPoint ( ) {
CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint ( ) ;
casAuthenticationEntryPoint. setLoginUrl ( casProperties. getCasServerLoginUrl ( ) ) ;
casAuthenticationEntryPoint. setServiceProperties ( serviceProperties ( ) ) ;
return casAuthenticationEntryPoint;
}
@Bean
public ServiceProperties serviceProperties ( ) {
ServiceProperties serviceProperties = new ServiceProperties ( ) ;
serviceProperties. setService ( casProperties. getAppServerUrl ( ) + casProperties. getAppLoginUrl ( ) ) ;
serviceProperties. setAuthenticateAllArtifacts ( true ) ;
return serviceProperties;
}
@Bean
public CasAuthenticationFilter casAuthenticationFilter ( ) throws Exception {
CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter ( ) ;
casAuthenticationFilter. setAuthenticationManager ( authenticationManager ( ) ) ;
casAuthenticationFilter. setFilterProcessesUrl ( casProperties. getAppLoginUrl ( ) ) ;
casAuthenticationFilter. setAuthenticationSuccessHandler ( casAuthenticationSuccessHandler) ;
return casAuthenticationFilter;
}
@Bean
public CasAuthenticationProvider casAuthenticationProvider ( ) {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider ( ) ;
casAuthenticationProvider. setAuthenticationUserDetailsService ( userDetailsServiceCasImpl) ;
casAuthenticationProvider. setServiceProperties ( serviceProperties ( ) ) ;
casAuthenticationProvider. setTicketValidator ( cas30ServiceTicketValidator ( ) ) ;
casAuthenticationProvider. setKey ( "casAuthenticationProviderKey" ) ;
return casAuthenticationProvider;
}
@Bean
public Cas30ServiceTicketValidator cas30ServiceTicketValidator ( ) {
return new Cas30ServiceTicketValidator ( casProperties. getCasServerTicketValidatorUrl ( ) ) ;
}
@Bean
public SingleSignOutTokenFilter singleSignOutTokenFilter ( ) {
SingleSignOutTokenFilter singleSignOutTokenFilter = new SingleSignOutTokenFilter ( ) ;
singleSignOutTokenFilter. setIgnoreInitConfiguration ( true ) ;
return singleSignOutTokenFilter;
}
@Bean
public ServletListenerRegistrationBean < SingleSignOutHttpSessionListener > singleSignOutHttpSessionListenerBean ( ) {
ServletListenerRegistrationBean < SingleSignOutHttpSessionListener > listenerRegistrationBean = new ServletListenerRegistrationBean < > ( ) ;
listenerRegistrationBean. setListener ( new SingleSignOutHttpSessionListener ( ) ) ;
listenerRegistrationBean. setEnabled ( true ) ;
listenerRegistrationBean. setOrder ( Ordered . HIGHEST_PRECEDENCE ) ;
return listenerRegistrationBean;
}
@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 ;
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 ;
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) ) {
RedisCache redisCache = SpringUtils . getBean ( "redisCache" ) ;
TokenService tokenService = SpringUtils . getBean ( "tokenService" ) ;
String loginToken = redisCache. getCacheObject ( CacheConstants . LOGIN_TICKET_KEY + token) ;
if ( StringUtils . isNotEmpty ( loginToken) ) {
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 {
Claims claims = Jwts . parser ( )
. setSigningKey ( secret)
. parseClaimsJws ( loginToken)
. getBody ( ) ;
String uuid = ( String ) claims. get ( Constants . LOGIN_USER_KEY ) ;
String userKey = CacheConstants . LOGIN_TOKEN_KEY + uuid;
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
casEnable : true ,
casUrl : 'http://host:port/sso/login' ,
apploginUrl : process. env. VUE_APP_FRONT_END_HOST_AND_PORT + process. env. VUE_APP_BASE_API + '/cas/index' ,
casloginUrl : 'http://host:port/sso/login?service=' + process. env. VUE_APP_FRONT_END_HOST_AND_PORT + process. env. VUE_APP_PUBLIC_PATH + '/index' ,
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
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)
if ( to. path === '/login' ) {
next ( { path : '/' } )
NProgress. done ( )
} else {
if ( store. getters. roles. length === 0 ) {
isRelogin. show = true
store. dispatch ( 'GetInfo' ) . then ( ( ) => {
isRelogin. show = false
store. dispatch ( 'GenerateRoutes' ) . then ( accessRoutes => {
router. addRoutes ( accessRoutes)
next ( { ... to, replace : true } )
} )
} ) . catch ( err => {
store. dispatch ( 'LogOut' ) . then ( ( ) => {
Message. error ( err)
next ( { path : '/' } )
} )
} )
} else {
next ( )
}
}
} else {
if ( whiteList. indexOf ( to. path) !== - 1 ) {
next ( )
} else {
if ( ! defaultSettings. casEnable) {
next ( ` /login?redirect= ${ to. fullPath} ` )
}
if ( defaultSettings. casEnable) {
alert ( 'defaultSettings.apploginUrl:' + defaultSettings. apploginUrl) ;
window. location. href = defaultSettings. apploginUrl
}
NProgress. done ( )
}
}
} )
router. afterEach ( ( ) => {
NProgress. done ( )
} )
3.修改 request.js、Navbar.vue
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'
const service = axios. create ( {
baseURL : process. env. VUE_APP_BASE_API ,
timeout : 10000
} )
service. interceptors. request. use ( config => {
const isToken = ( config. headers || { } ) . isToken === false
const isRepeatSubmit = ( config. headers || { } ) . repeatSubmit === false
if ( getToken ( ) && ! isToken) {
config. headers[ 'Authorization' ] = 'Bearer ' + getToken ( )
}
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 ;
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
< template>
< div class = " navbar" >
< hamburger id = " hamburger-container" :is-active = " sidebar.opened" class = " hamburger-container" @toggleClick = " toggleSideBar" />
< breadcrumb id = " breadcrumb-container" class = " breadcrumb-container" v-if = " !topNav" />
< top-nav id = " topmenu-container" class = " topmenu-container" v-if = " topNav" />
< div class = " right-menu" >
< template v-if = " device!=='mobile'" >
< search id = " header-search" class = " right-menu-item" />
< el-tooltip content = " 源码地址" effect = " dark" placement = " bottom" >
< ruo-yi-git id = " ruoyi-git" class = " right-menu-item hover-effect" />
</ el-tooltip>
< el-tooltip content = " 文档地址" effect = " dark" placement = " bottom" >
< ruo-yi-doc id = " ruoyi-doc" class = " right-menu-item hover-effect" />
</ el-tooltip>
< screenfull id = " screenfull" class = " right-menu-item hover-effect" />
< el-tooltip content = " 布局大小" effect = " dark" placement = " bottom" >
< size-select id = " size-select" class = " right-menu-item hover-effect" />
</ el-tooltip>
</ template>
< el-dropdown class = " avatar-container right-menu-item hover-effect" trigger = " click" >
< div class = " avatar-wrapper" >
< img :src = " avatar" class = " user-avatar" >
< i class = " el-icon-caret-bottom" />
</ div>
< el-dropdown-menu slot = " dropdown" >
< router-link to = " /user/profile" >
< el-dropdown-item> 个人中心</ el-dropdown-item>
</ router-link>
< el-dropdown-item @click.native = " setting = true" >
< span> 布局设置</ span>
</ el-dropdown-item>
< el-dropdown-item divided @click.native = " 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'
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 ( ( ) => {
} )
} ) . catch ( ( ) => { } ) ;
}
}
}
</ script>
< style lang = " 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
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 ) {
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'