您的位置:首页 > 编程语言 > Java开发

spring security 整合 CAS

2014-12-03 14:53 309 查看

Spring security+CAS单点登录

Spring security 版本 3.2.4

CAS Server版本 3.4.10

CAS client 版本 3.2.1

JDK 1.7

Tomcat 8.0

原理:

从结构上看,CAS 包含两个部分: CAS Server 和 CAS Client。CAS Server 需要独立部署,主要负责对用户的认证工作;CAS Client 负责处理对客户端受保护资源的访问请求,需要登录时,重定向到 CAS Server。图1 是 CAS 最基本的协议过程:

图 1. CAS 基础协议



CAS Client 与受保护的客户端应用部署在一起,以 Filter 方式保护受保护的资源。对于访问受保护资源的每个 Web 请求,CAS Client 会分析该请求的 Http 请求中是否包含 Service Ticket,如果没有,则说明当前用户尚未登录,于是将请求重定向到指定好的 CAS Server 登录地址,并传递 Service (也就是要访问的目的资源地址),以便登录成功过后转回该地址。用户在第 3 步中输入认证信息,如果登录成功,CAS Server 随机产生一个相当长度、唯一、不可伪造的 Service Ticket,并缓存以待将来验证,之后系统自动重定向到 Service 所在地址,并为客户端浏览器设置一个 Ticket Granted Cookie(TGC),CAS Client 在拿到 Service 和新产生的 Ticket 过后,在第 5,6 步中与 CAS Server 进行身份合适,以确保 Service Ticket 的合法性。

在该协议中,所有与 CAS 的交互均采用 SSL 协议,确保ST 和 TGC 的安全性。协议工作过程中会有 2 次重定向的过程,但是 CAS Client 与 CAS Server 之间进行 Ticket 验证的过程对于用户是透明的。

另外,CAS 协议中还提供了 Proxy (代理)模式,以适应更加高级、复杂的应用场景,具体介绍可以参考 CAS 官方网站上的相关文档。

配置tomcat https

本例是将CAS server 和 CAS client部署在不同的tomcat 上,首先需要为客户端和服务端的tomcat配置https协议:

SSL文件准备:

server.keystore——服务器端库文件

client.keystore——客户端库文件

server.cer——服务器端证书(自制)

client.cer——客户端证书(自制)

cacerts——证书链

1、生成服务器端库文件

keytool -genkey -v -alias server -keyalg RSA -keystore F:\server.keystore -validity 36500

(输入密码,并输入你的名字和姓氏,组织名称,地区名称等,注意在填入 你的名字和姓氏CN的时候输入localhost)

2、导出服务器端证书

keytool -export -alias server -storepass 密码 -file F:\server.cer -keystore F:\server.keystore

3、生成客户端库文件

keytool -genkey -v -alias client -keyalg RSA -keystore F:\client.keystore -validity 36500

(输入密码,并输入你的名字和姓氏,组织名称,地区名称等,注意在填入 你的名字和姓氏CN的时候输入localhost)

4、导出客户端证书

keytool -export -alias client -storepass 密码 -file F:\client.cer -keystore F:\client.keystore

5、导入服务器端证书到cacerts(cacerts是JDK下的jre的可信任证书仓库)

keytool -import -trustcacerts -alias server -file F:\server.cer -keystore ”%JAVA_HOME%\jre\lib\security\cacerts” -storepass changeit

6、

6、导入客户端证书到cacerts(cacerts是JDK下的jre的可信任证书仓库)

keytool -import -trustcacerts -alias server -file F:\server.cer -keystore ”%JAVA_HOME%\jre\lib\security\cacerts” -storepass changeit

将server.keystore、client.keystore、server.cer、client.cer、cacerts文件复制到cas服务器、cas客户机、cas客户机1的TOMCAT_HOME主目录及JAVA_HOEM\jre\lib\security目录下。

分别修改CAS server 和 CAS client的tomcat中的 conf/server.xml

修改CAS server的tomcat以下端口:

<Connector useBodyEncodingForURI="true" URIEncoding="UTF-8" connectionTimeout="20000" port="8089" protocol="HTTP/1.1" redirectPort="8443"/>

<Connector useBodyEncodingForURI="true" URIEncoding="UTF-8" SSLEnabled="true" acceptCount="100" clientAuth="false"

disableUploadTimeout="true" enableLookups="true"

keystoreFile="/server.keystore" keystorePass="wentao211()"

maxSpareThreads="75" maxThreads="200" minSpareThreads="5" port="8443"

protocol="org.apache.coyote.http11.Http11Protocol" scheme="https" secure="true"

sslProtocol="TLS"/>

<!-- Define an AJP 1.3 Connector on port 8009 -->

<Connector port="8019" protocol="AJP/1.3" redirectPort="8443"/>

