spring security integrates cas to achieve single sign on

Recently, I took a task. The company made a lot of systems for customers, and later made a general business system for external sales. Therefore, we need to make a demonstration system to put all business systems into the demonstration system. Users can access any business system after logging in to the demonstration system. This sounds like a single sign on requirement, So I went and found CAS,

CAS is the abbreviation of Central Authentication Service. Originally developed by Shawn Bayern of Yale University and later maintained by Jasig community, after more than ten years of development, it has become the most influential, widely used, Java based and open source SSO solution.

First of all, we need to explain the meaning and basic process of oss

Meaning: there are countless business systems, which have their own users, roles, permissions, etc. to do oss now, we need to unify the users of all business systems to the user center, remove the login of the business system, and all logins go to the user center. After the user center is successfully logged in, other business systems do not need to log in again

Process: I found a process on the Internet. The explanation is very clear. You can have a look https://www.cnblogs.com/Eleven-Liu/p/10336181.html

Therefore, if we want to do single sign on, we must have a unified user center. Then the problem comes. We must have many old systems. We can't unify users for historical reasons. Perhaps the query data is coupled with users, or it is not developed with familiar technologies. This is the situation I am facing now. It takes time and effort to transform the old system, It is decided that the construction of the user center shall be carried out synchronously with the transformation of the business system. Before the user center is built, a unified user shall be established in each system. When logging in uniformly, first log in in the user center and then return to the front page. Obtain a token from the business system according to the ticket returned by the user center, and then access each business system according to the token, Finally, gradually integrate the users of various business systems into the user center, because most of our business systems used spring security for permission verification, so this oss is also done on the basis of spring security, which can be compatible with the relevant interface permissions in the previous system. No more nonsense, go straight to the code

First of all, we need to build a cas server. The relevant code can be downloaded from the official website of cas and started directly under tomcat. However, this method is not recommended. There is another way of overlay, that is, cas War is put into your custom cas server project in the form of overlay, and then you can implement a variety of custom configurations in your cas server. There are many online posts here, so I won't repeat them

First, build a spring boot project of CAS spring security boot starter. The specific directory is as follows

 

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 yes CAS support -->
        <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 tool kit-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.6.2</version>
        </dependency>
    </dependencies>

    <distributionManagement>
        <repository>
            <id>Your private server id1</id>
            <name>Your private server name 1</name>
            <url>http://Your private server address / repository / Maven releases / < / url >
        </repository>
        <snapshotRepository>
            <id>Your private server id2</id>
            <name>Your private server name 2</name>
            <url>http://Your private server address / repository / Maven snapshots / < / url >
        </snapshotRepository>
    </distributionManagement>

</project>

Spring security cas is used to integrate cas, and jjwt is used to generate user token s

CasSecurityConfig security and cas configuration classes
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);
    }

    /**
     * Configure properties of CAS Client
     */
    @Bean
    public ServiceProperties serviceProperties() {
        ServiceProperties serviceProperties = new ServiceProperties();

        // Consistent with the URL monitored by CasAuthenticationFilter
        serviceProperties.setService(casProperties.getClientLoginUrl());
        //serviceProperties.setServiceParameter(casProperties.getFilterUrlPattern());
        // Whether to turn off single sign on is false by default, so it can not be set.
        serviceProperties.setSendRenew(false);
        return serviceProperties;
    }

    /**
     * CAS Authentication portal, which provides the redirection address of the user's browser
     */
    @Bean
    @Primary
    public AuthenticationEntryPoint authenticationEntryPoint() {
        CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint();
        // Login address of CAS Server authentication
        entryPoint.setLoginUrl(casProperties.getServerLoginUrl());
        entryPoint.setServiceProperties(serviceProperties());
        return entryPoint;
    }

    /**
     * ticket For verification, the address of CAS Server verification ticket needs to be provided
     */
    @Bean
    public TicketValidator ticketValidator() {
        // By default, Cas20ProxyTicketValidator is used, and the validation entry is ${casServerPrefix}/proxyValidate
        return new Cas20ProxyTicketValidator(casProperties.getServerUrlPrefix());
    }


    /**
     * cas Authentication processing logic
     */
    @Bean
    public CasAuthenticationProvider casAuthenticationProvider() {
        CasAuthenticationProvider provider = new CasAuthenticationProvider();
        provider.setServiceProperties(serviceProperties());
        provider.setTicketValidator(ticketValidator());
        provider.setUserDetailsService(userDetailsPlusService);
        provider.setKey("blurooo");
        return provider;
    }


    /**
     * A special filter for CAS authentication is provided, and the authentication logic of the filter is provided by CasAuthenticationProvider
     */
    @Bean
    public CasAuthenticationFilter casAuthenticationFilter() {
        CasAuthenticationFilter filter = new CasAuthenticationFilter();
        filter.setServiceProperties(serviceProperties());
        filter.setAuthenticationManager(new ProviderManager(Arrays.asList(casAuthenticationProvider())));
        return filter;
    }

    /**
     * Accept the logoff request sent by cas server
     */
    @Bean
    public SingleSignOutFilter singleSignOutFilter() {
        SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
        singleSignOutFilter.setCasServerUrlPrefix(casProperties.getServerUrlPrefix());
        singleSignOutFilter.setIgnoreInitConfiguration(true);
        return singleSignOutFilter;
    }

    /**
     * Forward logout request to cas server
     */
    @Bean
    public LogoutFilter logoutFilter() {
        LogoutFilter logoutFilter = new LogoutFilter(casProperties.getServerLogoutUrl(), new SecurityContextLogoutHandler());
        // Set the path of the client logout request
        logoutFilter.setFilterProcessesUrl(casProperties.getServerLogoutUrl());
        return logoutFilter;
    }
}
Configuration file of 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;
    }
}

