spring security 集成cas实现单点登录

        最近接了一个任务,公司之前为客户做了很多的系统,后面做成了通用的业务系统准备向外销售,因此需要做一个演示系统将所有业务系统都放到演示系统中,用户在演示系统登录后可以访问其中的任意业务系统,这一听就是一个单点登录的需求啊,因此就去了解了下,发现了CAS,

CAS是中央认证服务Central Authentication Service的简称。最初由耶鲁大学的Shawn Bayern 开发,后由Jasig社区维护,经过十多年发展,目前已成为影响最大、广泛使用的、基于Java实现的、开源SSO解决方案。

这里首先需要说明一下SSO的含义和基本流程

含义:有无数业务系统,它们有自己的用户、角色、权限等,现在要做SSO即要将所有业务系统的用户统一到用户中心,去掉业务系统的登录,所有的登录都走用户中心,用户中心登录成功后其他业务系统不需要再次登录

流程:

因此要想做单点登录,必须要有统一的用户中心,那么问题来了,我们肯定有很多老系统,因历史原因没法统一用户,或许是查询数据耦合了用户,或许压根就不是用我们熟悉的技术开发的、等等,我现在面对的就是这种情况,改造老系统需要耗时又耗力,决定用户中心的搭建与业务系统的改造同步进行,在用户中心建好之前先在每个系统里面建立一个统一的用户,统一登录的时候先在用户中心登录之后返回到前端页面根据用户中心返回的ticket去业务系统中获取一个token然后根据token访问各个业务系统,最后在逐步整合各个业务系统的用户到用户中心,因为我们的业务系统之前大多数都是用的spring security 来做的权限验证,因此这次做SSO也是在 spring security 的基础上来做,这样可以兼容之前系统中的相关接口权限。废话不多说直接上代码了

这里首先需要搭建一个cas服务端,相关代码可以去cas官网下载直接放在tomcat下启动,但是这种方式不推荐,还有另外一种overlay的方式,就是将 cas.war 以overlay的方式放到你的自定义cas-server项目中,然后可以在你的 cas-server中实现多种自定义的配置,这里网上的帖子有很多就不赘述了

首先兴建一个 cas-spring-security-boot-starter的spring boot 项目具体目录如下

pom.xml 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
        <relativePath/>
    </parent>

    <groupId>com.rxsk.cas</groupId>
    <artifactId>cas-spring-security-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- security starter Poms -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- security 对CAS支持 -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-cas</artifactId>
            <version>4.2.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>
        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
        </dependency>
        <!--Hutool Java工具包-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.6.2</version>
        </dependency>
    </dependencies>

    <distributionManagement>
        <repository>
            <id>你的私服id1</id>
            <name>你的私服名称1</name>
            <url>http://你的私服地址/repository/maven-releases/</url>
        </repository>
        <snapshotRepository>
            <id>你的私服id2</id>
            <name>你的私服名称2</name>
            <url>http://你的私服地址/repository/maven-snapshots/</url>
        </snapshotRepository>
    </distributionManagement>

</project>

使用了 spring-security-cas 来集成cas,使用了 jjwt 来生成用户token

CasSecurityConfig security与cas的配置类
package com.rxsk.cas.config;

import com.rxsk.cas.filter.JwtTokenFilter;
import com.rxsk.cas.properties.CasProperties;
import com.rxsk.cas.service.UserDetailsPlusService;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.validation.Cas20ProxyTicketValidator;
import org.jasig.cas.client.validation.TicketValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.ProviderManager;
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.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;

import javax.annotation.Resource;
import java.util.Arrays;