为了防止和CAS client的tomcat端口冲突,我将端口修改



修改CAS client的tomcat的端口:

<Connector URIEncoding="UTF-8" connectionTimeout="20000" port="8088" protocol="HTTP/1.1" redirectPort="8445" useBodyEncodingForURI="true"/>

<Connector SSLEnabled="true" URIEncoding="UTF-8" acceptCount="100" clientAuth="false" disableUploadTimeout="true" enableLookups="true" keystoreFile="/client.keystore" keystorePass="wentao211()" maxSpareThreads="75" maxThreads="200" minSpareThreads="5" port="8445" protocol="org.apache.coyote.http11.Http11Protocol" scheme="https" secure="true" sslProtocol="TLS" useBodyEncodingForURI="true"/>

<!-- Define an AJP 1.3 Connector on port 8009 -->

<Connector port="8009" protocol="AJP/1.3" redirectPort="8445"/>

配置CAS Server

将下载的cas-server-3.4.10-release.zip 解压,把cas-server-3.4.10/modules下面的

cas-server-webapp-3.4.10.war拷贝到tomcat下的webapps下,然后启动tomcat;或者直接从myeclipse中导入war包,将其导入到myeclipse中,然后再发布到tomcat中,推荐使用后缀,因为我们需要继续对CAS server进行一些修改才能满足我们的需求:

1,我们需要使用数据源来对用户进行登陆验证,本例使用mysql作为数据库,所以需要拷贝一个mysql-connector-java-5.1.33-bin.jar和一个cas-server-support-jdbc-3.4.10.jar到lib下面,

并且需要在数据库中建立一个表,用来存放用户数据,当然表中的字段可以根据需要新增,其中的字段名也是可以修改,只是需要在配置

deployerConfigContext.xml的数据源中字段查询的时候跟其对应上



然后修改/WEB-INF/deployerConfigContext.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"

xmlns:sec="http://www.springframework.org/schema/security"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">

<bean id="authenticationManager" class="org.jasig.cas.authentication.AuthenticationManagerImpl">

<property name="credentialsToPrincipalResolvers">

<list>

<bean

class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver">

<property name="attributeRepository" ref="attributeRepository" />

</bean>

<bean

class="org.jasig.cas.authentication.principal.HttpBasedServiceCredentialsToPrincipalResolver" />

</list>

</property>

<property name="authenticationHandlers">

<list>

<bean

class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"

p:httpClient-ref="httpClient" />

<bean

class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">

<property name="dataSource" ref="dataSource" />

<property name="sql" value="select password from user where username=? and enabled=1" />

<property name="passwordEncoder" ref="passwordEncoder"></property>

</bean>

</list>

</property>

</bean>

<!-- 自定义的加密类 -->

<bean id="passwordEncoder" class="org.jasig.cas.util.MD5Encoder"></bean>

<!-- 数据源 -->

<bean id="dataSource"

class="org.springframework.jdbc.datasource.DriverManagerDataSource">

<property name="driverClassName">

<value>com.mysql.jdbc.Driver</value>

</property>

<property name="url">

<value>jdbc:mysql://127.0.0.1:3306/test</value>

</property>

<property name="username">

<value>root</value>

</property>

<property name="password">

<value>mysql</value>

</property>

</bean>

<bean id="attributeRepository"

class="org.jasig.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao">

<constructor-arg index="0" ref="dataSource" />

<constructor-arg index="1"

value="select * from user where username = ?" />

<property name="queryAttributeMapping">

<map>

<entry key="username" value="username" />

</map>

</property>

<!-- key为数据库中的字段名,value为客户端需要通过得到的名字 -->

<property name="resultAttributeMapping">

<map>

<entry key="username" value="username" />

<entry key="password" value="password" />

<entry key="enabled" value="enabled" />

<entry key="authority" value="authorities" />

</map>

</property>

</bean>

<!-- 这个userDetailsService名字不能变,因为在spring-configuration/securityContext.xml中有引用 -->

<sec:jdbc-user-service data-source-ref="dataSource" id="userDetailsService"

users-by-username-query="select username,password,enabled from user where username = ?"

authorities-by-username-query="select username,authority from user where username = ?"/>

<bean id="serviceRegistryDao" class="org.jasig.cas.services.InMemoryServiceRegistryDaoImpl">

</bean>

<bean id="auditTrailManager" class="com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager" />

</beans>

2.修改view/jsp/protocol/2.0/casServiceValidationSuccess.jsp

<%@ page session="false" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>

<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>

<cas:authenticationSuccess>

<cas:user>${fn:escapeXml(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.id)}</cas:user>

<c:if test="${not empty pgtIou}">

<cas:proxyGrantingTicket>${pgtIou}</cas:proxyGrantingTicket>

</c:if>

