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

后端

1.添加CAS依赖

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

<dependency>
<groupId>org.springframework.securitygroupId>
    <artifactId>spring-security-casartifactId>
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(

你可能感兴趣的:(java,vue.js,前端,javascript,java)