@EnableWebSecurity
@Configuration
@EnableConfigurationProperties(CasProperties.class)
public class CasSecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private CasProperties casProperties;

    @Resource
    private UserDetailsPlusService userDetailsPlusService;

    @Resource
    private JwtTokenFilter jwtTokenFilter;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(casAuthenticationProvider()).userDetailsService(userDetailsPlusService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors()
                .and()
                .csrf()
                .disable()
                .authorizeRequests()
                //.antMatchers("/**").anonymous()
                .antMatchers(casProperties.getIgnoredUrl()).anonymous()
                .anyRequest().authenticated()
                .and()
                .exceptionHandling()
                /*.accessDeniedHandler(casAccessDeniedHandler)*/
                .authenticationEntryPoint(authenticationEntryPoint())
                .and()
                .addFilter(casAuthenticationFilter())
                .addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class)
                .addFilterBefore(logoutFilter(), LogoutFilter.class);
    }

    /**
     * 配置CAS Client的属性
     */
    @Bean
    public ServiceProperties serviceProperties() {
        ServiceProperties serviceProperties = new ServiceProperties();

        // 与CasAuthenticationFilter监视的URL一致
        serviceProperties.setService(casProperties.getClientLoginUrl());
        //serviceProperties.setServiceParameter(casProperties.getFilterUrlPattern());
        // 是否关闭单点登录,默认为false,所以也可以不设置。
        serviceProperties.setSendRenew(false);
        return serviceProperties;
    }

    /**
     * CAS认证入口,提供用户浏览器重定向的地址
     */
    @Bean
    @Primary
    public AuthenticationEntryPoint authenticationEntryPoint() {
        CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint();
        // CAS Server认证的登录地址
        entryPoint.setLoginUrl(casProperties.getServerLoginUrl());
        entryPoint.setServiceProperties(serviceProperties());
        return entryPoint;
    }

    /**
     * ticket校验,需要提供CAS Server校验ticket的地址
     */
    @Bean
    public TicketValidator ticketValidator() {
        // 默认情况下使用Cas20ProxyTicketValidator,验证入口是${casServerPrefix}/proxyValidate
        return new Cas20ProxyTicketValidator(casProperties.getServerUrlPrefix());
    }


    /**
     * cas认证处理逻辑
     */
    @Bean
    public CasAuthenticationProvider casAuthenticationProvider() {
        CasAuthenticationProvider provider = new CasAuthenticationProvider();
        provider.setServiceProperties(serviceProperties());
        provider.setTicketValidator(ticketValidator());
        provider.setUserDetailsService(userDetailsPlusService);
        provider.setKey("blurooo");
        return provider;
    }


    /**
     * 提供CAS认证专用过滤器,过滤器的认证逻辑由CasAuthenticationProvider提供
     */
    @Bean
    public CasAuthenticationFilter casAuthenticationFilter() {
        CasAuthenticationFilter filter = new CasAuthenticationFilter();
        filter.setServiceProperties(serviceProperties());
        filter.setAuthenticationManager(new ProviderManager(Arrays.asList(casAuthenticationProvider())));
        return filter;
    }

    /**
     * 接受cas服务端发出的注销请求
     */
    @Bean
    public SingleSignOutFilter singleSignOutFilter() {
        SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
        singleSignOutFilter.setCasServerUrlPrefix(casProperties.getServerUrlPrefix());
        singleSignOutFilter.setIgnoreInitConfiguration(true);
        return singleSignOutFilter;
    }

    /**
     * 将注销请求转发到cas server
     */
    @Bean
    public LogoutFilter logoutFilter() {
        LogoutFilter logoutFilter = new LogoutFilter(casProperties.getServerLogoutUrl(), new SecurityContextLogoutHandler());
        // 设置客户端注销请求的路径
        logoutFilter.setFilterProcessesUrl(casProperties.getServerLogoutUrl());
        return logoutFilter;
    }
}
CasProperties cas的配置文件:
package com.rxsk.cas.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "cas")
public class CasProperties {

    private String serverUrlPrefix;

    private String serverLoginUrl;

    private String serverLogoutUrl;

    private String filterUrlPattern;

    private String clientUrlPrefix;

    private String clientLoginUrl;

    private String ossLoginUserAccount;

    private String ossLoginUserPassword;

    private String[] ignoredUrl;

    private String jwtTokenHead;

    private String jwtSecretKey;

    private Long jwtExpired;

    private String jwtTokenPrefix;


    public String getServerUrlPrefix() {
        return serverUrlPrefix;
    }

    public void setServerUrlPrefix(String serverUrlPrefix) {
        this.serverUrlPrefix = serverUrlPrefix;
    }

    public String getServerLoginUrl() {
        return serverLoginUrl;
    }

    public void setServerLoginUrl(String serverLoginUrl) {
        this.serverLoginUrl = serverLoginUrl;
    }

    public String getServerLogoutUrl() {
        return serverLogoutUrl;
    }

    public void setServerLogoutUrl(String serverLogoutUrl) {
        this.serverLogoutUrl = serverLogoutUrl;
    }

    public String getFilterUrlPattern() {
        return filterUrlPattern;
    }

    public void setFilterUrlPattern(String filterUrlPattern) {
        this.filterUrlPattern = filterUrlPattern;
    }