<c:if test="${fn:length(assertion.chainedAuthentications) > 1}">

<cas:proxies>

<c:forEach var="proxy" items="${assertion.chainedAuthentications}" varStatus="loopStatus" begin="0" end="${fn:length(assertion.chainedAuthentications)-2}" step="1">

<cas:proxy>${fn:escapeXml(proxy.principal.id)}</cas:proxy>

</c:forEach>

</cas:proxies>

</c:if>

<%-- 加入以下这段,否则客户端验证后会出现没有权限的错误 --%>

<c:if test="${fn:length(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes)> 0}">

<cas:attributes>

<c:forEach var="attr" items="${assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes}" varStatus="loopStatus" begin="0" end="${fn:length(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes)-1}" step="1">

<cas:${fn:escapeXml(attr.key)}>${fn:escapeXml(attr.value)}</cas:${fn:escapeXml(attr.key)}>

</c:forEach>

</cas:attributes>

</c:if>

</cas:authenticationSuccess>

</cas:serviceResponse>

3.在src下的org.jasig.cas.util.MD5Encoder.jar

public class MD5Encoder implements PasswordEncoder{

private static final String SALTStR = "testsalt"; //盐值

@Override

public String encode(String password) {

return new Md5PasswordEncoder().encodePassword(password, SALTStR);

}

}

4.修改spring-configuration/securityContext.xml,修改一下一段,主要是加入了密码加密类

<sec:authentication-manager alias="casAuthenticationManager">

<sec:authentication-provider ref="casAuthenticationProvider">

<sec:password-encoder ref="passwordEncoder"></sec:password-encoder>

</sec:authentication-provider>

</sec:authentication-manager>

至此,服务器端已经搭建完成,在浏览器中输入:
https://localhost:8443/cas-server-webapp-3.4.10/login就会出现以下页面,根据数据库中你插入的用户数据登陆即可


CAS client配置

客户端采用SpringMVC进行简单的配置,客户端的流程是:

点击前往登陆页面 ---> 被CAS client拦截跳转到 CAS server的登陆页面 ---> 登陆成功后返回到主操作页面

客户端结构:

1.先配置好web.xml

<?xml version="1.0" encoding="UTF-8"?>

<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<display-name></display-name>

<welcome-file-list>

<welcome-file>index.jsp</welcome-file>

</welcome-file-list>

<!-- 上下文配置文件路径 -->

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>

classpath:properties/applicationContext.xml,

classpath:properties/springmvc-security.xml

</param-value>

</context-param>

<!-- 上下文加载监听器 -->

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>

<!-- 防止session溢出 -->

<listener>

<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>

</listener>

<!-- 中文过滤器 -->

<filter>

<filter-name>characterFilter</filter-name>

<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>

<init-param>

<param-name>encoding</param-name>

<param-value>UTF-8</param-value>

</init-param>

<init-param>

<param-name>forceEncoding</param-name>

<param-value>true</param-value><!-- 强制进行转码 -->

</init-param>

</filter>

<filter-mapping>

<filter-name>characterFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

<!-- 然后接着是SpringSecurity必须的filter 优先配置,让SpringSecurity先加载,防止SpringSecurity拦截失效 -->

<filter>

<filter-name>springSecurityFilterChain</filter-name>

<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

</filter>

<filter-mapping>

<filter-name>springSecurityFilterChain</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

<!-- springmvc的servlet -->

<servlet>

<servlet-name>springmvc</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<init-param>

<param-name>contextConfigLocation</param-name>

<param-value>classpath:properties/springmvc-servlet.xml</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>springmvc</servlet-name>

<url-pattern>*.action</url-pattern>

</servlet-mapping>

</web-app>

2.配置好applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans

xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:p="http://www.springframework.org/schema/p"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<context:property-placeholder location="classpath:properties/jdbc.properties"/>

<!-- 扫描包,controller不扫描 -->

<context:component-scan base-package="com.springsecurity">

<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>

</context:component-scan>

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"

init-method="init" destroy-method="close">

<property name="url" value="${druid.url}" />

<property name="username" value="${druid.username}" />

<property name="password" value="${druid.password}" />

<property name="maxActive" value="${druid.maxActive}" />

<property name="minIdle" value="${druid.minIdle}" />

<property name="maxWait" value="${druid.maxWait}" />

<property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}" />

<property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" />

<property name="connectionProperties" value="config.decrypt=true" />

<property name="filters" value="config,stat,wall,log4j" />

</bean>

</beans>

3.配置spring-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"

xmlns:context="http://www.springframework.org/schema/context"

xmlns:mvc="http://www.springframework.org/schema/mvc"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<!-- 扫描包 -->

<context:component-scan base-package="com.springsecurity.action" />

<!-- 加载资源路径 -->

<mvc:resources location="/resources/" mapping="/resources/**" />

