最近使用JeeCMS的系统需要用到CA认证,之前很是苦逼了一段时间毕竟是第一次接触类似的东西,没有专业的工程师指导,只给了一个demo和几个文档,当时真的是无从下手。幸好通过两个熬夜,不断的尝试终于成功了,下面就把具体内容分享一下。
关于CA认证是什么东西,我想大多数人都知道的,网上银行使用的key宝或者U盾都属于此列,使用数字证书和密钥加固重要密码的认证流程。也就是说,我手里有个钥匙盘,插入钥匙盘,输入钥匙盘的密码,才能通过该认证,否则拒绝通行。具体原理大家可以去百度百科上去熟悉(http://baike.baidu.com/view/356572.htm),此处不再赘言。
CA认证首先要有认证服务器,服务器可以配置认证关键字(相当于应用ID),产生认证原文,进行密码验证,返回认证结果和证书信息。本文主要说明的是客户端的配置和服务器端的交互过程。
涉及到的文件:前台登陆界面,后台两个servlet。
所采用认证方式登陆,则进行以下流程:
前台代码如下:
random对应RandomServlet,代码如下:
public class RandomServlet extends HttpServlet {
private static final long serialVersionUID = 3923090461076418525L;
private String tempURL = null,propertiesURL = null;
private Properties props = null;
/** 认证地址 */
private final String KEY_AUTHURL = "authURL";
/** 应用标识 */
private final String KEY_APP_ID = "appId";
/**
* @see javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
*/
public void init(ServletConfig cfg) throws ServletException {
// 初始化程序跳转页面
tempURL = cfg.getInitParameter("url");
propertiesURL = cfg.getInitParameter("propertiesURL");
}
/*
* (non-Javadoc)
*
* @see
* javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest
* , javax.servlet.http.HttpServletResponse)
*/
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
//System.out.println("已进入RandomServlet!!!");
// 设置页面不缓存
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
// 初始化属性文件路径
// String parentPath = request.getSession().getServletContext()
// .getRealPath("/WEB-INF");
// 产生认证原文
String randNum = generateRandomNum();
if (randNum == null || randNum.trim().equals("")) {
System.out.println("证书认证数据不完整!");
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
/**************************
* 第三步 服务端返回认证原文 *
**************************/
// 设置认证原文到session,用于程序向后传递,通讯报文中使用
//System.out.println("设置认证原文到session开始");
HttpSession session = request.getSession();
session.setAttribute("original_data", randNum);
//System.out.println("设置认证原文到session结束");
// 设置认证原文到页面,给页面程序提供参数,用于产生认证请求数据包
request.setAttribute("original", randNum);
// 设置跳转页面
//request.getRequestDispatcher(tempURL).forward(request, response);
response.getWriter().write(randNum);
return;
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
doGet(req, resp);
}
/**
* 产生认证原文
*/
private String generateRandomNum() {
/**************************
* 第二步 服务端产生认证原文 *
**************************/
String num = "1234567890abcdefghijklmnopqrstopqrstuvwxyz";
int size = 10;
char[] charArray = num.toCharArray();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < size; i++) {
sb.append(charArray[((int) (Math.random() * 10000) % charArray.length)]);
}
return sb.toString();
}
/**
* 获取文件中的属性值
*/
private String getProperties(String key) {
return props.get(key) == null ? null : (String) props.get(key);
}
}
/**
* Copyright © 1999-2008 JIT Co,Ltd.
* All right reserved.
*/
package cn.com.jit.cinas;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.XMLWriter;
import sun.misc.BASE64Encoder;
public class AuthenServlet extends HttpServlet {
private static final long serialVersionUID = -1686835672374220173L;
private String tempURL = null, propertiesURL = null;
private Properties props = null;
public void init(ServletConfig cfg) throws ServletException {
tempURL = cfg.getInitParameter("url");
propertiesURL = cfg.getInitParameter("propertiesURL");
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
response.setCharacterEncoding("UTF-8");
/***************************************************************************
* isSuccess 认证是否成功,true成功/false失败;errCode 错误码;errDesc 错误描述 *
* ************************************************************************/
//第四步:客户端认证
//第五步:服务端验证认证原文
//第六步:应用服务端认证
//第七步:网关返回认证响应
//第八步:服务端处理
/***********************************
* 获取应用标识及网关认证地址 *
***********************************/
boolean isSuccess = true;
String errCode = null, errDesc = null;
// 初始化属性文件路径
String parentPath = request.getSession().getServletContext()
.getRealPath("/WEB-INF");
// 初始化配置文件属性
InputStream in = new FileInputStream(parentPath + propertiesURL);
props = new Properties();
props.load(in);
// 可以根据需求使用不同的获取方法
String appId = this.getProperties(KEY_APP_ID);
String authURL = this.getProperties(KEY_AUTHURL);
if (!isNotNull(appId) || !isNotNull(authURL)) {
isSuccess = false;
errDesc = "应用标识或网关认证地址不可为空";
System.out.println("应用标识或网关认证地址不可为空\n");
}
String original_data = null, signed_data = null,original_jsp = null , username = null , password = null;
/**************************
* 获取认证数据信息 *
**************************/
if (isSuccess) {
//System.out.println("应用标识及网关的认证地址读取成功!\n应用标识:" + appId + "\n认证地址:"+ authURL + "\n");
//System.out.println("最先的原文ORIGINAL_DATA="+(String) request.getSession().getAttribute(KEY_ORIGINAL_DATA));
//System.out.println("从客户端返回的原文="+(String) request.getParameter(KEY_ORIGINAL_JSP));
//System.out.println("从客户的返回的认证报文="+(String) request.getParameter(KEY_SIGNED_DATA));
if (isNotNull((String) request.getSession().getAttribute(KEY_ORIGINAL_DATA))
&& isNotNull((String) request.getParameter(KEY_SIGNED_DATA))
&&isNotNull((String) request.getParameter(KEY_ORIGINAL_JSP))) {
// 获取session中的认证原文
original_data = (String) request.getSession().getAttribute(KEY_ORIGINAL_DATA);
// 获取request中的认证原文
original_jsp = (String) request.getParameter(KEY_ORIGINAL_JSP);
/**************************
* 第五步:服务端验证认证原文 *
**************************/
if(!original_data.equalsIgnoreCase(original_jsp)){
isSuccess = false;
errDesc = "客户端提供的认证原文与服务端的不一致";
System.out.println("客户端提供的认证原文与服务端的不一致!\n");
}else{
// 获取证书认证请求包
signed_data = (String) request.getParameter(KEY_SIGNED_DATA);
/* 随机密钥 */
original_data = new BASE64Encoder().encode(original_jsp
.getBytes());
//System.out.println("读取认证原文和认证请求包成功!\n认证原文:" + original_jsp+ "\n认证请求包:" + signed_data + "\n");
}
} else {
isSuccess = false;
errDesc = "证书认证数据不完整";
System.out.println("证书认证数据不完整!\n");
}
}
/**************************
* 第六步:应用服务端认证 *
**************************/
// 认证处理
try {
byte[] messagexml = null;
if (isSuccess) {
/*** 1 组装认证请求报文数据 ** 开始 **/
Document reqDocument = DocumentHelper.createDocument();
Element root = reqDocument.addElement(MSG_ROOT);
Element requestHeadElement = root.addElement(MSG_HEAD);
Element requestBodyElement = root.addElement(MSG_BODY);
/* 组装报文头信息 */
requestHeadElement.addElement(MSG_VSERSION).setText(
MSG_VSERSION_VALUE);
requestHeadElement.addElement(MSG_SERVICE_TYPE).setText(
MSG_SERVICE_TYPE_VALUE);
/* 组装报文体信息 */
// 组装应用标识信息
requestBodyElement.addElement(MSG_APPID).setText(appId);
Element authenElement = requestBodyElement.addElement(MSG_AUTH);
Element authCredentialElement = authenElement
.addElement(MSG_AUTHCREDENTIAL);
// 组装证书认证信息
authCredentialElement.addAttribute(MSG_AUTH_MODE,
MSG_AUTH_MODE_CERT_VALUE );
authCredentialElement.addElement(MSG_DETACH).setText(
signed_data);
authCredentialElement.addElement(MSG_ORIGINAL).setText(
original_data);
// 组装口令认证信息
//username = request.getParameter( "" );//获取认证页面传递过来的用户名/口令
//password = request.getParameter( "" );
//authCredentialElement.addAttribute(MSG_AUTH_MODE,MSG_AUTH_MODE_PASSWORD_VALUE );
//authCredentialElement.addElement( MSG_USERNAME ).setText(username);
//authCredentialElement.addElement( MSG_PASSWORD ).setText(password);
// 组装属性查询列表信息
Element attributesElement = requestBodyElement
.addElement(MSG_ATTRIBUTES);
attributesElement.addAttribute(MSG_ATTRIBUTE_TYPE,
MSG_ATTRIBUTE_TYPE_PORTION);
// TODO 取公共信息
addAttribute(attributesElement, "X509Certificate.SubjectDN",
"http://www.jit.com.cn/cinas/ias/ns/saml/saml11/X.509");
addAttribute(attributesElement, "UMS.UserID",
"http://www.jit.com.cn/ums/ns/user");
/*** 1 组装认证请求报文数据 ** 完毕 **/
StringBuffer reqMessageData = new StringBuffer();
try {
/*** 2 将认证请求报文写入输出流 ** 开始 **/
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
XMLWriter writer = new XMLWriter(outStream);
writer.write(reqDocument);
messagexml = outStream.toByteArray();
/*** 2 将认证请求报文写入输出流 ** 完毕 **/
reqMessageData.append("请求内容开始!\n");
reqMessageData.append(outStream.toString() + "\n");
reqMessageData.append("请求内容结束!\n");
//System.out.println(reqMessageData.toString() + "\n");
} catch (Exception e) {
isSuccess = false;
errDesc = "组装请求时出现异常";
System.out.println("组装请求时出现异常");
}
}
/****************************************************************
* 创建与网关的HTTP连接,发送认证请求报文,并接收认证响应报文*
****************************************************************/
/*** 1 创建与网关的HTTP连接 ** 开始 **/
System.out.println("/*** 1 创建与网关的HTTP连接 ** 开始 **/");
int statusCode = 500;
HttpClient httpClient = null;
PostMethod postMethod = null;
if (isSuccess) {
// HTTPClient对象
httpClient = new HttpClient();
postMethod = new PostMethod(authURL);
// 设置报文传送的编码格式
postMethod.setRequestHeader("Content-Type",
"text/xml;charset=UTF-8");
/*** 2 设置发送认证请求内容 ** 开始 **/
postMethod.setRequestBody(new ByteArrayInputStream(messagexml));
/*** 2 设置发送认证请求内容 ** 结束 **/
// 执行postMethod
try {
/*** 3 发送通讯报文与网关通讯 ** 开始 **/
// System.out.println("/*** 3 发送通讯报文与网关通讯 ** 开始 **/");
statusCode = httpClient.executeMethod(postMethod);
// System.out.println("/*** 3 发送通讯报文与网关通讯 ** 结束 **/");
/*** 3 发送通讯报文与网关通讯 ** 结束 **/
} catch (Exception e) {
isSuccess = false;
errCode = String.valueOf(statusCode);
errDesc = e.getMessage();
System.out.println("与网关连接出现异常\n");
}
}
/****************************************************************
* 第七步:网关返回认证响应*
****************************************************************/
//System.out.println("第七步:网关返回认证响应*");
StringBuffer respMessageData = new StringBuffer();
String respMessageXml = null;
if (isSuccess) {
// 当返回200或500状态时处理业务逻辑
if (statusCode == HttpStatus.SC_OK
|| statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR) {
// 从头中取出转向的地址
try {
/*** 4 接收通讯报文并处理 ** 开始 **/
System.out.println("/*** 4 接收通讯报文并处理 ** 开始 **/");
byte[] inputstr = postMethod.getResponseBody();
ByteArrayInputStream ByteinputStream = new ByteArrayInputStream(
inputstr);
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
int ch = 0;
try {
while ((ch = ByteinputStream.read()) != -1) {
int upperCh = (char) ch;
outStream.write(upperCh);
}
} catch (Exception e) {
isSuccess = false;
errDesc = e.getMessage();
}
if (isSuccess) {
System.out.println("// 200 表示返回处理成功");
// 200 表示返回处理成功
if (statusCode == HttpStatus.SC_OK) {
respMessageData.append("响应内容开始!\n");
respMessageData.append(new String(outStream
.toByteArray(), "UTF-8")
+ "\n");
respMessageData.append("响应内容开始!\n");
respMessageXml = new String(outStream
.toByteArray(), "UTF-8");
} else {
// 500 表示返回失败,发生异常
respMessageData.append("响应500内容开始!\n");
respMessageData.append(new String(outStream
.toByteArray())
+ "\n");
respMessageData.append("响应500内容结束!\n");
isSuccess = false;
errCode = String.valueOf(statusCode);
errDesc = new String(outStream.toByteArray());
}
//System.out.println(respMessageData.toString()+ "\n");
}
/*** 4 接收通讯报文并处理 ** 结束 **/
} catch (IOException e) {
isSuccess = false;
errCode = String.valueOf(statusCode);
errDesc = e.getMessage();
//System.out.println("读取认证响应报文出现异常!");
}
}
}
/*** 1 创建与网关的HTTP连接 ** 结束 **/
/**************************
*第八步:服务端处理 *
**************************/
Document respDocument = null;
Element headElement = null;
Element bodyElement = null;
if (isSuccess) {
//把string转换为xml
respDocument = DocumentHelper.parseText(respMessageXml);
headElement = respDocument.getRootElement().element(MSG_HEAD);
bodyElement = respDocument.getRootElement().element(MSG_BODY);
/*** 1 解析报文头 ** 开始 **/
if (headElement != null) {
boolean state = Boolean.valueOf(
headElement.elementTextTrim(MSG_MESSAGE_STATE))
.booleanValue();
if (state) {
isSuccess = false;
errCode = headElement.elementTextTrim(MSG_MESSAGE_CODE);
errDesc = headElement.elementTextTrim(MSG_MESSAGE_DESC);
//System.out.println("认证业务处理失败!\t" + errDesc + "\n");
}
}
}
if (isSuccess) {
//System.out.println("解析报文头成功!\n");
/* 解析报文体 */
// 解析认证结果集
Element authResult = bodyElement.element(MSG_AUTH_RESULT_SET)
.element(MSG_AUTH_RESULT);
isSuccess = Boolean.valueOf(
authResult.attributeValue(MSG_SUCCESS)).booleanValue();
if (!isSuccess) {
errCode = authResult
.elementTextTrim(MSG_AUTH_MESSSAGE_CODE);
errDesc = authResult
.elementTextTrim(MSG_AUTH_MESSSAGE_DESC);
//System.out.println("身份认证失败,失败原因:" + errDesc);
}
}
if (isSuccess) {
//System.out.println("身份认证成功!\n");
// 解析用户属性列表
Element attrsElement = bodyElement.element(MSG_ATTRIBUTES);
if (attrsElement != null) {
List attributeNodeList = attrsElement
.elements(MSG_ATTRIBUTE);
for (int i = 0; i < attributeNodeList.size(); i++) {
Element userAttrNode = (Element) attributeNodeList
.get(i);
String name = userAttrNode.attributeValue(MSG_NAME);
String value = userAttrNode.getTextTrim();
request.setAttribute(name, value);
//System.out.println("属性名:" + name + "\t属性值:" + value
// + "\n");
//this.password = value;
this.getUsername(value);
}
}
}
} catch (Exception e) {
isSuccess = false;
errDesc = e.getMessage();
}
if (!isSuccess) {
if (isNotNull(errCode)) {
request.setAttribute("errCode", errCode);
}
if (isNotNull(errDesc)) {
request.setAttribute("errDesc", errDesc);
}
System.out.println("处理数据结束,业务处理失败,失败原因:" + errDesc + "\n");
}else {
System.out.println("处理数据结束,一切正常!\n");
}
request.setAttribute("isSuccess", new Boolean(isSuccess).toString());
//request.getRequestDispatcher(tempURL).forward(request, response);
if(isSuccess){
response.getWriter().write("success,"+this.username+","+this.realname);
}else{
response.getWriter().write("failure,"+errCode+","+errDesc);
}
}
private void getUsername(String value) {
String[] users = value.split(",");
String usercodeTemp= users[0].trim();
String usernameTemp = users[1].trim();
this.realname = usercodeTemp.substring(usercodeTemp.indexOf('=')+1, usercodeTemp.length());
this.username = usernameTemp.substring(usernameTemp.indexOf('=')+1, usernameTemp.length());
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
doGet(req, resp);
}
/**
* 判断是否是空串
*/
private boolean isNotNull(String str) {
if (str == null || str.trim().equals(""))
return false;
else
return true;
}
/**
* 获取文件中的属性值
*/
private String getProperties(String key) {
return props.get(key) == null ? null : (String) props.get(key);
}
/**
* 向xml插入结点
*/
private void addAttribute(Element attributesElement, String name,
String namespace) {
Element attr = attributesElement.addElement(MSG_ATTRIBUTE);
attr.addAttribute(MSG_NAME, name);
attr.addAttribute(MSG_NAMESPACE, namespace);
}
/******************************* 报文公共部分 ****************************/
/** 报文根结点 */
private final String MSG_ROOT = "message";
/** 报文头结点 */
private final String MSG_HEAD = "head";
/** 报文体结点 */
private final String MSG_BODY = "body";
/** 服务版本号 */
private final String MSG_VSERSION = "version";
/** 服务版本值 */
private final String MSG_VSERSION_VALUE = "1.0";
/** 服务类型 */
private final String MSG_SERVICE_TYPE = "serviceType";
/** 服务类型值 */
private final String MSG_SERVICE_TYPE_VALUE = "AuthenService";
/** 报文体 认证方式 */
private final String MSG_AUTH_MODE = "authMode";
/** 报文体 证书认证方式 */
private final String MSG_AUTH_MODE_CERT_VALUE = "cert";
/** 报文体 口令认证方式 */
private final String MSG_AUTH_MODE_PASSWORD_VALUE = "password";
/** 报文体 属性集 */
private final String MSG_ATTRIBUTES = "attributes";
/** 报文体 属性 */
private final String MSG_ATTRIBUTE = "attr";
/** 报文体 属性名 */
private final String MSG_NAME = "name";
/** 报文体 属性空间 */
private final String MSG_NAMESPACE = "namespace";
/*********************************************************************/
/******************************* 请求报文 ****************************/
/** 报文体 应用ID */
private final String MSG_APPID = "appId";
/** 报文体 认证结点 */
private final String MSG_AUTH = "authen";
/** 报文体 认证凭据 */
private final String MSG_AUTHCREDENTIAL = "authCredential";
/** 报文体 detach认证请求包 */
private final String MSG_DETACH = "detach";
/** 报文体 原文 */
private final String MSG_ORIGINAL = "original";
/** 报文体 用户名 */
private final String MSG_USERNAME = "username";
/** 报文体 口令 */
private final String MSG_PASSWORD = "password";
/** 报文体 属性类型 */
private final String MSG_ATTRIBUTE_TYPE = "attributeType";
/** 指定属性 */
private final String MSG_ATTRIBUTE_TYPE_PORTION = "portion";
/*********************************************************************/
/******************************* 响应报文 ****************************/
/** 报文体 认证结果集状态 */
private final String MSG_MESSAGE_STATE = "messageState";
/** 响应报文消息码 */
private final String MSG_MESSAGE_CODE = "messageCode";
/** 响应报文消息描述 */
private final String MSG_MESSAGE_DESC = "messageDesc";
/** 报文体 认证结果集 */
private final String MSG_AUTH_RESULT_SET = "authResultSet";
/** 报文体 认证结果 */
private final String MSG_AUTH_RESULT = "authResult";
/** 报文体 认证结果状态 */
private final String MSG_SUCCESS = "success";
/** 报文体 认证错误码 */
private final String MSG_AUTH_MESSSAGE_CODE = "authMessageCode";
/** 报文体 认证错误描述 */
private final String MSG_AUTH_MESSSAGE_DESC = "authMessageDesc";
/*********************************************************************/
/**应用数据库用户名**/
private String username;
/**应用数据库真实姓名**/
private String realname;
/**应用数据库密码**/
private String password;
/**************************** 业务处理常量 ****************************/
/** 认证地址 */
private final String KEY_AUTHURL = "authURL";
/** 应用标识 */
private final String KEY_APP_ID = "appId";
/** session中原文 */
private final String KEY_ORIGINAL_DATA = "original_data";
/** 客户端返回的认证原文,request中原文 */
private final String KEY_ORIGINAL_JSP = "original_jsp";
/** 证书认证请求包 */
private final String KEY_SIGNED_DATA = "signed_data";
/*********************************************************************/
}
1,需要在服务器上配置和应用相关的ID,用于识别不同的应用。
2,需要安装吉大正元自带的驱动程序,否则无法识别key盘,还要使用object标签加载一个动态链接库,如上:
为了加载一个类,JITDSign可以直接使用类里面的方法。
3,在进行ajax提交的时候注意保持同步,否则可能出现不按顺序执行的情况。
关于文档和demo可以到http://115.com/file/c2gqtrxh下载。