Finally, the project will be packaged into jars and pushed to your private server. POM. Com, a business system that needs cas integration Add relevant jars to XML

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

Add cas related configuration in your configuration file

# oss single sign on related configurations
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

At this time, your system has initially possessed the ability of single sign on. The first access to all interfaces requiring authorization and authentication will be redirected to server login URL: http://10.100.3.8:8080/cas/login This address is used for unified authentication login. After successful login, it will call back to {client URL prefix: http://10.100.12.44:8085/passport This address is as follows http://10.100.12.44:8085/passport?ticket=ST-28-JO1sEEDlNDDcr7fyAxdyeoLMips-DESKTOP-H2TVDRJ

Here, you will get a ticket, and then the front end will get the ticket on this page and call the business system's "ignored URL: / login / ticket login" interface to verify whether the ticket is correct. To call this interface, you need to pass the parameter "service", which must be followed by "client URL prefix:" http://10.100.12.44:8085/passport This parameter is consistent, otherwise the verification fails

After the final verification, a token is generated in the business system and returned to the front end. With this token, the front end can access all business systems that have unified the token generation rules after transformation

It should be added here that some interfaces can be accessed only with relevant permissions, so the business system needs to be implemented

The loadUserByUserId(Long userId) interface of the UserDetailsPlusService interface and obtain user permissions by querying the database
 @Override
    public UserDetailPlus loadUserByUserId(Long userId) throws UsernameNotFoundException {
        if (userId == null) {
            throw new BusinessException("userId Cannot be empty");
        }
        SysUserDO sysUserDO = sysUserMapper.selectByUserIdAndStatus(userId, null);

        if (Objects.isNull(sysUserDO)) {
            throw new BusinessException("user does not exist");
        }

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

The userId here can be obtained by parsing the returned token. Therefore, when calling the / login / ticket login interface to generate a token, we need to set the user id to the token. We use jwt, so we can easily save the user id to jwt claims and get the user id during parsing

The business system needs to define the / login / ticket login interface to verify the ticket and return the 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("bill verification exception", e);
    }

    AttributePrincipal casPrincipal = casAssertion.getPrincipal();

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

    //Generate a token and record it to redis
    LoginRespVO loginRespVO = super.createTokenVO(sysUserDO, true);

    ticketLoginRespVO.setToken(loginRespVO.getToken());

    return Response.builder(ticketLoginRespVO);
}

CAS spring security boot starter has been submitted to the code cloud. If you need it, you can find me or have the relevant link address in the previous article

You can add wechat if you have questions

In addition, wechat doesn't always belong to you. They are all migrant workers. Don't be so polite. I'm only 18, ha ha

Please pay attention to the blog. Thank you. Please pay attention to the blog. Thank you. Please pay attention to the blog. Thank you for saying important things three times

 

Keywords: Java Maven Spring

Added by Jabop on Sun, 19 Dec 2021 03:12:18 +0200