    public String getClientUrlPrefix() {
        return clientUrlPrefix;
    }

    public void setClientUrlPrefix(String clientUrlPrefix) {
        this.clientUrlPrefix = clientUrlPrefix;
    }

    public String getClientLoginUrl() {
        return clientLoginUrl;
    }

    public void setClientLoginUrl(String clientLoginUrl) {
        this.clientLoginUrl = clientLoginUrl;
    }

    public String getOssLoginUserAccount() {
        return ossLoginUserAccount;
    }

    public void setOssLoginUserAccount(String ossLoginUserAccount) {
        this.ossLoginUserAccount = ossLoginUserAccount;
    }

    public String getOssLoginUserPassword() {
        return ossLoginUserPassword;
    }

    public void setOssLoginUserPassword(String ossLoginUserPassword) {
        this.ossLoginUserPassword = ossLoginUserPassword;
    }

    public String getJwtSecretKey() {
        return jwtSecretKey;
    }

    public void setJwtSecretKey(String jwtSecretKey) {
        this.jwtSecretKey = jwtSecretKey;
    }

    public Long getJwtExpired() {
        return jwtExpired;
    }

    public void setJwtExpired(Long jwtExpired) {
        this.jwtExpired = jwtExpired;
    }

    public String getJwtTokenPrefix() {
        return jwtTokenPrefix;
    }

    public void setJwtTokenPrefix(String jwtTokenPrefix) {
        this.jwtTokenPrefix = jwtTokenPrefix;
    }

    public String getJwtTokenHead() {
        return jwtTokenHead;
    }

    public void setJwtTokenHead(String jwtTokenHead) {
        this.jwtTokenHead = jwtTokenHead;
    }

    public String[] getIgnoredUrl() {
        return ignoredUrl;
    }

    public void setIgnoredUrl(String[] ignoredUrl) {
        this.ignoredUrl = ignoredUrl;
    }
}

最终将此项目打包成jar并推送到你的私服中,在需要cas集成的业务系统pom.xml 中加入相关的jar

<dependency>
            <groupId>com.rxsk.cas</groupId>
            <artifactId>cas-spring-security-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

在你的配置文件中中加入cas相关配置

# oss单点登录相关配置
cas:
  server-url-prefix: http://10.100.3.8:8080/cas
  server-login-url: http://10.100.3.8:8080/cas/login
  server-logout-url: http://10.100.3.8:8080/cas/logout
  filter-url-pattern:
  client-url-prefix: http://10.100.12.44:8085/passport
  ignored-url: /login/ticket-login
  client-login-url: ${cas.client-url-prefix}
  oss-login-user-account: admin
  oss-login-user-password: 123456
  jwt-secret-key: 12345678
  jwt-expired: 2592000
  jwt-token-prefix: Bearer
  jwt-token-head: Authorization

此时你的系统就已经初步具备了单点登录的能力,第一次访问所有需要授权认证的接口都将被重定向到server-login-url: http://10.100.3.8:8080/cas/login 这个地址去做统一认证登录,登录成功后会回调到 client-url-prefix: http://10.100.12.44:8085/passport 这个地址,具体如下http://10.100.12.44:8085/passport?ticket=ST-28-JO1sEEDlNDDcr7fyAxdyeoLMips-DESKTOP-H2TVDRJ

这里会获取到一个 ticket,然后前端在这个页面获取到这个ticket并调用业务系统的 ignored-url: /login/ticket-login 这个接口去验证ticket是否正确,调这个接口需要传递service这个参数,这个参数必须跟 client-url-prefix: http://10.100.12.44:8085/passport 这个参数保持一致,否则验证不通过

最终验证通过后在业务系统中生成一个token并返回给前端,前端拿着这个token就可以访问所有经过改造后统一了token生成规则的所有业务系统了

这里要补充的是有些接口是需要相关权限才能访问的,因此业务系统需要实现 

