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