由于公司需要cas集成微信。但是在网上没有找到相应的示例。然后我就跑到官网上去找了一下CAS怎么集成OAuth的。这是基于pac4j-oauth-1.4.1.jar包org.pac4j.oauth.client包中用于oauth 2.0协议与CAS集成类的对应与的。下面是对应的Client,毕竟是人家外国人的jar。没有微信的,那就只有自己写咯。
下面的官网加上我自己的改动。
<dependency>
<groupId>org.jasig.casgroupId>
<artifactId>cas-server-support-pac4jartifactId>
<version>${cas.version}version>
dependency>
在CAS service端为了把属性传递到CAS client端,我们需要在deployerConfigContext.xml文件中配置以下信息:
<bean id="serviceRegistryDao" class="org.jasig.cas.services.InMemoryServiceRegistryDaoImpl">
<property name="registeredServices">
<list>
<bean class="org.jasig.cas.services.RegisteredServiceImpl">
<property name="id" value="0" />
<property name="name" value="HTTP" />
<property name="description" value="Only Allows HTTP Urls" />
<property name="serviceId" value="http://**" />
<property name="evaluationOrder" value="10000001" />
<property name="allowedAttributes">
<list>
<value>openidvalue>
<value>nicknamevalue>
<value>and so onvalue>
...
<dependency>
<groupId>org.pac4jgroupId>
<artifactId>pac4j-oauthartifactId>
<version>${pac4j.version}version>
dependency>
在applicationContext.xml添加对应的oauth的clients。并把clients增加到对应的org.pac4j.core.client.Clients中,同样也是在applicationContext.xml
id="clients" class="org.pac4j.core.client.Clients">
<property name="callbackUrl" value="https://login.nmall.com/cas/login" />
<property name="clients">
<list>
<ref bean="weiXin" />
<ref bean="qq" />
list>
property>
id="weiXin" class="org.jasig.cas.support.pac4j.plugin.weixin.WeiXinClient">
<property name="key" value="yourkey" />
<property name="secret" value="yousecret" />
id="qq" class="org.jasig.cas.support.pac4j.plugin.qq.QqClient">
<property name="key" value="yourkey" />
<property name="secret" value="yousecret" />
把处理oauth的client action添加到webflow中在login-webflow.xml中,这clientAction添加在webflow的最前面.它的任务是微信oauth用户验证的callback的调用.
<action-state id="clientAction">
<evaluate expression="clientAction" />
<transition on="success" to="sendTicketGrantingTicket" />
<transition on="error" to="ticketGrantingTicketCheck" />
<transition on="stop" to="stopWebflow" />
action-state>
<view-state id="stopWebflow" />
clientAction这个bean必须定义在cas-servlet.xml,并且需要注入clients.
<bean id="clientAction" class="org.jasig.cas.support.pac4j.web.flow.ClientAction">
<constructor-arg index="0" ref="centralAuthenticationService"/>
<constructor-arg index="1" ref="clients"/>
bean
clientAction需要用到的centralAuthenticationService这个bean已经之前的用户验证已经配置好了。
<bean id="authenticationManager" class="org.jasig.cas.authentication.PolicyBasedAuthenticationManager">
<constructor-arg>
<map>
<entry key-ref="proxyAuthenticationHandler" value-ref="proxyPrincipalResolver" />
<entry key-ref="primaryAuthenticationHandler" value-ref="primaryPrincipalResolver" />
<entry key-ref="customerDBAuthHandler" value-ref="customerDBPrincipalResolver" />
map>
constructor-arg>
<property name="authenticationMetaDataPopulators">
<util:list>
<bean class="org.jasig.cas.support.pac4j.authentication.ClientAuthenticationMetaDataPopulator" />
util:list>
property>
<property name="authenticationPolicy">
<bean class="org.jasig.cas.authentication.AnyAuthenticationPolicy" />
property>
bean>
<bean id="primaryAuthenticationHandler" class="org.jasig.cas.support.pac4j.authentication.handler.support.ClientAuthenticationHandler">
<constructor-arg index="0" ref="clients"/>
bean>
<bean id="primaryPrincipalResolver" class="org.jasig.cas.support.pac4j.plugin.common.OauthPersonDirectoryPrincipalResolver" / >
这个OauthPersonDirectoryPrincipalResolver类是需要自己重写的,此类用于oauth微信返回的用户信息处理返回给cas client端。
package org.jasig.cas.support.pac4j.plugin.common;
import java.util.Map;
import org.jasig.cas.authentication.Credential;
import org.jasig.cas.authentication.principal.Principal;
import org.jasig.cas.authentication.principal.PrincipalResolver;
import org.jasig.cas.authentication.principal.SimplePrincipal;
import org.jasig.cas.support.pac4j.authentication.principal.ClientCredential;
import org.pac4j.core.profile.UserProfile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OauthPersonDirectoryPrincipalResolver implements PrincipalResolver {
/** Log instance. */
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
private boolean returnNullIfNoAttributes = false;
public void setReturnNullIfNoAttributes(final boolean returnNullIfNoAttributes) {
this.returnNullIfNoAttributes = returnNullIfNoAttributes;
}
@Override
public Principal resolve(Credential credential) {
logger.debug("Attempting to resolve a principal...");
if (credential instanceof ClientCredential){
// do nothing
} else {
throw new RuntimeException("用户数据转换异常!");
}
ClientCredential oauthCredential = (ClientCredential) credential;
String principalId = oauthCredential.getUserProfile().getId();
if (principalId == null) {
logger.debug("Got null for extracted principal ID; returning null.");
return null;
}
logger.debug("Creating SimplePrincipal for [{}]", principalId);
UserProfile userProfile = oauthCredential.getUserProfile();
final Map attributes = userProfile.getAttributes();
if (attributes == null & !this.returnNullIfNoAttributes) {
return new SimplePrincipal(principalId);
}
if (attributes == null) {
return null;
}
return new SimplePrincipal(principalId, attributes);
}
@Override
public boolean supports(Credential credential) {
return true;
}
}
最后为了添加用户验证在远程调用,也就是微信。必须添加以下链接到登录页面casLoginView.jsp(ClientNameUrl这个属性是被ClientAction自动创建).也就是你自定义的Client类名加上Url.如我创建的类为WeixinClient则对应的link名为WeixinClientUrl。
<a href="${WeiXinClientUrl}">Authenticate with Wechata> <br />
<br />
<a href="${QqClientUrl}">Authenticate with QQa><br />
以上都是准备工作,下面才是真正的工作。因为在pac4j-oauth-1.4.1.jar这个包里面没有对weixin的相关支持所有只有自己手动修改了。以下的类是基于pac4j-oauth-1.4.1.jar包org.pac4j.oauth.client包中用于oauth 2.0协议与CAS集成类的对应与的。项目结构如下图所示:
1)用于处理CAS与微信的OAUTH通信。
package org.jasig.cas.support.pac4j.plugin.weixin;
import org.pac4j.core.client.BaseClient;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.exception.HttpCommunicationException;
import org.pac4j.oauth.client.BaseOAuth20Client;
import org.pac4j.oauth.credentials.OAuthCredentials;
import org.pac4j.oauth.profile.JsonHelper;
import org.scribe.model.OAuthConfig;
import org.scribe.model.ProxyOAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.SignatureType;
import org.scribe.model.Token;
import org.springframework.beans.factory.annotation.Autowired;
import com.fasterxml.jackson.databind.JsonNode;
import com.lm.b2c.core.enumerate.LoginKeyType;
import com.lm.b2c.core.lang.Pair;
import com.lm.b2c.user.manager.customer.api.CustomerManager;
import com.lm.b2c.user.manager.customer.vo.CombinedAccountVO;
/**
* 此类用于处理CAS与微信的OAUTH通信
* @author b2c021
*
*/
public class WeiXinClient extends BaseOAuth20Client<WeiXinProfile> {
private final static WeiXinAttributesDefinition WEI_XIN_ATTRIBUTES = new WeiXinAttributesDefinition();
@Autowired
private CustomerManager customerManager;
public WeiXinClient(){}
public WeiXinClient(final String key, final String secret){
setKey(key);
setSecret(secret);
}
@Override
protected BaseClient newClient() {
// TODO
WeiXinClient newClient = new WeiXinClient();
return newClient;
}
@Override
protected void internalInit() {
// TODO
super.internalInit();
WeiXinApi20 api = new WeiXinApi20();
this.service = new WeiXinOAuth20ServiceImpl(api, new OAuthConfig(this.key, this.secret, this.callbackUrl,SignatureType.Header, null, null),
this.connectTimeout, this.readTimeout, this.proxyHost,this.proxyPort);
}
@Override
protected String getProfileUrl() {
// TODO Auto-generated method stub
// eg.google2Client:return "https://www.googleapis.com/oauth2/v2/userinfo";
return "https://api.weixin.qq.com/sns/userinfo";
}
@Override
protected WeiXinProfile extractUserProfile(String body) {
WeiXinProfile weiXinProfile = new WeiXinProfile();
final JsonNode json = JsonHelper.getFirstNode(body);
if (null != json) {
for(final String attribute : WEI_XIN_ATTRIBUTES.getPrincipalAttributes()){
weiXinProfile.addAttribute(attribute, JsonHelper.get(json, attribute));
}
/** 绑定账号到系统 */
String openId = (String) weiXinProfile.getAttributes().get("openid");
String nickName = (String) weiXinProfile.getAttributes().get("nickname");
CombinedAccountVO combinedAccount = generateAccount(openId, LoginKeyType.WECHAT, nickName);
Pair suidAndLoginName = customerManager.bindAccount(combinedAccount);
weiXinProfile.addAttribute("suid", suidAndLoginName.getFirst());
weiXinProfile.setId(suidAndLoginName.getSecond());
}
return weiXinProfile;
}
/**
* 需求state元素
*/
@Override
protected boolean requiresStateParameter() {
return false;
}
@Override // Cancelled 取消
protected boolean hasBeenCancelled(WebContext context) {
return false;
}
@Override
protected String sendRequestForData(final Token accessToken, final String dataUrl) {
logger.debug("accessToken : {} / dataUrl : {}", accessToken, dataUrl);
final long t0 = System.currentTimeMillis();
final ProxyOAuthRequest request = createProxyRequest(dataUrl);
this.service.signRequest(accessToken, request);
final Response response = request.send();
final int code = response.getCode();
final String body = response.getBody();
final long t1 = System.currentTimeMillis();
logger.debug("Request took : " + (t1 - t0) + " ms for : " + dataUrl);
logger.debug("response code : {} / response body : {}", code, body);
if (code != 200) {
logger.error("Failed to get data, code : " + code + " / body : " + body);
throw new HttpCommunicationException(code, body);
}
return body;
}
private CombinedAccountVO generateAccount(String openId,LoginKeyType keyType, String nickName){
CombinedAccountVO vo = new CombinedAccountVO();
vo.setLoginKey(openId);
vo.setKeyType(keyType);
vo.setNickName(nickName);
return vo;
}
}
2)、用于定义获取微信返回的CODE与ACCESSTOKEN
package org.jasig.cas.support.pac4j.plugin.weixin;
import org.scribe.builder.api.DefaultApi20;
import org.scribe.extractors.AccessTokenExtractor;
import org.scribe.extractors.JsonTokenExtractor;
import org.scribe.model.OAuthConfig;
import org.scribe.model.Verb;
import org.scribe.utils.OAuthEncoder;
/**
* 用于定义获取微信返回的CODE与ACCESS_TOKEN
* @author b2c021
*
*/
public class WeiXinApi20 extends DefaultApi20 {
private static final String WEIXIN_AUTHORIZE_URL = "https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_login#wechat_redirect";
@Override
public AccessTokenExtractor getAccessTokenExtractor()
{
return new JsonTokenExtractor();
}
@Override
public Verb getAccessTokenVerb()
{
return Verb.POST;
}
@Override
public String getAccessTokenEndpoint() {
return "https://api.weixin.qq.com/sns/oauth2/access_token";
}
@Override
public String getAuthorizationUrl(OAuthConfig config) {
return String.format(WEIXIN_AUTHORIZE_URL, config.getApiKey(), OAuthEncoder.encode(config.getCallback()));
}
}
3)、用于接收微信返回的用户信息
package org.jasig.cas.support.pac4j.plugin.weixin;
import org.pac4j.core.profile.converter.Converters;
import org.pac4j.oauth.profile.OAuthAttributesDefinition;
/**
* 用于接收微信返回的用户信息
* @author b2c021
*
*/
public class WeiXinAttributesDefinition extends OAuthAttributesDefinition {
public static final String OPEN_ID = "openid";
public static final String NICK_NAME = "nickname";
/** 用户性别,1为男性,2为女性 */
public static final String SEX = "sex";
public static final String COUNTRY = "country";
public static final String PROVINCE = "province";
public static final String CITY = "city";
public static final String HEAD_IMG_URL = "headimgurl";
public static final String PRIVILEGE = "privilege";
public static final String UNION_ID = "unionid";
// appended
public static final String APP_NAME = "appName";
public static final String SUID = "suid";
public WeiXinAttributesDefinition(){
addAttribute(OPEN_ID, Converters.stringConverter);
addAttribute(NICK_NAME, Converters.stringConverter);
addAttribute(SEX, Converters.integerConverter);
addAttribute(COUNTRY, Converters.stringConverter);
addAttribute(PROVINCE, Converters.stringConverter);
addAttribute(CITY, Converters.stringConverter);
addAttribute(HEAD_IMG_URL, Converters.stringConverter);
addAttribute(UNION_ID, Converters.stringConverter);
addAttribute(APP_NAME, Converters.stringConverter);
addAttribute(SUID, Converters.longConverter);
}
}
4)、用于获取微信返回的ACCESS_TOKEN
package org.jasig.cas.support.pac4j.plugin.weixin;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.scribe.exceptions.OAuthException;
import org.scribe.model.Token;
import org.scribe.utils.Preconditions;
/**
* 用于获取微信返回的ACCESS_TOKEN
* @author b2c021
*
*/
public class WeiXinJsonTokenExtractor {
private Pattern accessTokenPattern = Pattern.compile("\"access_token\":\\s*\"(\\S*?)\"");
public Token extract(String response){
Preconditions.checkEmptyString(response, "Cannot extract a token from a null or empty String");
Matcher matcher = accessTokenPattern.matcher(response);
if(matcher.find()){
return new Token(matcher.group(1), "", response);
}
else{
throw new OAuthException("Cannot extract an acces token. Response was: " + response);
}
}
}
5)、用于添加获取ACCESS_TOKEN与用户信息添加参数并请求微信
package org.jasig.cas.support.pac4j.plugin.weixin;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.scribe.builder.api.DefaultApi20;
import org.scribe.exceptions.OAuthException;
import org.scribe.model.OAuthConfig;
import org.scribe.model.OAuthConstants;
import org.scribe.model.OAuthRequest;
import org.scribe.model.ProxyOAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.Token;
import org.scribe.model.Verifier;
import org.scribe.oauth.ProxyOAuth20ServiceImpl;
/**
* 用于添加获取ACCESS_TOKEN与用户信息添加参数并请求微信
* @author b2c021
*
*/
public class WeiXinOAuth20ServiceImpl extends ProxyOAuth20ServiceImpl {
private static Pattern openIdPattern = Pattern.compile("\"openid\":\\s*\"(\\S*?)\"");
public WeiXinOAuth20ServiceImpl(DefaultApi20 api, OAuthConfig config, int connectTimeout, int readTimeout, String proxyHost, int proxyPort) {
super(api, config, connectTimeout, readTimeout, proxyHost, proxyPort);
}
/**
* 获取account_token的http请求参数添加
*/
@Override
public Token getAccessToken(final Token requestToken, final Verifier verifier) {
final OAuthRequest request = new ProxyOAuthRequest(this.api.getAccessTokenVerb(),
this.api.getAccessTokenEndpoint(), this.connectTimeout,
this.readTimeout, this.proxyHost, this.proxyPort);
request.addBodyParameter("appid", this.config.getApiKey());
request.addBodyParameter("secret", this.config.getApiSecret());
request.addBodyParameter(OAuthConstants.CODE, verifier.getValue());
request.addBodyParameter(OAuthConstants.REDIRECT_URI, this.config.getCallback());
request.addBodyParameter("grant_type", "authorization_code");
final Response response = request.send();
return this.api.getAccessTokenExtractor().extract(response.getBody());
}
@Override
public void signRequest(final Token accessToken, final OAuthRequest request) {
request.addQuerystringParameter(OAuthConstants.ACCESS_TOKEN, accessToken.getToken());
String response = accessToken.getRawResponse();
Matcher matcher = openIdPattern.matcher(response);
if(matcher.find()){
request.addQuerystringParameter("openid", matcher.group(1));
}
else{
throw new OAuthException("微信接口返回数据miss openid: " + response);
}
}
}
6)、用于添加返回用户信息
package org.jasig.cas.support.pac4j.plugin.weixin;
import org.pac4j.core.profile.AttributesDefinition;
import org.pac4j.oauth.profile.OAuth20Profile;
/**
* 用于添加返回用户信息
* @author b2c021
*
*/
public class WeiXinProfile extends OAuth20Profile {
private static final long serialVersionUID = -7969484323692570444L;
@Override
protected AttributesDefinition getAttributesDefinition() {
return new WeiXinAttributesDefinition();
}
}
引用地址:http://jasig.github.io/cas/4.0.x/integration/Delegate-Authentication.html