UserDetailsPlusService 这个接口的 loadUserByUserId(Long userId) 这个接口并通过查数据库获取用户权限
 @Override
    public UserDetailPlus loadUserByUserId(Long userId) throws UsernameNotFoundException {
        if (userId == null) {
            throw new BusinessException("userId不能空");
        }
        SysUserDO sysUserDO = sysUserMapper.selectByUserIdAndStatus(userId, null);

        if (Objects.isNull(sysUserDO)) {
            throw new BusinessException("用户不存在");
        }

        if(StringUtils.isBlank(sysUserDO.getAccount())){
            sysUserDO.setAccount("none");
        }

        Long parkId = 0L;

        if(sysUserDO.getParkId() != null){

            parkId = sysUserDO.getParkId();

        }else if(sysUserDO.getDefaultParkId() != null){

            parkId = sysUserDO.getDefaultParkId();

        }

        String[] userResource = getUserResource(sysUserDO.getUserId());

        return new UserDetailPlus(sysUserDO.getUserId(), parkId, sysUserDO.getUsername(), sysUserDO.getAccount(),
                sysUserDO.getPassword(), true, true, true, true,
                AuthorityUtils.createAuthorityList(userResource));
    }

这里的userId可以通过解析之前返回的token获取到,因此在调 /login/ticket-login 这个接口生成token的时候需要把用户id设置到token中,我们用的是jwt因此可以方便的把用户id保存到jwt的claims中,解析的时候可以拿到用户id

业务系统需要定义 /login/ticket-login 这个接口来验证ticket与返回token

@RequestMapping("/ticket-login")
public Response<TicketLoginRespVO> ticketLogin(@RequestBody TicketLoginReqVO req){

    TicketLoginRespVO ticketLoginRespVO = new TicketLoginRespVO();

    TicketValidator ticketValidator = new Cas20ProxyTicketValidator(casProperties.getServerUrlPrefix());
    Assertion casAssertion = null;
    try {
        casAssertion = ticketValidator.validate(req.getTicket(), req.getService());
    } catch (TicketValidationException e) {
        e.printStackTrace();
        log.error("票据校验异常", e);
    }

    AttributePrincipal casPrincipal = casAssertion.getPrincipal();

    SysUserDO sysUserDO = loginService.loginByAccountPassword(casProperties.getOssLoginUserAccount(),
            casProperties.getOssLoginUserPassword());

    //生成token,记录到redis
    LoginRespVO loginRespVO = super.createTokenVO(sysUserDO, true);

    ticketLoginRespVO.setToken(loginRespVO.getToken());

    return Response.builder(ticketLoginRespVO);
}

cas-spring-security-boot-starter 已提交到码云,有需要的小伙伴可以找我要或者在之前的文章中有相关的链接地址

有问题可加微信 

补充一句,加微信别老是您您您的,都是打工人,不必这么客气,我也才18啊哈哈

请大家关注下博客谢谢 请大家关注下博客谢谢 请大家关注下博客谢谢 重要的事情说三遍

  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Security可以与CAS(Central Authentication Service)集成实现单点登录(SSO)功能。CAS是一个开源的单点登录协议,它通过一个中心认证服务系统来管理用户的登录状态。在CAS系统中,有CAS Server和CAS Client两部分。 要使用Spring Security实现CAS单点登录,需要进行以下步骤: 1. 添加CAS依赖:在项目的构建文件中添加CAS相关的依赖,例如Maven的pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-cas</artifactId> <version>5.4.2</version> </dependency> ``` 2. 配置CAS Server和CAS Client:在Spring Security的配置文件中,配置CAS Server和CAS Client的相关信息。例如,在application.properties文件中添加以下配置: ```properties # CAS Server配置 cas.server.url.prefix=https://cas.example.com:8443/cas cas.server.login.url=https://cas.example.com:8443/cas/login cas.server.logout.url=https://cas.example.com:8443/cas/logout # CAS Client配置 cas.client.server-url-prefix=https://your-application.com cas.client.service-url=https://your-application.com/login/cas cas.client.logout-url=https://your-application.com/logout # 其他Spring Security配置 spring.security.user.name=user spring.security.user.password=password spring.security.user.roles=USER ``` 3. 配置Spring Security过滤器链:在Spring Security的配置类中,配置CAS认证过滤器链。例如,在SecurityConfig.java文件中添加以下配置: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/login").permitAll() .anyRequest().authenticated() .and() .logout() .logoutSuccessUrl("/") .and() .addFilter(casAuthenticationFilter()); } @Bean public CasAuthenticationFilter casAuthenticationFilter() throws Exception { CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter(); casAuthenticationFilter.setAuthenticationManager(authenticationManager()); return casAuthenticationFilter; } // 其他配置... } ``` 4. 创建登录和注销页面:在应用程序中创建登录和注销页面,以便用户进行CAS单点登录和注销操作。 通过以上步骤,就可以使用Spring Security实现CAS单点登录了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值