单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
耶鲁大学(yale)开发的单点登录(Single Sign On)系统称为CAS(Central Authentication Service)被设计成一个独立的Web应用程序(cas.war)。
CAS在2004年12月成为Jasig项目,所以也叫JA-SIG CAS。
本文中服务器版本基于3.5.2版本,对应的客户端版本为3.2.1;
目前客户端已经出了3.3.0,但是官方服务器端项目中POM中使用的依赖cas-client-core仍然是3.2.1版本。
常见的Server端配置方式
1.简单搭建Server比较简单见官网说明;
2.cas提供的服务器只有简单认证的功能,所以和数据库进行交换需要cas-server-support-jdbc
。
由于这种方式,包括如何添加DataSource网上的说明比较多,在这里就不再赘述了。值得注意的是service最后必须要部署在有ssl认证的服务器上,可以参考我另外的帖子keytool+tomcat 单向/双向认证的配置,注意文章中设置本机域名的原因和方式。
对Server进行深度定制
这里主要说明的是如何对Server端进行深度定制,要实现的功能有:
1.自定义登陆参数;2.不通过cas-server-support-jdbc,添加数据库操作;3.使用CXF实现JAX-RS
技术,添加WebService接口。用于账户的维护功能;4.通过flyway自动创建账户表。
一. 导入项目源码
上述通过Maven导入war包的项目有两点缺陷,一不方便通过Maven确定依赖,及不方便对项目的管理;二不方便进行测试。所以采用从官网上下载源码,再进行修改的方式。
官网上下载地址为:http://www.jasig.org/cas/download,我在这里下的最新的CAS Server 3.5.2 Release的ZIP版本。下载下来是一个77.5M的ZIP文件。
因为该地址需要用goagent过去,我将 \cas-server-3.5.2\modules 目录下打包好的文件清空,从新打压缩包上传上来,以供参考。
我们将cas-server-webapp子项目提取出来,通过修改POM文件,将该项目修改为独立项目。
说明:
1.依赖版本参考根目录下parent pom;
2.或源cas-server-webapp通过maven导出的依赖树;
3.添加各种Log桥接到slf4j,并检查依赖树排除log4j和commons-log,添加logback的配置文件,移除/webapp/WEB-INF/classes/log4j.xml和/webapp/WEB-INF/spring-configuration/log4jConfiguration.xml
二.自定义登陆参数
注意:要完成自定义登陆参数,及客户端返回参数的自定义。需要修改如下:
两个JSP
/webapp/WEB-INF/view/jsp/default/ui/casLoginView.jsp
/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp
两个配置文件
/webapp/WEB-INF/deployerConfigContext.xml
/webapp/WEB-INF/login-webflow.xml
三个properties文件中添加内容
/webapp/WEB-INF/classes/messages_en.properties
/webapp/WEB-INF/classes/messages_zh_CN.properties
/webapp/WEB-INF/cas.properties
1.修改登陆页面casLoginView.jsp
通过/my-cas-server/src/main/webapp/WEB-INF/classes/default_views.properties,可以看到casLoginView.url指向的是casLoginView.jsp,
修改class="row fl-controls-left"的DIV中username改为loginname(我这里定义登陆名用logginname)
<div class="row fl-controls-left"> <label for="loginname" class="fl-label"> <spring:message code="screen.welcome.label.netid" /> </label> <c:if test="${not empty sessionScope.openIdLocalId}"> <strong>${sessionScope.openIdLocalId}</strong> <input type="hidden" id="loginname" name="loginname" value="${sessionScope.openIdLocalId}" /> </c:if> <c:if test="${empty sessionScope.openIdLocalId}"> <spring:message code="screen.welcome.label.netid.accesskey" var="userNameAccessKey" /> <form:input cssClass="required" cssErrorClass="error" id="loginname" size="25" tabindex="1" accesskey="${userNameAccessKey}" path="loginname" autocomplete="false" htmlEscape="true" /> </c:if> </div>
在该JSP中class="row fl-controls-left"的DIV和class="row check"的DIV之间,添加如下代码
<!--Custom By SGQ 添加登陆字段 --> <div class="row fl-controls-left"> <label for="custom" class="fl-label"> <spring:message code="screen.welcome.label.custom" /> </label> <spring:message code="screen.welcome.label.custom.accesskey" var="customAccessKey" /> <form:input cssClass="required" cssErrorClass="error" id="custom" size="25" tabindex="2" path="custom" accesskey="${customAccessKey}" htmlEscape="true" autocomplete="off" /> </div>
2.用于将参数返回客户端的casServiceValidationSuccess.jsp,在<cas:user>标签下加入如下代码,
为了传递中文KEY和VALUE的参数
1.需要将<%@ page session="false" %>修改为<%@ page session="false" language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
2.如果部署的是tomcat服务器,修改/bin/catalina.bat文件,在下面第一行和第三行中间加入第二行,重点是-Dfile.encoding=UTF-8
rem ----- Execute The Requested Command --------------------------------------- set JAVA_OPTS=%JAVA_OPTS% -server -Dfile.encoding=UTF-8 -Xms512m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=256m echo Using CATALINA_BASE: "%CATALINA_BASE%"
<%@ page session="false" language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!-- 解析返回的参数 Custom By SGQ --> <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}"> <cas:${fn:escapeXml(attr.key)}>${fn:escapeXml(attr.value)}</cas:${fn:escapeXml(attr.key)}> </c:forEach> </cas:attributes> </c:if>
3.在properties中添加字段为casLoginView.jsp添加国际化的显示添加内容
添加相关提示screen.welcome.label.custom和screen.welcome.label.custom.accesskey在
比如
在messages_zh_CN.properties中添加
#Custom By sgq0085 screen.welcome.label.custom=\u81ea\u5b9a\u4e49: screen.welcome.label.custom.accesskey=c required.logingname=\u5fc5\u987b\u5f55\u5165\u7528\u6237\u540d\u3002 required.custom=\u81EA\u5B9A\u4E49\u5B57\u6BB在 messages_en.properties文件中添加
#Custom By sgq0085 screen.welcome.label.custom=<span class="accesskey">C</span>ustom: screen.welcome.label.custom.accesskey=c
或者使用JQuery Validate自己添加验证,以及登陆页面的其他样式等。
4.cas.properties中修改如下字段,用于单点登出。实际上数据源相关信息也可以放在这个文件中。我们在后面添加
cas.logout.followServiceRedirects=true
5.login-webflow.xml中修改如下内容
<!-- Custom By SGQ--> <!-- <var name="credentials" class="org.jasig.cas.authentication.principal.UsernamePasswordCredentials" /> --> <var name="credentials" class="com.gqshao.cas.principal.MyCredentials" /> <!-- Custom By SGQ--> <binder> <!-- <binding property="username" /> --> <binding property="loginname" /> <binding property="password" /> <binding property="custom" /> </binder>
2.修改配置文件deployerConfigContext.xml
修改的目的:1.支持页面新增的输入参数;2.实现自定义的认证;3.实现自定义客户端认证后返回的参数;
<?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:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> <bean id="authenticationManager" class="org.jasig.cas.authentication.AuthenticationManagerImpl"> <property name="credentialsToPrincipalResolvers"> <list> <!-- Custom By SGQ 用于支持登陆页面输入参数 --> <bean class="com.gqshao.cas.principal.MyCredentialsToPrincipalResolver"> <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" /> <!-- Custom By SGQ 认证的实际位置 --> <bean class="com.gqshao.cas.adaptors.MyAuthenticationHandler" /> </list> </property> </bean> <sec:user-service id="userDetailsService"> <sec:user name="@@THIS SHOULD BE REPLACED@@" password="notused" authorities="ROLE_ADMIN" /> </sec:user-service> <!-- Custom By SGQ 服务器返回值实际封装位置 --> <bean id="attributeRepository" class="com.gqshao.cas.support.MyStubPersonAttributeDao" /> <bean id="serviceRegistryDao" class="org.jasig.cas.services.InMemoryServiceRegistryDaoImpl"> <property name="registeredServices"> <list> <bean class="org.jasig.cas.services.RegexRegisteredService"> <property name="id" value="0" /> <property name="name" value="HTTP and IMAP" /> <property name="description" value="Allows HTTP(S) and IMAP(S) protocols" /> <property name="serviceId" value="^(https?|imaps?)://.*" /> <property name="evaluationOrder" value="10000001" /> <!-- Custom By SGQ --> <property name="ignoreAttributes" value="true" /> </bean> </list> </property> </bean> <bean id="auditTrailManager" class="com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager" /> <bean id="healthCheckMonitor" class="org.jasig.cas.monitor.HealthCheckMonitor"> <property name="monitors"> <list> <bean class="org.jasig.cas.monitor.MemoryMonitor" p:freeMemoryWarnThreshold="10" /> <bean class="org.jasig.cas.monitor.SessionMonitor" p:ticketRegistry-ref="ticketRegistry" p:serviceTicketCountWarnThreshold="5000" p:sessionCountWarnThreshold="100000" /> </list> </property> </bean> </beans>
五个实现类
(1)com.gqshao.cas.adaptors.MyAuthenticationHandler 中authenticateLoginnamePasswordInternal方法包含认证的实际位置,后续添加数据库持久之后,在这里完善认证
package com.gqshao.cas.adaptors; import javax.validation.constraints.NotNull; import org.jasig.cas.authentication.handler.AuthenticationException; import org.jasig.cas.authentication.handler.AuthenticationHandler; import org.jasig.cas.authentication.principal.Credentials; import com.gqshao.cas.principal.MyCredentials; public class MyAuthenticationHandler implements AuthenticationHandler { private static final Class<MyCredentials> DEFAULT_CLASS = MyCredentials.class; @NotNull private Class<?> classToSupport = DEFAULT_CLASS; private boolean supportSubClasses = true; public boolean supports(Credentials credentials) { return credentials != null && (this.classToSupport.equals(credentials.getClass()) || (this.classToSupport .isAssignableFrom(credentials.getClass())) && this.supportSubClasses); } public boolean authenticate(Credentials credentials) throws AuthenticationException { return authenticateLoginnamePasswordInternal((MyCredentials) credentials); } public final void setClassToSupport(final Class<?> classToSupport) { this.classToSupport = classToSupport; } /** * 认证的实际位置 * @param credentials * @return */ private boolean authenticateLoginnamePasswordInternal(MyCredentials credentials) { MyCredentials c = (MyCredentials) credentials; if (c.getLoginname().equals(c.getPassword())) { credentials.setId("select_id_from_table"); return true; } return false; } }
(2)com.gqshao.cas.principal.MyCredentials 与登陆参数一一对应
package com.gqshao.cas.principal; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import org.jasig.cas.authentication.principal.Credentials; public class MyCredentials implements Credentials { private static final long serialVersionUID = -7008439202352047770L; private String id; /** The username. */ @NotNull @Size(min=1,message = "required.loginname") private String loginname; /** The password. */ @NotNull @Size(min=1, message = "required.password") private String password; /** The Custom*/ @NotNull @Size(min = 1, message = "required.custom") private String custom; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getLoginname() { return loginname; } public void setLoginname(String loginname) { this.loginname = loginname; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getCustom() { return custom; } public void setCustom(String custom) { this.custom = custom; } public String toString() { return "[loginname: " + this.loginname + "]"; } @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MyCredentials that = (MyCredentials) o; if (password != null ? !password.equals(that.password) : that.password != null) return false; if (loginname != null ? !loginname.equals(that.loginname) : that.loginname != null) return false; if (custom != null ? !custom.equals(that.custom) : that.custom != null) return false; return true; } @Override public int hashCode() { int result = loginname != null ? loginname.hashCode() : 0; result = 31 * result + (password != null ? password.hashCode() : 0); return result; } }
(3).MyCredentialsToPrincipalResolver
package com.gqshao.cas.principal; import org.jasig.cas.authentication.principal.AbstractPersonDirectoryCredentialsToPrincipalResolver; import org.jasig.cas.authentication.principal.Credentials; public class MyCredentialsToPrincipalResolver extends AbstractPersonDirectoryCredentialsToPrincipalResolver { public boolean supports(Credentials credentials) { return credentials != null && MyCredentials.class.isAssignableFrom(credentials .getClass()); } @Override protected String extractPrincipalId(Credentials credentials) { final MyCredentials myCredentials = (MyCredentials) credentials; return myCredentials.getId(); } }
(4)com.gqshao.cas.support.MyPersonImpl 将返回客户端参数封装成POJO
package com.gqshao.cas.support; import java.util.List; import java.util.Map; import org.jasig.services.persondir.IPersonAttributes; import org.jasig.services.persondir.support.BasePersonImpl; public class MyPersonImpl extends BasePersonImpl { private static final long serialVersionUID = -6468711518798238482L; public static final String DEFAULT_NAME_ATTRIBUTE = "loginname"; private final String nameAttribute; public MyPersonImpl(Map<String, List<Object>> attributes) { super(attributes); this.nameAttribute = DEFAULT_NAME_ATTRIBUTE; } public MyPersonImpl(String nameAttribute, Map<String, List<Object>> attributes) { super(attributes); this.nameAttribute = nameAttribute; } public MyPersonImpl(IPersonAttributes personAttributes) { this(personAttributes.getName(), personAttributes.getAttributes()); } public String getName() { final Object attributeValue = this.getAttributeValue(this.nameAttribute); if (attributeValue == null) { return null; } return attributeValue.toString(); } }
(5)com.gqshao.cas.support.MyStubPersonAttributeDao 返回客户端的实际位置,这里对中文等进行测试。后续自定义返回有意义的参数。
package com.gqshao.cas.support; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import org.jasig.services.persondir.IPersonAttributes; import org.jasig.services.persondir.support.StubPersonAttributeDao; import com.google.common.collect.Lists; import com.google.common.collect.Maps; public class MyStubPersonAttributeDao extends StubPersonAttributeDao { /** * 服务器返回值实际封装位置 */ @Override public IPersonAttributes getPerson(String uid) { Map<String, List<Object>> attributes = Maps.newHashMap(); attributes.put("uid", Collections.singletonList((Object) uid)); attributes.put("custom", Collections.singletonList((Object) "custom_result")); List<Object> lnarray = Lists.newArrayList(); lnarray.add("admin"); attributes.put("loginname", lnarray); attributes.put("中文KEY", Collections.singletonList((Object) "中文值")); for (String key : attributes.keySet()) { System.out.println(key + " : " + attributes.get(key)); } MyPersonImpl person = new MyPersonImpl(attributes); return person; }; @Override public Set<IPersonAttributes> getPeopleWithMultivaluedAttributes(Map<String, List<Object>> query) { return super.getPeopleWithMultivaluedAttributes(query); } }
其实到此为止,CAS服务器已经简单实现完成。可以独立运行了,通过MyAuthenticationHandler.authenticateLoginnamePasswordInternal(Credentials)这里实现自己的认证就可以了,现在只要是用户名和密码相同。即可实现认证。在实际中肯定不是这样。下面我们同时添加数据源、Flyway建库和CXF开放web service接口。
三. 功能扩展和完善
1.POM文件中添加依赖,这里使用H2做演示,用Spring JdbcTemplate做数据库做数据库持久层
需要引入
cxf 2.7.7
jackson 2.3.0
tomcat-jdbc 7.0.42
flyway 2.3.1
2.添加数据库配置
上面提到过,数据源配置可以配置到/webapp/WEB-INF/cas.properties中,这里采用数据库连接池选用tomcat-jdbc,数据库H2,并用log4jdbc展示SQL,所以内容如下
jdbc.driver=net.sf.log4jdbc.DriverSpy jdbc.url=jdbc:log4jdbc:h2:file:~/.h2/cas;AUTO_SERVER=TRUE;DB_CLOSE_ON_EXIT=TRUE; jdbc.username=sa jdbc.password= jdbc.pool.maxIdle=10 jdbc.pool.maxActive=50
3.添加CXF和Datasource的配置文件和flyway的配置
首先在web.xml中加入cxf的监听
<servlet> <servlet-name>CXFServlet</servlet-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>CXFServlet</servlet-name> <url-pattern>/cxf/*</url-pattern> </servlet-mapping>
然后添加数据源和CXF的配置
/src/main/resources/datasource/applicationContext-datasource.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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jaxrs="http://cxf.apache.org/jaxrs" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd"> <description>维护账户信息用数据库连接信息</description> <!-- 数据源配置,使用应用内的Tomcat JDBC连接池 --> <bean id="dataSource" class="org.apache.tomcat.jdbc.pool.DataSource" destroy-method="close"> <!-- Connection Info --> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="maxActive" value="${jdbc.pool.maxActive}" /> <property name="maxIdle" value="${jdbc.pool.maxIdle}" /> <property name="defaultAutoCommit" value="false" /> <!-- 连接Idle半个小时后超时,每15分钟检查一次 --> <property name="timeBetweenEvictionRunsMillis" value="900000" /> <property name="minEvictableIdleTimeMillis" value="1800000" /> </bean> <!-- flyway配置 --> <bean id="flyway" class="com.googlecode.flyway.core.Flyway" init-method="migrate"> <property name="dataSource" ref="dataSource" /> <property name="encoding" value="UTF-8" /> <property name="initVersion" value="0" /> <property name="table" value="lcm_schema_version" /> <property name="locations" value="dbmigrate" /> <property name="initOnMigrate" value="true" /> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="dataSource" depends-on="flyway" /> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource" /> <bean id="accountService" class="com.gqshao.account.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao" /> </bean> <bean id="accountDao" class="com.gqshao.account.dao.AccountDao"> <property name="jdbcTemplate" ref="jdbcTemplate" /> </bean> <!-- 通过AOP配置提供事务增强,让AccountService下所有Bean的所有方法拥有事务 --> <aop:config> <aop:pointcut id="serviceMethod" expression=" execution(* com.gqshao.account.service..*(..))" /> <aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice" /> </aop:config> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="find*" read-only="true" /> <tx:method name="save" rollback-for="Exception" /> </tx:attributes> </tx:advice> </beans>
/src/main/resources/webservice/applicationContext-jaxrs-server.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:jaxrs="http://cxf.apache.org/jaxrs" xsi:schemaLocation="http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd" > <description>Apache CXF的Restful Web Service配置</description> <!-- jax-rs endpoint定义 --> <jaxrs:server id="serviceContainer" address="/jaxrs"> <jaxrs:serviceBeans> <ref bean="accountJaxRsService" /> </jaxrs:serviceBeans> <jaxrs:providers> <bean class="com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider" /> </jaxrs:providers> </jaxrs:server> <!-- WebService的实现Bean定义 --> <bean id="accountJaxRsService" class="com.gqshao.account.jaxrs.AccountJaxRsService"> <property name="accountService" ref="accountService" /> </bean> </beans>
数据库初始化脚本/src/main/resources/dbmigrate/V0_0_1__account_schema.sql
---- 创建报告表------- create table rbac_account( ID CHAR(32) not null, login_name VARCHAR2(50), password VARCHAR2(50), salt VARCHAR2(50), custom VARCHAR2(50), constraint pk_rbac_account primary key (ID) ); -- Add comments to the table comment on table rbac_account is '账户表'; -- Add comments to the columns comment on column rbac_account.id is '主键ID'; comment on column rbac_account.login_name is '登录名'; comment on column rbac_account.password is '密码'; comment on column rbac_account.salt is '盐'; comment on column rbac_account.custom is '自定义字段'; insert into rbac_account (id, login_name, password, salt,custom) values ('ABCDEFGHIJKLMNOPQRSTUVWXYZ012345','admin','8b988cb8b23f880b96575b9fb8b4792b214b3152','fe3d7e30d8e116e2','scope1');
在/webapp/WEB-INF/deployerConfigContext.xml最后引入上述两个配置
<!-- datasource Custom By SGQ --> <import resource="classpath*:/datasource/applicationContext-datasource.xml" /> <!-- webservice Custom By SGQ --> <import resource="classpath*:/webservice/applicationContext-jaxrs-server.xml" />
3.实现类
这里基本上就完成了,实现类的这里把基于Jax-rx的web service接口展示一下
package com.gqshao.account.jaxrs; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import com.gqshao.account.domain.Account; import com.gqshao.account.domain.ResultDTO; import com.gqshao.account.service.AccountService; import com.gqshao.common.web.MediaTypes; /** * cxf在web.xml侦听/cxf, 在applicationContext.xml里侦听/jaxrx,完整访问路径为 /cxf/jaxrs/account/{loginName}/{custom} */ @Path("/account") public class AccountJaxRsService { private AccountService accountService; /** * 判断用户是否存在 * {@link http://localhost:8080/cas/cxf/jaxrs/account/admin/admin} */ @GET @Path("/{loginName}/{custom}") @Produces(MediaTypes.JSON_UTF_8) public ResultDTO query(@PathParam("loginName") String loginName, @PathParam("custom") String custom) { Account account = accountService.findByLoginNameAndCustom(loginName, custom); if (account == null) { String message = "用户不存在(id:" + loginName + ")"; throw buildException(Status.NOT_FOUND, message); } ResultDTO res = new ResultDTO(); res.setSuccess(true); res.setMsg("用户存在(id:" + loginName + ")"); return res; } @POST @Path("/{loginName}/{password}/{custom}") @Produces(MediaTypes.JSON_UTF_8) public ResultDTO save(@PathParam("loginName") String loginName, @PathParam("password") String password, @PathParam("custom") String custom) { try { boolean isSuccess = accountService.save(loginName, password, custom); ResultDTO res = new ResultDTO(); res.setSuccess(isSuccess); if (isSuccess) { res.setMsg("创建" + loginName + "成功"); } else { res.setMsg("创建" + loginName + "失败"); } return res; } catch (Exception e) { String message = "创建" + loginName + "失败"; throw buildException(Status.EXPECTATION_FAILED, message); } } public void setAccountService(AccountService accountService) { this.accountService = accountService; } private WebApplicationException buildException(Status status, String message) { return new WebApplicationException(Response.status(status).entity(message) .type(MediaTypes.TEXT_PLAIN_UTF_8).build()); } }
4.完善认证
还记得com.gqshao.cas.adaptors.MyAuthenticationHandler中的authenticateLoginnamePasswordInternal方法么,可以在该类中引入账户的相关service,完成实际认证工作。
/** * 认证的实际位置 * @param credentials * @return */ private boolean authenticateLoginnamePasswordInternal(MyCredentials credentials) { credentials.setCustom(null); BeanValidators.validateWithException(validator, credentials); Account account = accountService.authenticate(credentials); if (account == null) { return false; } credentials.setId(account.getId()); return true; }
这里面用到了JSR303去判断credentials属性是否正确,因为前端会做验证,所以这里验证不通过并且抛出异常的都是非正常情况,不用考虑用户界面的友好型。
Restful风格的webservice 可以通过进行测试soapUI
转载请注明 :
http://sgq0085.iteye.com/blog/2003190
至此,客户端定制完成。下一篇为服务器端接收参数,并与shiro进行整合。
再次提醒,服务器端应该部署在SSL认证的服务器上