欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

yale-cas服务器端深度定制

程序员文章站 2022-06-14 08:44:04
...

    单点登录(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文件。

 

因为该地址需要用*过去,我将 \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认证的服务器上