spring security+cas 单点登录示例(单点退出)

版本说明

6.0及以上需要jdk11,如果你是jdk8,最高只能用5.3版本

5.3以下版本的是maven工程,6.0以上改成gradle工程了

这里基于5.3版本搭建

原理

spring security+cas 单点登录示例(单点退出)_第1张图片

一、服务端搭建

1、下载源码

https://github.com/apereo/cas-overlay-template/tree/5.3

下载官方的cas-overlay-template,在此基础上修改打包,运行。
cas-overlay-template已经集成了springboot,可直接运行。

build之后会在target目录生成一个cas.war文件

编译过程需要下载很多文件,过程有点长,需要耐心等待
如果本机maven环境变量没配置,用idea打开工程编译也可以

把war包放在tomcat中启动

默认用户名和密码在cas\WEB-INF\classes\application.properties中
用户名:casuser 密码:Mellon

CAS默认使用的是HTTPS协议,如果使用HTTPS协议需要SSL安全证书(需向特定的机构申请和购买) 。如果对安全要求不高或是在开发测试阶段,可使用HTTP协议。我们这里讲解通过修改配置,让CAS使用HTTP协议。
如果不去除https认证下面整合客户端时会出现未认证授权的服务

2、去掉https配置

使用http链接跳转到cas服务器默认会有未认证授权的服务提示
未认证授权的服务
1)修改CAS服务端配置文件
cas\WEB-INF\classes目录的application,properties添加如下的内容

cas.tgc.secure=false
cas.serviceRegistry.initFromJson=true

2)cas\WEB-INF\classes\services目录下的HTTPSandIMAPS-10000001.json
修改内容如下,即添加http
"serviceId" : "^(https|http|imaps)://.*",

3)重启tomcat

3、tomcat 配置https证书

1、生成证书

keytool -genkey -alias sso-server -keystore ./mydestore -keyalg RSA -validity 2000 -storepass 111111 -keypass 111111
# 输入:sso-server.com

keytool -export -alias sso-server -keystore ./mydestore -file server.crt -keypass 111111
# 将证书导入到jdk
keytool -import -alias sso-server -file ./server.crt -keystore "C:/Program Files/Java/jdk1.8.0_161/jre/lib/security/cacerts" -storepass changeit -keypass 111111
# jdk默认密码:changeit

2、配置tomcat

把证书文件mydestore复制到conf目录下

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
         maxThreads="150" SSLEnabled="true" 
         scheme="https"  secure="true" clientAuth="false" sslProtocol="TLS"
         keystoreFile="conf/mydestore" keystorePass="111111"
         >
Connector>

修改本地host文件:

127.0.0.1 sso-server.com

重启tomcat,输入访问地址:https://sso-server.com:8443/cas

4、修改cas-server链接到mysql数据库

1)服务端工程引入jar包,重新编译生成cas.war

<dependency>
    <groupId>org.apereo.casgroupId>
    <artifactId>cas-server-support-jdbcartifactId>
    <version>${cas.version}version>
dependency>

<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>8.0.13version>
dependency>

2)修改配置文件:
cas/WEB-INF/classes/application.properties

##
# CAS Authentication Credentials
#
# cas.authn.accept.users=casuser::Mellon

cas.authn.jdbc.query[0].url=jdbc:mysql://127.0.0.1:3306/user?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true
cas.authn.jdbc.query[0].user=root
cas.authn.jdbc.query[0].password=password
cas.authn.jdbc.query[0].sql=select * from u_user where nickname = ?
cas.authn.jdbc.query[0].fieldPassword=pswd
cas.authn.jdbc.query[0].driverClass=com.mysql.cj.jdbc.Driver

## md5加密
cas.authn.jdbc.query[0].passwordEncoder.type=DEFAULT
cas.authn.jdbc.query[0].passwordEncoder.characterEncoding=UTF-8
cas.authn.jdbc.query[0].passwordEncoder.encodingAlgorithm=MD5

#配置单点登出
#配置允许登出后跳转到指定页面
cas.logout.followServiceRedirects=true
#跳转到指定页面需要的参数名为 service
cas.logout.redirectParameter=service
#登出后需要跳转到的地址,如果配置该参数,service将无效。
# cas.logout.redirectUrl=http://sso-server.com:8088/index
#在退出时是否需要 确认退出提示   true弹出确认提示框  false直接退出
cas.logout.confirmLogout=true
#是否移除子系统的票据
cas.logout.removeDescendantTickets=true
#禁用单点登出,默认是false不禁止
#cas.slo.disabled=true
#默认异步通知客户端,清除session
#cas.slo.asynchronous=true

3)测试数据库脚本

