CAS SSO 4.0.x 集成OAuth(微信登陆示例)

由于公司需要cas集成微信。但是在网上没有找到相应的示例。然后我就跑到官网上去找了一下CAS怎么集成OAuth的。这是基于pac4j-oauth-1.4.1.jar包org.pac4j.oauth.client包中用于oauth 2.0协议与CAS集成类的对应与的。下面是对应的Client,毕竟是人家外国人的jar。没有微信的,那就只有自己写咯。
这里写图片描述
下面的官网加上我自己的改动。

1、在cas-server-webapp中的pom.xml中加入以下dependency用于支持oauth.
<dependency>  
    <groupId>org.jasig.cas</groupId>  
    <artifactId>cas-server-support-pac4j</artifactId>  
    <version>${cas.version}</version>  
</dependency>  
2、配置传递属性

在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>  
                  <!-- weixin -->  
                  <value>openid</value>  
                  <value>nickname</value>  
                  <value>and so on</value>  
    ... 
3、在cas-server-support-pac4j项目的pom.xml增加必需的pac4j-* libraries
<dependency>  
    <groupId>org.pac4j</groupId>  
    <artifactId>pac4j-oauth</artifactId>  
    <version>${pac4j.version}</version>  
</dependency>  
4、修改applicationContext.xml

在applicationContext.xml添加对应的oauth的clients。并把clients增加到对应的org.pac4j.core.client.Clients中,同样也是在applicationContext.xml

<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、修改login-webflow.xml,增加oauth用户验证逻辑

把处理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已经之前的用户验证已经配置好了。

6、增加用户验证的handler与数据入口.
<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<String, Object> 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。

<a href="${WeiXinClientUrl}">Authenticate with Wechat</a> <br />  
<br />  
<a href="${QqClientUrl}">Authenticate with QQ</a><br />  
8、修改cas-server-support-pac4j(重点)

以上都是准备工作,下面才是真正的工作。因为在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<OAuthCredentials, WeiXinProfile> 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<Long,String> 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

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值