单点登录
单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
由于一个系统存在诸多子系统,而这些子系统是分别部署在不同的服务器中,那么使用传统方式的session是无法解决的,我们需要使用相关的单点登录技术来解决。
原理:用户第一次登录之后,登录系统会返回一个cookie对象。然后用户访问其他子系统的时候,就会携带者这个cookie一起访问,系统则通过cookie来判断用户是否登录。
CAS
一种可靠的单点登录方法.
CAS 具有以下特点:
- 开源的企业级单点登录解决方案。
- CAS Server 为需要独立部署的 Web 应用。
- CAS Client 支持非常多的客户端(这里指单点登录系统中的各个Web 应用),包括Java, .Net, PHP, Perl, Apache, uPortal, Ruby 等。
CAS 包含两个部分: CAS Server 和 CAS Client。CAS Server 需要独立部署,主要负责对用户的认证工作。CAS Client 负责处理对客户端受保护资源的访问请求,需要登录时,重定向到 CAS Server。
SSO单点登录访问流程主要有以下步骤:
- 访问服务:SSO客户端发送请求访问应用系统提供的服务资源。
- 定向认证:SSO客户端会重定向用户请求到SSO服务器。
- 用户认证:用户身份认证。
- 发放票据:SSO服务器会产生一个随机的Service Ticket。
- 验证票据:SSO服务器验证票据Service Ticket的合法性,验证通过后,允许客户端访问服务。
- 传输用户信息:SSO服务器验证票据通过后,传输用户认证结果信息给客户端。
部署CAS服务端
找到cas验证中心的war文件cas-server-webapp-4.0.0.war,修改名称为cas.war,将war包拷贝到tomcat中。启动tomcat即可访问。http://localhost:8080/cas/login,默认登录名为:casuser,密码为:Mellon
配置CAS服务端
1. 在配置文件中添加登录用户(不使用数据库)
-
找到tomcat中项目的WEB-INF下的deployerConfigContext.xml文件,添加用户名和密码即可
<bean id="primaryAuthenticationHandler" class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler"> <property name="users"> <map> <entry key="casuser" value="Mellon"/> <!--默认用户名和密码--> <entry key="admin" value="admin"/> <!--追加用户名和密码--> </map> </property> </bean>
2. 修改访问的端口
-
修改tomcat端口
进入tomcat目录 conf\server.xml
找到下面的配置,将默认的8080修改为9100<Connector port="9100" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
-
修改CAS的配置文件
修改cas的WEB-INF/cas.properties,也修改为9100
server.name=http://localhost:9100
3. 去除https认证,使用http认证
CAS默认使用的是HTTPS协议,如果使用HTTPS协议需要SSL安全证书(需向特定的机构申请和购买) 。
如果对安全要求不高或是在开发测试阶段,可使用HTTP协议。
修改cas配置文件即可去除https认证
-
修改cas的WEB-INF/deployerConfigContext.xml
增加参数p:requireSecure=“false”。requireSecure属性意思为是否需要安全验证,即HTTPS,false为不采用
-
修改cas的 WEB-INF/spring-configuration/ticketGrantingTicketCookieGenerator.xml
参数p:cookieSecure=“true”,同理为HTTPS验证相关,TRUE为采用HTTPS验证,FALSE为不采用https验证。
参数p:cookieMaxAge="-1",是COOKIE的最大生命周期。-1为无生命周期,即只在当前打开的窗口有效,关闭或重新打开其它窗口,仍会要求验证。可以根据需要修改为大于0的数字,比如3600,意思是在3600秒内,打开任意窗口,都不需要验证。
配置:将cookieSecure改为false , cookieMaxAge 改为3600s
-
修改cas的 WEB-INF/spring-configuration/warnCookieGenerator.xml
配置:将cookieSecure改为false , cookieMaxAge 改为3600s
4. 修改单点退出登录,【重定向】
在cas的WEB-INF/cas-servlet.xml文件中,找到logoutAction对象,将followServiceRedirects改为true
在需要退出的按钮上添加链接:"http://localhost:9100/cas/logout?service=http://www.baidu.com"即可。重定向固定写法,service后加重定向的连接
CAS客户端入门小Demo(原生方式)
参考Demo:《F:/myProject/CAS_Demo》的casClient_Demo1或2
-
创建maven war工程,引入依赖、tomcat
<dependencies> <!-- cas --> <dependency> <groupId>org.jasig.cas.client</groupId> <artifactId>cas-client-core</artifactId> <version>3.3.3</version> </dependency> <!--servlet--> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <!-- 指定端口 --> <port>9001</port> <!-- 请求路径 --> <path>/</path> </configuration> </plugin> </plugins> </build>
-
在web.xml中添加cas配置
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <!-- ======================== 单点登录开始 ======================== --> <!-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置 --> <listener> <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class> </listener> <!-- 该过滤器用于实现单点登出功能,可选配置。 --> <filter> <filter-name>CAS Single Sign Out Filter</filter-name> <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class> </filter> <filter-mapping> <filter-name>CAS Single Sign Out Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 该过滤器负责用户的认证工作,必须启用它 --> <filter> <filter-name>CASFilter</filter-name> <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class> <!--配置CAS服务端的路径--> <init-param> <param-name>casServerLoginUrl</param-name> <param-value>http://localhost:9100/cas/login</param-value> <!--这里的server是服务端的IP --> </init-param> <!--配置本客户端的请求路径--> <init-param> <param-name>serverName</param-name> <param-value>http://localhost:9002</param-value> </init-param> </filter> <filter-mapping> <filter-name>CASFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 该过滤器负责对Ticket的校验工作,必须启用它 --> <filter> <filter-name>CAS Validation Filter</filter-name> <filter-class> org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class> <init-param> <param-name>casServerUrlPrefix</param-name> <param-value>http://localhost:9100/cas</param-value> </init-param> <init-param> <param-name>serverName</param-name> <param-value>http://localhost:9002</param-value> </init-param> </filter> <filter-mapping> <filter-name>CAS Validation Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 该过滤器负责实现HttpServletRequest请求的包裹, 比如允许开发者通过HttpServletRequest的getRemoteUser()方法获得SSO登录用户的登录名,可选配置。 --> <filter> <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> <filter-class> org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class> </filter> <filter-mapping> <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 该过滤器使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。 比如AssertionHolder.getAssertion().getPrincipal().getName()。 --> <filter> <filter-name>CAS Assertion Thread Local Filter</filter-name> <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class> </filter> <filter-mapping> <filter-name>CAS Assertion Thread Local Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- ======================== 单点登录结束 ======================== --> </web-app>
-
测试
编写jsp页面(cas默认使用jsp页面),启动该工程即可测试。
如果想看想过,可以创建多个相同的war工程来进行测试,只要tomcat的端口不一样即可。
配置数据源(配置从数据库获取用户名和密码)
-
在cas服务端的webapps\cas\WEB-INF\lib目录添加jar包
-
在cas的WEB-INF下,在deployerConfigContext.xml文件中添加连接数据库的配置
-
在任意位置配置下面配置
<!-- 配置数据库连接,【注意修改本地数据库的连接参数】 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" p:driverClass="com.mysql.jdbc.Driver" p:jdbcUrl="jdbc:mysql://127.0.0.1:3306/casTest?characterEncoding=utf8" p:user="root" p:password="123" /> <!-- 配置密码加密方式 --> <bean id="passwordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder" c:encodingAlgorithm="MD5" p:characterEncoding="UTF-8" /> <!-- 配置管理者,【注意修改SQL语句】 --> <bean id="dbAuthHandler" class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler" p:dataSource-ref="dataSource" p:sql="select password from tb_user where username = ?" p:passwordEncoder-ref="passwordEncoder"/>
-
找到bean对象authenticationManager,追加entry属性,指定key-ref为配置的管理者
注意:如果要使用自己配置的entry,必须把primaryAuthenticationHandler注释掉才行
<entry key-ref="dbAuthHandler" value-ref="primaryPrincipalResolver"/>
-
-
重启服务器即可使用数据库中的账户登录
注意:配置的数据源不能和默认的用户共存
修改登录界面
将自己的登录页面拷贝到cas的WEB-INF\view\jsp\default\ui 目录下;将页面需要的css等文件拷贝到cas目录下;将登录页面名改为:casLoginView.jsp,替换原有的文件(原有文件可以改名作为参考)
修改我们自己的登录页面内容:
-
添加jsp指令
<%@ page pageEncoding="UTF-8" %> <%@ page contentType="text/html; charset=UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-
修改form标签
添加class属性即可保持原有页面的样式
<form:form class="sui-form" method="post" id="fm1" commandName="${commandName}" htmlEscape="true"> //............. </form:form>
-
修改用户名输入框
添加class属性即可保持原有页面的样式
<form:input placeholder="邮箱/用户名/手机号" class="span2 input-xfat" id="username" size="25" tabindex="1" accesskey="${userNameAccessKey}" path="username" autocomplete="off" htmlEscape="true" />
-
修改密码输入框
添加class属性即可保持原有页面的样式
<form:password placeholder="请输入密码" class="span2 input-xfat" id="password" size="25" tabindex="2" path="password" accesskey="${passwordAccessKey}" htmlEscape="true" autocomplete="off" />
-
修改登录按钮
需要多加三个input标签
<input type="hidden" name="lt" value="${loginTicket}" /> <input type="hidden" name="execution" value="${flowExecutionKey}" /> <input type="hidden" name="_eventId" value="submit" /> <input class="sui-btn btn-block btn-xlarge btn-danger" name="submit" accesskey="l" value="登 录" tabindex="4" type="submit" />
修改错误信息提示(中文提示)
I18N:国际化(internationalizayion)
-
修改cas的WEB-INF\cas-servlet.xml文件,设置国际化为zh_CN
在文件中直接搜索“en”就能找到
-
在messages_zh_CN.properties文件中添加错误信息,中文信息需要转码
# 错误信息提示 # 用户名不存在 authenticationFailure.AccountNotFoundException=\u7528\u6237\u4E0D\u5B58\u5728. # 密码错误 authenticationFailure.FailedLoginException=\u5BC6\u7801\u9519\u8BEF.
-
在登录页面需要信息提示的地方添加标签
<!--错误信息提示--> <span style="color:red"> <form:errors path="*" id="msg" cssClass="errors" element="div" htmlEscape="false" /> </span>
CAS 与 SpringSecurity 集成
参考代码:《F:/myProject/CAS_Demo》下的casClient_Demo3
-
搭建war工程,引入spring坐标,spring-security坐标以及Servlet坐标,tomcat编译器
-
在web.xml中添加springSecurity 以及 springMVC ,在sources中添加springmvc配置文件(扫描包和开启注解)
-
在webapps下创建页面:index.html、login.html、exit.html、以及error.html
--欢迎的登陆我的系统-- <form action="/login" method="post"> 用户名:<input name="username"><br> 密码:<input name="password"><br> <button>登陆</button> </form>
index.html 欢迎进入神奇的spring security世界~~~<br/> <a href="/logout/cas">退出</a>
-
SpringSecurity与cas集成
-
引入集成坐标
<!--security-cas集成--> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-cas</artifactId> <version>4.1.0.RELEASE</version> </dependency> <!--cas--> <dependency> <groupId>org.jasig.cas.client</groupId> <artifactId>cas-client-core</artifactId> <version>3.3.3</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> </exclusion> </exclusions> </dependency>
-
添加spring-security.xml文件(不是原生的SpringSecurity配置文件,是集成的配置文件)
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <!--过滤掉退出后显示的页面--> <http pattern="/exit.html" security="none"/> <!-- entry-point-ref 入口点引用 --> <http use-expressions="false" entry-point-ref="casProcessingFilterEntryPoint"> <intercept-url pattern="/**" access="ROLE_USER"/> <csrf disabled="true"/> <!-- custom-filter为过滤器, position 表示将过滤器放在指定的位置上,before表示放在指定位置之前 ,after表示放在指定的位置之后 --> <custom-filter ref="casAuthenticationFilter" position="CAS_FILTER" /> <custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER"/> <custom-filter ref="singleLogoutFilter" before="CAS_FILTER"/> </http> <!-- CAS入口点 开始 --> <beans:bean id="casProcessingFilterEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint"> <!-- 单点登录服务器登录URL --> <beans:property name="loginUrl" value="http://localhost:9100/cas/login"/> <beans:property name="serviceProperties" ref="serviceProperties"/> </beans:bean> <beans:bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties"> <!--service 配置自身工程的根地址+/login/cas --> <beans:property name="service" value="http://localhost:9003/login/cas"/> </beans:bean> <!-- CAS入口点 结束 --> <!-- 认证过滤器 开始 --> <beans:bean id="casAuthenticationFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter"> <beans:property name="authenticationManager" ref="authenticationManager"/> </beans:bean> <!-- 认证管理器 --> <authentication-manager alias="authenticationManager"> <authentication-provider ref="casAuthenticationProvider"> </authentication-provider> </authentication-manager> <!-- 认证提供者 --> <beans:bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> <beans:property name="authenticationUserDetailsService"> <beans:bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> <beans:constructor-arg ref="userDetailsService" /> </beans:bean> </beans:property> <beans:property name="serviceProperties" ref="serviceProperties"/> <!-- ticketValidator 为票据验证器 --> <beans:property name="ticketValidator"> <beans:bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator"> <beans:constructor-arg index="0" value="http://localhost:9100/cas"/> </beans:bean> </beans:property> <beans:property name="key" value="an_id_for_this_auth_provider_only"/> </beans:bean> <!-- 认证类 --> <beans:bean id="userDetailsService" class="cn.lxh.demo.service.UserDetailServiceImpl"/> <!-- 认证过滤器 结束 --> <!-- 单点登出 开始 --> <beans:bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter"/> <!--该配置作用:在地址栏输入“本地工程/logout/cas”即可实现单点登出并重定向。相当于"http://localhost:9100/cas/logout?service=http://www.baidu.com"--> <beans:bean id="requestSingleLogoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter"> <beans:constructor-arg value="http://localhost:9100/cas/logout?service=http://localhost:9003/exit.html"/> <beans:constructor-arg> <beans:bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/> </beans:constructor-arg> <beans:property name="filterProcessesUrl" value="/logout/cas"/> </beans:bean> <!-- 单点登出 结束 --> </beans:beans>
-
编写认证类UserDetailServiceImpl
该认证类不需要密码,设置为""即可,springSecurity只起到赋予角色的作用,用户验证是交给cas服务器进行
/** * spring-security-cas 用户授权类 */ public class UserDetailServiceImpl implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { System.out.println("经过认证类:"+username); List<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority("ROLE_USER")); return new User(username,"",authorities); } }
-
启动工程即可进行登录验证
-
编写控制器类进行登录名的获取
@RestController public class LoginController { @RequestMapping("/findLoginUser") public void findLoginUser(){ String username = SecurityContextHolder.getContext().getAuthentication().getName(); System.out.println("用户名为:"+username); } }
集成如何找到index.html???
默认会找index.jsp页面,如果没有,需要在web.xml中添加配置
<welcome-file-list> <welcome-file>home-index.html</welcome-file> </welcome-file-list>
-