<mvc:annotation-driven/>

<!-- jsp页面视图处理 @author黄文韬 -->

<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">

<property name="order" value="1"></property>

<property name="defaultContentType" value="text/html" />

<property name="ignoreAcceptHeader" value="true"></property>

<property name="mediaTypes">

<map>

<entry key="json" value="application/json" />

<entry key="html" value="text/html" />

</map>

</property>

<property name="viewResolvers">

<list>

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">

<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />

<property name="prefix" value="/WEB-INF/pages/" />

<property name="suffix" value=".jsp" />

</bean>

</list>

</property>

</bean>

</beans>

4.配置spring-security.xml(*****重点*****)

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:security="http://www.springframework.org/schema/security"

xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd">
<security:debug/>

<!-- Spring-Security 的配置 -->

<!-- filters="none" 不过滤这些资源-->

<security:http pattern="/images/**" security="none"/>

<security:http pattern="/js/**" security="none" />

<security:http pattern="/index.jsp" security="none" />

<!-- 单点登录拦截 -->

<security:http use-expressions="true" auto-config="false"

entry-point-ref="casProcessingFilterEntryPoint"

access-denied-page="/WEB-INF/pages/common/noAuth.jsp">

<security:intercept-url pattern="/main/*.action" access="hasRole('ROLE_USER')"/>

<!-- 对权限角色进行处理,见下面配置的权限层级控制 -->

<security:expression-handler ref="expressHandler"/>

<!-- 在https登陆的时候需要配置端口 -->

<security:port-mappings>

<security:port-mapping http="8088" https="8443"/>

</security:port-mappings>

<security:custom-filter ref="casAuthenticationFilter" position="CAS_FILTER"/>

<!-- 退出登陆,将需要退出后返回的url通过service传入到服务器即可,还需要在cas server的/WEB-INF/cas-servlet.xml中修改 bean id="logoutController" 新增p:followServiceRedirects="true"/>

-->

<security:logout logout-url="/securitylogout" invalidate-session="true" delete-cookies="JSESSIONID"

logout-success-url="https://localhost:8443/cas-server-webapp-3.4.10/logout?service=http://localhost:8088/springsecuritydemo_CAS" />

</security:http>

<!-- 指定一个自定义的authentication-manager :customUserDetailsService -->

<security:authentication-manager alias="theAuthenticationManager">

<security:authentication-provider ref="casAuthenticationProvider" />

</security:authentication-manager>

<!-- 单点登录 -->

<bean id="casAuthenticationFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter">

<property name="authenticationManager" ref="theAuthenticationManager"/>

</bean>

<bean id="casProcessingFilterEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">

<property name="loginUrl" value="https://localhost:8443/cas-server-webapp-3.4.10/login"/>

<property name="serviceProperties" ref="serviceProperties"/>

</bean>

<!-- loginUrl定义CAS server登陆页面,A的地址-->

<bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider">

<property name="authenticationUserDetailsService" ref="authenticationUserDetailsService"/>

<property name="serviceProperties" ref="serviceProperties"></property>

<property name="ticketValidator">

<bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">

<!-- <property name="proxyCallbackUrl" value="http://localhost:8088/springsecuritydemo_CAS/main/main.action"></property> -->

<constructor-arg index="0" value="https://localhost:8443/cas-server-webapp-3.4.10" /> <!-- SSO验证地址-->

</bean>

</property>

<property name="key" value="cas"></property>

</bean>

<!-- authorities对应 CAS server的 登录属性, 在此设置到spirng security中,用于spring security的验证 -->

<bean id="authenticationUserDetailsService" class="org.springframework.security.cas.userdetails.GrantedAuthorityFromAssertionAttributesUserDetailsService">

<constructor-arg>

<array>

<!-- 这里是根据CAS server上的配置得到的authorities的值 -->

<value>userId</value>

<value>username</value>

<value>password</value>

<value>enabled</value>

<value>authorities</value>

</array>

</constructor-arg>

</bean>

<!-- http://localhost:8088/SpringSecurity 具体应用 -->

<!-- j_spring_cas_security_check spring的虚拟URL,此标志标识使用 CAS authentication upon return from CAS SSO login. -->

<bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties">

<property name="service" value="http://localhost:8088/springsecuritydemo_CAS/j_spring_cas_security_check"></property>

<property name="sendRenew" value="false"></property>

</bean>

<!-- 权限层级控制 -->

<bean id="roleHierarchy" class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">

<property name="hierarchy" value="ROLE_ADMIN > ROLE_USER"/>

</bean>

<bean id="expressHandler" class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler">

<property name="roleHierarchy" ref="roleHierarchy"/>

</bean>

</beans>

前往登陆的页面(client):



登陆页面(server):



返回主页面:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: