由于公司需要cas集成微信。但是在网上没有找到相应的示例。然后我就跑到官网上去找了一下CAS怎么集成OAuth的。下面的官网加上我自己的改动。
1、在cas-server-webapp中的pom.xml中加入以下dependency用于支持oauth.
[html] view plain copy
- <dependency>
- <groupId>org.jasig.casgroupId>
- <artifactId>cas-server-support-pac4jartifactId>
- <version>${cas.version}version>
- dependency>
2、在CAS service端为了把属性传递到CAS client端,我们需要在deployerConfigContext.xml文件中配置以下信息:
[html] view plain copy
- <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>
- ...
3、在cas-server-support-pac4j项目的pom.xml增加必需的pac4j-* libraries
[html] view plain copy
- <dependency>
- <groupId>org.pac4jgroupId>
- <artifactId>pac4j-oauthartifactId>
- <version>${pac4j.version}version>
- dependency>
4、在applicationContext.xml添加对应的oauth的clients。并把clients增加到对应的org.pac4j.core.client.Clients中,同样也是在applicationContext.xml
[html] view plain copy
- <bean 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>
- bean>
- <bean id="weiXin" class="org.jasig.cas.support.pac4j.plugin.weixin.WeiXinClient">
- <property name="key" value="yourkey" />
- <property name="secret" value="yousecret" />
- bean>
- <bean id="qq" class="org.jasig.cas.support.pac4j.plugin.qq.QqClient">
- <property name="key" value="yourkey" />
- <property name="secret" value="yousecret" />
- bean>
5、把处理oauth的client action添加到webflow中在login-webflow.xml中,这clientAction添加在webflow的最前面.它的任务是微信oauth用户验证的callback的调用.
[html] view plain copy
- <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.
clientAction需要用到的centralAuthenticationService这个bean已经之前的用户验证已经配置好了。
[html] view plain copy
- <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端。
[java] view plain copy
- 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;
- }
- }
7、最后为了添加用户验证在远程调用,也就是微信。必须添加以下链接到登录页面casLoginView.jsp(ClientNameUrl这个属性是被ClientAction自动创建).也就是你自定义的Client类名加上Url.如我创建的类为WeixinClient则对应的link名为WeixinClientUrl。
[html] view plain copy
- <a href="${WeiXinClientUrl}">Authenticate with Wechata> <br />
- <br />
- <a href="${QqClientUrl}">Authenticate with QQa><br />
8、以下的类是基于pac4j-oauth-1.4.1.jar包org.pac4j.oauth.client包中用于oauth 2.0协议与CAS集成类的对应与的。项目结构如下图所示:
1)用于处理CAS与微信的OAUTH通信。
[java] view plain copy
- 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
{ - 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
[java] view plain copy
- 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)、用于接收微信返回的用户信息
[java] view plain copy
- 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
[java] view plain copy
- 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与用户信息添加参数并请求微信[java] view plain copy
- 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)、用于添加返回用户信息
[java] view plain copy
- 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