yale-cas服务器端深度定制
<div /> </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 csscssErrorid="loginname" size="25" tabindex="1" accesskey="${userNameAccessKey}" path="loginname" autocomplete="false" htmlEscape="true" /> </c:if></div>?
在该JSP中name="code"><!--Custom By SGQ 添加登陆字段 --><div /> </label> <spring:message code="screen.welcome.label.custom.accesskey" var="customAccessKey" /> <form:input csscssErrorid="custom" size="25" tabindex="2" path="custom" accesskey="${customAccessKey}" htmlEscape="true" autocomplete="off" /></div>
?
2.用于将参数返回客户端的casServiceValidationSuccess.jsp,在<cas:user>标签下加入如下代码
?
<!-- 解析返回的参数 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 sgq0085screen.welcome.label.custom=\u81ea\u5b9a\u4e49:screen.welcome.label.custom.accesskey=crequired.logingname=\u5fc5\u987b\u5f55\u5165\u7528\u6237\u540d\u3002required.custom=\u81EA\u5B9A\u4E49\u5B57\u6BB在messages_en.properties文件中添加
#Custom By sgq0085screen.welcome.label.custom=<span name="code">cas.logout.followServiceRedirects=true?
?
5.login-webflow.xml中修改如下内容
?
<!-- Custom By SGQ--><!-- <var name="credentials" /> --><var name="credentials" /><!-- 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" ref="attributeRepository" /> </bean> <bean /> </list> </property> <property name="authenticationHandlers"> <list> <bean /> <!-- Custom By SGQ 认证的实际位置 --> <bean /> </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" /> <bean id="serviceRegistryDao" 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" /> <bean id="healthCheckMonitor" p:freeMemoryWarnThreshold="10" /> <bean p:ticketRegistry-ref="ticketRegistry" p:serviceTicketCountWarnThreshold="5000" p:sessionCountWarnThreshold="100000" /> </list> </property> </bean></beans>
?
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.DriverSpyjdbc.url=jdbc:log4jdbc:h2:file:~/.h2/cas;AUTO_SERVER=TRUE;DB_CLOSE_ON_EXIT=TRUE;jdbc.username=sajdbc.password=jdbc.pool.maxIdle=10jdbc.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.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsdhttp://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsdhttp://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" 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" 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" p:dataSource-ref="dataSource" depends-on="flyway" /> <bean id="transactionManager" /> <bean id="accountService" ref="accountDao" /> </bean> <bean id="accountDao" 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 /> </jaxrs:providers> </jaxrs:server> <!-- WebService的实现Bean定义 --> <bean id="accountJaxRsService" 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.完善认证
还记得 /** * 认证的实际位置 * @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进行整合。
完整的DEMO在http://download.csdn.net/detail/sgq0085/6847125
再次提醒,服务器端应该部署在SSL认证的服务器上
?