springboot+springSecurity+springSessionDataRedis+CAS搭建集群单点登陆系统

本文介绍如何使用SpringBoot结合Spring Security、Spring Session Data Redis和CAS搭建集群环境下的单点登录系统。主要内容包括配置CAS服务、集成Spring Security、使用Redis作为HTTP Session存储以实现会话共享及通过Nginx实现负载均衡。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

springboot+springSecurity+springSessionDataRedis+CAS搭建集群单点登陆系统

环境描述

windows 7 64bit
spring boot:1.4.0.RELEASE
spring security:4.1.0.RELEASE
CAS:4.1.3
nginx:随便

搭建CAS服务

1.Tomcat配置SSL
1.生成证书

以命令方式进入目录%TOMCAT_HOME%,输入如下命令:

keytool -genkey -alias tomcat_key -keyalg RSA -storepass changeit -keystore server.keystore -validity 3600 

注意: -storepass 与 -validity 可以根据需要更改

用户名输入域名,如:test.cas.com,其它全部以Enter跳过,最后确认,此时会在%TOMCAT_HOME%下生成server.keystore文件

2.将证书导入到客户端使用的JDK的证书信任库中

这步对于Tomcat的SSL配置不是必须,但对于CAS SSO是必须的,否则会出现如下错误: 
edu.yale.its.tp.cas.client.CASAuthenticationException: Unable to validate ProxyTicketValidate.

导入过程分2步,第一步是导出证书,第二步是导入到证书信任库,命令如下:

keytool -export -trustcacerts -alias tomcat_key -file server.cer -keystore server.keystore -storepass changeit 

keytool -import -trustcacerts -alias tomcat_key -file server.cer -keystore D:/”Program Files”/Java/jdk1.8.0_60/jre/lib/security/cacerts -storepass changeit

其它有用keytool命令(列出信任证书库中所有已有证书,删除库中某个证书):

keytool -list -keystore %JAVA_HOME%/jre/lib/security/cacerts >t.txt 
keytool -delete -trustcacerts -alias tomcat_key -keystore %JAVA_HOME%/jre/lib/security/cacerts -storepass changeit 
3.tomcat启用ssl

配置server.xml:

<Connector
       protocol="org.apache.coyote.http11.Http11NioProtocol"
       port="9443" maxThreads="200"
       scheme="https" secure="true" SSLEnabled="true"
       keystoreFile="server.keystore" keystorePass="changeit"
       clientAuth="false" sslProtocol="TLS"/>
2.CAS服务的配置
1.部署CAS web server:
  1. https://github.com/apereo 下载CAS server对应的版本源码
  2. 解压,使用maven命令mvn package对cas进行编译打包,这里可能会出现2个问题:

      a.单元测试不通过==》尝试加上-Dmaven.test.skip=true跳过;

      b.编译到一半编译不过==》进入cas-server-core,执行命令mvn install -Dmaven.test.skip=true先将core编译为jar包后,再进行。

3.将打包好的cas-server-webapp下的war包复制到tomcat下,运行tomcat先试试吧; 
注意:如果觉得麻烦,可以上网搜索cas-server-webapp,使用别人打好的war包

4.将war包放入tomcat的webapps目录下即可

2.从数据库获取用户信息

1.修改%tomcathome%\webapps\cas\WEB-INF中deployerConfigContext.xml:

 <constructor-arg>
        <map>
            <!--
               | IMPORTANT
               | Every handler requires a unique name.
               | If more than one instance of the same handler class is configured, you must explicitly
               | set its name to something other than its default name (typically the simple class name).
               -->
            <!--<entry key-ref="proxyAuthenticationHandler" value-ref="proxyPrincipalResolver" />-->
            <!--<entry key-ref="primaryAuthenticationHandler" value-ref="primaryPrincipalResolver" />-->
            <entry key-ref="dbAuthHandler" value-ref="primaryPrincipalResolver"/>
        </map>
    </constructor-arg>

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"  
  p:driverClass="com.mysql.jdbc.Driver"  
  p:jdbcUrl="jdbc:mysql://127.0.0.1:3306/demo2?characterEncoding=utf8"  
  p:user="root"  
  p:password="system" />  

<bean id="passwordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder"  
  c:encodingAlgorithm="MD5"  
  p:characterEncoding="UTF-8" />  

<bean id="dbAuthHandler"  
  class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler"  
  p:dataSource-ref="dataSource"  
  p:sql="select password_hash from jhi_user where login = ?"  
  p:passwordEncoder-ref="passwordEncoder"/>

注意:配置中使用mysql数据库,c3p0连接池,需要引入相关的jar包

将c3p0-0.9.1.1.jar,cas-server-support-jdbc-4.1.3.jar,mysql-connector-java-5.1.32.jar放入%tomcathome%\webapps\cas\WEB-INF\lib中

2.修改%tomcathome%\webapps\cas\WEB-INF\classes\services\Apereo-10000002.json

"serviceId" : "^http.*",
3.设置ST(service ticket)过期策略

修改%tomcathome%\webapps\cas\WEB-INF\spring-configuration\ticketExpirationPolicies.xml

<bean id="serviceTicketExpirationPolicy"
class="org.jasig.cas.ticket.support.MultiTimeUseOrTimeoutExpirationPolicy"
c:numberOfUses="10" c:timeToKill="${st.timeToKillInSeconds:1000}" c:timeUnit-ref="SECONDS"/>

注意:根据需要进行修改

至此,所需配置完成,浏览器:http://test.cas.com/cas,进行登陆测试! 
注意:test.cas.com为CAS 服务所在机器的域名(如无进行域名配置,可以修改本机hosts,127.0.0.1 test.cas.com达到测试效果)

整合springSecurity与CAS

1.在springboot项目中引入依赖:
    <!-- security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-data</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-cas</artifactId>
    </dependency>
2.配置类
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Inject
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(casAuthenticationProvider());
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
            .antMatchers(HttpMethod.OPTIONS, "/**")
            .antMatchers("/app/**/*.{js,html}")
            .antMatchers("/bower_components/**")
            .antMatchers("/i18n/**")
            .antMatchers("/content/**")
            .antMatchers("/test/**")
            ;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new StandardPasswordEncoder("tnappsk1234");
    }

    @Bean
    public ServiceProperties serviceProperties() {
        ServiceProperties sp = new ServiceProperties();
        //从cas server获取st后重定向的url,casAuthenticationFilter进行捕获,默认以/login/cas结尾
        String fstr = "http://test.tn.com/login/cas";
        sp.setService(fstr);
        sp.setSendRenew(false);
        return sp;
    }

    @Bean
    public CasAuthenticationEntryPoint casAuthenticationEntryPoint(){
        CasAuthenticationEntryPoint ep = new CasAuthenticationEntryPoint();
        ep.setServiceProperties(serviceProperties());
        //cas server 登陆路径
        ep.setLoginUrl("https://test.cas.com:9443/cas/login");
        return ep;
    }

    @Bean
    public CasAuthenticationProvider casAuthenticationProvider(){
        CasAuthenticationProvider pro = new CasAuthenticationProvider();
        pro.setServiceProperties(serviceProperties());
        pro.setAuthenticationUserDetailsService(customUserDetailsService());
        pro.setTicketValidator(cas20ServiceTicketValidator());
        pro.setKey("an_id_for_this_auth_provider_only");
        return pro;
    }

    @Bean
    public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
        //cas server路径
        return new Cas20ServiceTicketValidator("https://test.cas.com:9443/cas");
    }

    @Bean
    public AuthenticationUserDetailsService<CasAssertionAuthenticationToken> customUserDetailsService() {
        Set<String> admins = new HashSet<String>();
        String adminUserName = "admin";

        admins.add("admin");
        if (adminUserName != null && !adminUserName.isEmpty()) {
            admins.add(adminUserName);
        }
        return new CustomUserDetailsService(admins);
    }

    @Bean
    public CasAuthenticationFilter casAuthenticationFilter() throws Exception{
        CasAuthenticationFilter af = new CasAuthenticationFilter();
        af.setSessionAuthenticationStrategy(sessionStrategy());
        af.setAuthenticationManager(authenticationManager());
        return af;
    }

    @Bean
    public SessionAuthenticationStrategy sessionStrategy() {
        SessionAuthenticationStrategy sessionStrategy = new SessionFixationProtectionStrategy();
        return sessionStrategy;
    }

    @Bean
    public SingleSignOutFilter singleSignOutFilter() {
        SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
        singleSignOutFilter.setIgnoreInitConfiguration(true);
        singleSignOutFilter.setCasServerUrlPrefix("https://test.cas.com:9443/cas");
        return singleSignOutFilter;
    }

    @Bean
    public LogoutFilter requestCasGlobalLogoutFilter() {
        LogoutFilter logoutFilter = new LogoutFilter("https://test.cas.com:9443/cas/logout?service=http://test.tn.com", new SecurityContextLogoutHandler());
        logoutFilter.setFilterProcessesUrl("/api/logout");
        logoutFilter.setLogoutRequestMatcher(new AntPathRequestMatcher("/api/logout", "POST"));
        return logoutFilter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling().authenticationEntryPoint(casAuthenticationEntryPoint())
            .and().addFilter(casAuthenticationFilter())
            .addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class)
            .addFilterBefore(requestCasGlobalLogoutFilter(), LogoutFilter.class);

        http
        .csrf().disable()
            .headers()
            .frameOptions()
            .disable()
        .and()
            .authorizeRequests()
            .antMatchers("/api/register").denyAll()
            .antMatchers("/api/activate").permitAll()
            .antMatchers("/api/content-items/category").permitAll()
            .antMatchers("/api/authenticate").permitAll()
            .antMatchers("/api/account/reset_password/init").permitAll()
            .antMatchers("/api/account/reset_password/finish").permitAll()
            .antMatchers("/api/profile-info").permitAll()
            .antMatchers("/api/**").authenticated()
            .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
            .antMatchers("/v2/api-docs/**").permitAll()
            .antMatchers("/swagger-resources/configuration/ui").permitAll()
            .antMatchers("/swagger-ui/index.html").hasAuthority(AuthoritiesConstants.ADMIN);
    }

    @Bean
    public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
        return new SecurityEvaluationContextExtension();
    }
}

注意:密码passwordEncoder(),需要配置成与cas一致,使用MD5,这里配置有误

主要:CasAuthenticationFilter,CasAuthenticationEntryPoint,CasAuthenticationProvider的配置, 
具体可以参考Spring Security官方文档CAS Authentication部分:

http://docs.spring.io/spring-security/site/docs/4.2.0.RELEASE/reference/htmlsingle/#cas

使用redis做http session共享

使用redis做http session的缓存,这样当从cas server 重定向回某类应用的service url(下有多个机子组成集群)时,可以指定同一个域名,这样session就实现了共享。

1.引入依赖
<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-redis</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-data-redis</artifactId>
</dependency>
2.配置类
@Configuration
@EnableRedisHttpSession
public class RedisSessionConfig {
    /**
     * 修改数据在redis中的存储方式,方便调试
     * @Title: sessionRedisTemplate 
     * @Description: TODO
     * @param connectionFactory
     * @return
     * @return: RedisTemplate<Object,Object>
     */
    @Bean
    public RedisTemplate<Object, Object> sessionRedisTemplate(
            RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
        template.setKeySerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());
        template.setConnectionFactory(connectionFactory);
        return template;
    }
}

使用nginx做负载均衡

修改nginx.conf,负载均衡采用轮询的策略:
http{ 
  upstream tn { 
    server test.tn.com:6900; 
    server test.tn.com:6901; 
  } 

  server{ 
    listen 80; 
    location / { 
     proxy_pass http://tn; 
    } 
 }
修改hosts
127.0.0.1 test.tn.com  //webapp
127.0.0.1 test.cas.com //cas server

start nginx启动

浏览器中,访问http://test.tn.com/xxx/xxx(受管控方法),跳转到Cas Server,登陆验证身份后,重定向回之前的url,不断刷新,通过观察日子,请求分发到不同的webapp

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值