CREATE TABLE `u_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`nickname` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户昵称',
`email` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '邮箱|登录帐号',
`pswd` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密码',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`last_login_time` datetime(0) NULL DEFAULT NULL COMMENT '最后登录时间'
`status` bigint(1) NULL DEFAULT 1 COMMENT '1:有效,0:禁止登录',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;


INSERT INTO `u_user` VALUES (1, 'admin', '[email protected]', md5('1'), NOW(), NULL, 1);

4)将war包copy到tomcat运行

二、客户端配置,配置cas-client(集成spring-security)

客户端就是自己的项目工程

1、引入jar包

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
    <groupId>org.springframework.securitygroupId>
    <artifactId>spring-security-casartifactId>
dependency>

2、添加配置项

#cas服务端的地址
cas.server-url-prefix=http://localhost:8080/cas
#cas服务端的登录地址
cas.server-login-url=http://localhost:8080/cas/login
#当前服务器的地址(客户端)
cas.client-host-url=http://localhost:8088

3、代码示例

启动类添加注解

@SpringBootApplication
//开启spring security过滤器链
@EnableWebSecurity
public class CasDemoClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(CasDemoClientApplication.class, args);
    }
}

Cas配置类

package com.example.casclient.config;

import com.example.casclient.security.CustomUserDetailsService;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.validation.Cas30ServiceTicketValidator;
import org.jasig.cas.client.validation.TicketValidator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;

@Configuration
public class CasConfig {

    @Value("${cas.server-url-prefix}")
    private String casServerUrl;
    @Value("${cas.client-host-url}")
    private String baseUrl;

    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint();
        entryPoint.setLoginUrl(casServerUrl + "/login");
        entryPoint.setServiceProperties(this.serviceProperties());
        return entryPoint;
    }

    @Bean
    protected AuthenticationManager authenticationManager() throws Exception {
        return new ProviderManager(this.casAuthenticationProvider());
    }

    @Bean
    public CasAuthenticationFilter casAuthenticationFilter(
            AuthenticationManager authenticationManager,
            ServiceProperties serviceProperties) throws Exception {
        CasAuthenticationFilter filter = new CasAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManager);
        filter.setServiceProperties(serviceProperties);
        return filter;
    }

    @Bean
    public ServiceProperties serviceProperties() {
        ServiceProperties serviceProperties = new ServiceProperties();
        serviceProperties.setService(baseUrl + "/login/cas");
        serviceProperties.setSendRenew(false);
        return serviceProperties;
    }

    @Bean
    public TicketValidator ticketValidator() {
        return new Cas30ServiceTicketValidator(casServerUrl);
    }

    @Bean
    public CasAuthenticationProvider casAuthenticationProvider() {
        CasAuthenticationProvider provider = new CasAuthenticationProvider();
        provider.setServiceProperties(this.serviceProperties());
        provider.setTicketValidator(this.ticketValidator());
        provider.setUserDetailsService(new CustomUserDetailsService());
        provider.setKey("CAS_PROVIDER_LOCALHOST");
        return provider;
    }

    @Bean
    public SecurityContextLogoutHandler securityContextLogoutHandler() {
        return new SecurityContextLogoutHandler();
    }

    @Bean
    public LogoutFilter logoutFilter() {
        LogoutFilter logoutFilter = new LogoutFilter(casServerUrl + "/logout?service=" + baseUrl,
                securityContextLogoutHandler());
        logoutFilter.setFilterProcessesUrl("/logout/cas");
        return logoutFilter;
    }

    @Bean
    public SingleSignOutFilter singleSignOutFilter() {
        SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
        singleSignOutFilter.setIgnoreInitConfiguration(true);
        return singleSignOutFilter;
    }
}

WebConfig配置类

package com.example.casclient.config;

import org.jasig.cas.client.session.SingleSignOutFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.cas.web.CasAuthenticationFilter;
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.logout.LogoutFilter;

@EnableWebSecurity
public class WebConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private SingleSignOutFilter singleSignOutFilter;
    @Autowired
    private LogoutFilter logoutFilter;
    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;
    @Autowired
    private CasAuthenticationFilter casAuthenticationFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login/cas").permitAll()
                .anyRequest().authenticated()
                .and().httpBasic().authenticationEntryPoint(authenticationEntryPoint)
                .and()
                .addFilter(casAuthenticationFilter)
                .addFilterBefore(singleSignOutFilter, CasAuthenticationFilter.class)
                .addFilterBefore(logoutFilter, LogoutFilter.class);
    }
}

授权后获取用户信息处理类

package com.example.casclient.security;

import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public class CustomUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 可自定义获取用户信息
        // 示例代码写死,实际应该从数据库获取用户信息,并加载对应的权限
        return new User(username, username, true, true, true, true,
                AuthorityUtils.createAuthorityList("ROLE_ADMIN"));
    }
}

添加测试Controller类

package com.example.casclient.controller;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpSession;

@Controller
public class IndexController {

    @RequestMapping(value = "")
    public String index(Model model) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        User user = (User) auth.getPrincipal();
        System.out.println("当前用户信息:" + user);
        model.addAttribute("user", user);
        return "index";
    }

    @RequestMapping(value = "info")
    public String info(Model model) {
        return "info";
    }
}

测试页面代码,templates/index.html

DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
	<meta charset="UTF-8">
	<title>首页title>
head>
<body class="home-body">
	<h1>首页信息h1>
	<div>登录用户信息div>
	<div>用户名:<span th:text="${user.username}">span>div>
body>
html>

三、测试

1)在浏览中输入地址(http://sso-server.com:8088)会跳转到cas登录页面

输入指定的用户名/密码:admin/1
spring security+cas 单点登录示例(单点退出)_第2张图片
2)登录成功跳转到页面

spring security+cas 单点登录示例(单点退出)_第3张图片
3)注销,输入地址:http://sso-server.com:8088/logout/cas,点击确认退出,页面会回到登录页面
spring security+cas 单点登录示例(单点退出)_第4张图片

或直接在cas服务端退出:https://sso-server.com:8443/cas/logout
spring security+cas 单点登录示例(单点退出)_第5张图片

参考地址

  • https://github.com/apereo/cas
  • https://github.com/apereo/cas-overlay-template
  • https://www.apereo.org/projects/cas
  • https://github.com/apereo/java-cas-client
  • https://apereo.github.io/cas/5.3.x/installation/Configuration-Properties.html#query-database-authentication

你可能感兴趣的:(框架,cas,security,单点登录)