yale-cas服务器端深度定制

    单点登录(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认证的服务器上

 

你可能感兴趣的:(server,SSO,cas,单点登录,yale)