cas sso 4.0 集成OAuth,用微信登陆示例

来源:互联网 发布:淘宝无线端链接转化 编辑:程序博客网 时间:2024/06/10 19:39

由于公司需要cas集成微信。但是在网上没有找到相应的示例。然后我就跑到官网上去找了一下CAS怎么集成OAuth的。下面的官网加上我自己的改动。


1、在cas-server-webapp中的pom.xml中加入以下dependency用于支持oauth.

[html] view plain copy
  1. <dependency>  
  2.     <groupId>org.jasig.cas</groupId>  
  3.     <artifactId>cas-server-support-pac4j</artifactId>  
  4.     <version>${cas.version}</version>  
  5. </dependency>  

2、在CAS service端为了把属性传递到CAS client端,我们需要在deployerConfigContext.xml文件中配置以下信息:

[html] view plain copy
  1. <bean id="serviceRegistryDao" class="org.jasig.cas.services.InMemoryServiceRegistryDaoImpl">  
  2.     <property name="registeredServices">  
  3.         <list>  
  4.             <bean class="org.jasig.cas.services.RegisteredServiceImpl">  
  5.                 <property name="id" value="0" />  
  6.                 <property name="name" value="HTTP" />  
  7.                 <property name="description" value="Only Allows HTTP Urls" />  
  8.                 <property name="serviceId" value="http://**" />  
  9.                 <property name="evaluationOrder" value="10000001" />  
  10.                 <property name="allowedAttributes">  
  11.                 <list>  
  12.                   <!-- weixin -->  
  13.                   <value>openid</value>  
  14.                   <value>nickname</value>  
  15.                   <value>and so on</value>  
  16.     ...  

3、在cas-server-support-pac4j项目的pom.xml增加必需的pac4j-* libraries

[html] view plain copy
  1. <dependency>  
  2.     <groupId>org.pac4j</groupId>  
  3.     <artifactId>pac4j-oauth</artifactId>  
  4.     <version>${pac4j.version}</version>  
  5. </dependency>  


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

[html] view plain copy
  1. <bean id="clients" class="org.pac4j.core.client.Clients">  
  2.     <property name="callbackUrl" value="https://login.nmall.com/cas/login" />  
  3.     <property name="clients">  
  4.         <list>  
  5.             <ref bean="weiXin" />  
  6.             <ref bean="qq" />  
  7.         </list>  
  8.     </property>  
  9. </bean>  
  10. <bean id="weiXin" class="org.jasig.cas.support.pac4j.plugin.weixin.WeiXinClient">  
  11.     <property name="key" value="yourkey" />  
  12.     <property name="secret" value="yousecret" />  
  13. </bean>  
  14. <bean id="qq" class="org.jasig.cas.support.pac4j.plugin.qq.QqClient">  
  15.     <property name="key" value="yourkey" />  
  16.     <property name="secret" value="yousecret" />  
  17. </bean>  


5、把处理oauth的client action添加到webflow中在login-webflow.xml中,这clientAction添加在webflow的最前面.它的任务是微信oauth用户验证的callback的调用.

[html] view plain copy
  1. <action-state id="clientAction">  
  2.     <evaluate expression="clientAction" />  
  3.     <transition on="success" to="sendTicketGrantingTicket" />  
  4.     <transition on="error" to="ticketGrantingTicketCheck" />  
  5.     <transition on="stop" to="stopWebflow" />  
  6. </action-state>  
  7. <view-state id="stopWebflow" />  

clientAction这个bean必须定义在cas-servlet.xml,并且需要注入clients.


[html] view plain copy
  1. <bean id="clientAction" class="org.jasig.cas.support.pac4j.web.flow.ClientAction">  
  2.     <constructor-arg index="0" ref="centralAuthenticationService"/>  
  3.     <constructor-arg index="1" ref="clients"/>  
  4. </bean  


clientAction需要用到的centralAuthenticationService这个bean已经之前的用户验证已经配置好了。


6、增加用户验证的handler与数据入口.


[html] view plain copy
  1. <bean id="authenticationManager" class="org.jasig.cas.authentication.PolicyBasedAuthenticationManager">  
  2.     <constructor-arg>  
  3.         <map>     
  4.             <entry key-ref="proxyAuthenticationHandler" value-ref="proxyPrincipalResolver" />  
  5.             <entry key-ref="primaryAuthenticationHandler" value-ref="primaryPrincipalResolver" />  
  6.             <entry key-ref="customerDBAuthHandler" value-ref="customerDBPrincipalResolver" />  
  7.         </map>  
  8.     </constructor-arg>  
  9.     <property name="authenticationMetaDataPopulators">  
  10.         <util:list>  
  11.            <bean class="org.jasig.cas.support.pac4j.authentication.ClientAuthenticationMetaDataPopulator" />  
  12.         </util:list>  
  13.     </property>  
  14.     <property name="authenticationPolicy">  
  15.         <bean class="org.jasig.cas.authentication.AnyAuthenticationPolicy" />  
  16.     </property>  
  17. </bean>  
  18. <bean id="primaryAuthenticationHandler" class="org.jasig.cas.support.pac4j.authentication.handler.support.ClientAuthenticationHandler">  
  19.     <constructor-arg index="0" ref="clients"/>  
  20. </bean>  
  21. <bean id="primaryPrincipalResolver" class="org.jasig.cas.support.pac4j.plugin.common.OauthPersonDirectoryPrincipalResolver" /  

这个OauthPersonDirectoryPrincipalResolver类是需要自己重写的,此类用于oauth微信返回的用户信息处理返回给cas client端。


[java] view plain copy
  1. package org.jasig.cas.support.pac4j.plugin.common;  
  2.   
  3. import java.util.Map;  
  4.   
  5. import org.jasig.cas.authentication.Credential;  
  6. import org.jasig.cas.authentication.principal.Principal;  
  7. import org.jasig.cas.authentication.principal.PrincipalResolver;  
  8. import org.jasig.cas.authentication.principal.SimplePrincipal;  
  9. import org.jasig.cas.support.pac4j.authentication.principal.ClientCredential;  
  10. import org.pac4j.core.profile.UserProfile;  
  11. import org.slf4j.Logger;  
  12. import org.slf4j.LoggerFactory;  
  13.   
  14. public class OauthPersonDirectoryPrincipalResolver implements PrincipalResolver {  
  15.   
  16.     /** Log instance. */  
  17.     protected final Logger logger = LoggerFactory.getLogger(this.getClass());  
  18.   
  19.     private boolean returnNullIfNoAttributes = false;  
  20.   
  21.     public void setReturnNullIfNoAttributes(final boolean returnNullIfNoAttributes) {  
  22.         this.returnNullIfNoAttributes = returnNullIfNoAttributes;  
  23.     }  
  24.   
  25.     @Override  
  26.     public Principal resolve(Credential credential) {  
  27.         logger.debug("Attempting to resolve a principal...");  
  28.   
  29.         if (credential instanceof ClientCredential){  
  30.             // do nothing  
  31.         } else {  
  32.             throw new RuntimeException("用户数据转换异常!");  
  33.         }  
  34.   
  35.         ClientCredential oauthCredential = (ClientCredential) credential;  
  36.   
  37.         String principalId = oauthCredential.getUserProfile().getId();  
  38.   
  39.         if (principalId == null) {  
  40.             logger.debug("Got null for extracted principal ID; returning null.");  
  41.             return null;  
  42.         }  
  43.   
  44.         logger.debug("Creating SimplePrincipal for [{}]", principalId);  
  45.         UserProfile userProfile = oauthCredential.getUserProfile();  
  46.         final Map<String, Object> attributes = userProfile.getAttributes();  
  47.   
  48.         if (attributes == null & !this.returnNullIfNoAttributes) {  
  49.             return new SimplePrincipal(principalId);  
  50.         }  
  51.   
  52.         if (attributes == null) {  
  53.             return null;  
  54.         }  
  55.   
  56.         return new SimplePrincipal(principalId, attributes);  
  57.     }  
  58.   
  59.     @Override  
  60.     public boolean supports(Credential credential) {  
  61.         return true;  
  62.     }  
  63.   
  64. }  


7、最后为了添加用户验证在远程调用,也就是微信。必须添加以下链接到登录页面casLoginView.jsp(ClientNameUrl这个属性是被ClientAction自动创建).也就是你自定义的Client类名加上Url.如我创建的类为WeixinClient则对应的link名为WeixinClientUrl。


[html] view plain copy
  1. <a href="${WeiXinClientUrl}">Authenticate with Wechat</a> <br />  
  2. <br />  
  3. <a href="${QqClientUrl}">Authenticate with QQ</a><br />  


8、以下的类是基于pac4j-oauth-1.4.1.jar包org.pac4j.oauth.client包中用于oauth 2.0协议与CAS集成类的对应与的。项目结构如下图所示:

1111


1)用于处理CAS与微信的OAUTH通信。

[java] view plain copy
  1. package org.jasig.cas.support.pac4j.plugin.weixin;  
  2.   
  3. import org.pac4j.core.client.BaseClient;  
  4. import org.pac4j.core.context.WebContext;  
  5. import org.pac4j.core.exception.HttpCommunicationException;  
  6. import org.pac4j.oauth.client.BaseOAuth20Client;  
  7. import org.pac4j.oauth.credentials.OAuthCredentials;  
  8. import org.pac4j.oauth.profile.JsonHelper;  
  9. import org.scribe.model.OAuthConfig;  
  10. import org.scribe.model.ProxyOAuthRequest;  
  11. import org.scribe.model.Response;  
  12. import org.scribe.model.SignatureType;  
  13. import org.scribe.model.Token;  
  14. import org.springframework.beans.factory.annotation.Autowired;  
  15.   
  16. import com.fasterxml.jackson.databind.JsonNode;  
  17. import com.lm.b2c.core.enumerate.LoginKeyType;  
  18. import com.lm.b2c.core.lang.Pair;  
  19. import com.lm.b2c.user.manager.customer.api.CustomerManager;  
  20. import com.lm.b2c.user.manager.customer.vo.CombinedAccountVO;  
  21.   
  22. /** 
  23.  * 此类用于处理CAS与微信的OAUTH通信 
  24.  * @author b2c021 
  25.  * 
  26.  */  
  27. public class WeiXinClient extends BaseOAuth20Client<WeiXinProfile> {  
  28.   
  29.     private final static WeiXinAttributesDefinition WEI_XIN_ATTRIBUTES = new WeiXinAttributesDefinition();  
  30.   
  31.     @Autowired  
  32.     private CustomerManager customerManager;  
  33.   
  34.     public WeiXinClient(){}  
  35.   
  36.     public WeiXinClient(final String key, final String secret){  
  37.         setKey(key);  
  38.         setSecret(secret);  
  39.     }  
  40.   
  41.     @Override  
  42.     protected BaseClient<OAuthCredentials, WeiXinProfile> newClient() {  
  43.         // TODO  
  44.         WeiXinClient newClient = new WeiXinClient();  
  45.         return newClient;  
  46.     }  
  47.   
  48.     @Override  
  49.     protected void internalInit() {  
  50.         // TODO  
  51.         super.internalInit();  
  52.         WeiXinApi20 api = new WeiXinApi20();  
  53.         this.service = new WeiXinOAuth20ServiceImpl(api, new OAuthConfig(this.key, this.secret, this.callbackUrl,SignatureType.Header, nullnull),  
  54.                                                                         this.connectTimeout, this.readTimeout, this.proxyHost,this.proxyPort);  
  55.     }  
  56.   
  57.     @Override  
  58.     protected String getProfileUrl() {  
  59.         // TODO Auto-generated method stub  
  60.         // eg.google2Client:return "https://www.googleapis.com/oauth2/v2/userinfo";  
  61.         return "https://api.weixin.qq.com/sns/userinfo";  
  62.     }  
  63.   
  64.     @Override  
  65.     protected WeiXinProfile extractUserProfile(String body) {  
  66.         WeiXinProfile weiXinProfile = new WeiXinProfile();  
  67.         final JsonNode json = JsonHelper.getFirstNode(body);  
  68.         if (null != json) {  
  69.             for(final String attribute : WEI_XIN_ATTRIBUTES.getPrincipalAttributes()){  
  70.                 weiXinProfile.addAttribute(attribute, JsonHelper.get(json, attribute));  
  71.             }  
  72.   
  73.             /** 绑定账号到系统 */  
  74.             String openId = (String) weiXinProfile.getAttributes().get("openid");  
  75.             String nickName = (String) weiXinProfile.getAttributes().get("nickname");  
  76.             CombinedAccountVO combinedAccount = generateAccount(openId, LoginKeyType.WECHAT, nickName);  
  77.             Pair<Long,String> suidAndLoginName = customerManager.bindAccount(combinedAccount);  
  78.             weiXinProfile.addAttribute("suid", suidAndLoginName.getFirst());  
  79.             weiXinProfile.setId(suidAndLoginName.getSecond());  
  80.         }  
  81.         return weiXinProfile;  
  82.     }  
  83.   
  84.     /** 
  85.      * 需求state元素 
  86.      */  
  87.     @Override  
  88.     protected boolean requiresStateParameter() {  
  89.         return false;  
  90.     }  
  91.   
  92.     @Override // Cancelled 取消  
  93.     protected boolean hasBeenCancelled(WebContext context) {  
  94.         return false;  
  95.     }  
  96.   
  97.     @Override  
  98.     protected String sendRequestForData(final Token accessToken, final String dataUrl) {  
  99.         logger.debug("accessToken : {} / dataUrl : {}", accessToken, dataUrl);  
  100.         final long t0 = System.currentTimeMillis();  
  101.         final ProxyOAuthRequest request = createProxyRequest(dataUrl);  
  102.         this.service.signRequest(accessToken, request);  
  103.   
  104.         final Response response = request.send();  
  105.         final int code = response.getCode();  
  106.         final String body = response.getBody();  
  107.         final long t1 = System.currentTimeMillis();  
  108.         logger.debug("Request took : " + (t1 - t0) + " ms for : " + dataUrl);  
  109.         logger.debug("response code : {} / response body : {}", code, body);  
  110.         if (code != 200) {  
  111.             logger.error("Failed to get data, code : " + code + " / body : " + body);  
  112.             throw new HttpCommunicationException(code, body);  
  113.         }  
  114.         return body;  
  115.     }  
  116.   
  117.     private CombinedAccountVO generateAccount(String openId,LoginKeyType keyType, String nickName){  
  118.         CombinedAccountVO vo = new CombinedAccountVO();  
  119.         vo.setLoginKey(openId);  
  120.         vo.setKeyType(keyType);  
  121.         vo.setNickName(nickName);  
  122.         return vo;  
  123.     }  
  124. }  


2)、用于定义获取微信返回的CODE与ACCESSTOKEN

[java] view plain copy
  1. package org.jasig.cas.support.pac4j.plugin.weixin;  
  2.   
  3. import org.scribe.builder.api.DefaultApi20;  
  4. import org.scribe.extractors.AccessTokenExtractor;  
  5. import org.scribe.extractors.JsonTokenExtractor;  
  6. import org.scribe.model.OAuthConfig;  
  7. import org.scribe.model.Verb;  
  8. import org.scribe.utils.OAuthEncoder;  
  9.   
  10. /** 
  11.  * 用于定义获取微信返回的CODE与ACCESS_TOKEN 
  12.  * @author b2c021 
  13.  * 
  14.  */  
  15. public class WeiXinApi20 extends DefaultApi20 {  
  16.   
  17.     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";  
  18.   
  19.     @Override  
  20.     public AccessTokenExtractor getAccessTokenExtractor()  
  21.     {  
  22.       return new JsonTokenExtractor();  
  23.     }  
  24.   
  25.     @Override  
  26.     public Verb getAccessTokenVerb()  
  27.     {  
  28.       return Verb.POST;  
  29.     }  
  30.   
  31.     @Override  
  32.     public String getAccessTokenEndpoint() {  
  33.         return "https://api.weixin.qq.com/sns/oauth2/access_token";  
  34.     }  
  35.   
  36.     @Override  
  37.     public String getAuthorizationUrl(OAuthConfig config) {  
  38.         return String.format(WEIXIN_AUTHORIZE_URL, config.getApiKey(), OAuthEncoder.encode(config.getCallback()));  
  39.     }  
  40.   
  41. }  


3)、用于接收微信返回的用户信息

[java] view plain copy
  1. package org.jasig.cas.support.pac4j.plugin.weixin;  
  2.   
  3. import org.pac4j.core.profile.converter.Converters;  
  4. import org.pac4j.oauth.profile.OAuthAttributesDefinition;  
  5.   
  6. /** 
  7.  * 用于接收微信返回的用户信息 
  8.  * @author b2c021 
  9.  * 
  10.  */  
  11. public class WeiXinAttributesDefinition extends OAuthAttributesDefinition {  
  12.   
  13.     public static final String OPEN_ID = "openid";  
  14.     public static final String NICK_NAME = "nickname";  
  15.     /** 用户性别,1为男性,2为女性 */  
  16.     public static final String SEX = "sex";  
  17.     public static final String COUNTRY = "country";  
  18.     public static final String PROVINCE = "province";  
  19.     public static final String CITY = "city";  
  20.     public static final String HEAD_IMG_URL = "headimgurl";  
  21.     public static final String PRIVILEGE = "privilege";  
  22.     public static final String UNION_ID = "unionid";  
  23.     // appended  
  24.     public static final String APP_NAME = "appName";  
  25.     public static final String SUID = "suid";  
  26.   
  27.     public WeiXinAttributesDefinition(){  
  28.         addAttribute(OPEN_ID, Converters.stringConverter);  
  29.         addAttribute(NICK_NAME, Converters.stringConverter);  
  30.         addAttribute(SEX, Converters.integerConverter);  
  31.         addAttribute(COUNTRY, Converters.stringConverter);  
  32.         addAttribute(PROVINCE, Converters.stringConverter);  
  33.         addAttribute(CITY, Converters.stringConverter);  
  34.         addAttribute(HEAD_IMG_URL, Converters.stringConverter);  
  35.         addAttribute(UNION_ID, Converters.stringConverter);  
  36.         addAttribute(APP_NAME, Converters.stringConverter);  
  37.         addAttribute(SUID, Converters.longConverter);  
  38.     }  
  39. }  

4)、用于获取微信返回的ACCESS_TOKEN


[java] view plain copy
  1. package org.jasig.cas.support.pac4j.plugin.weixin;  
  2.   
  3. import java.util.regex.Matcher;  
  4. import java.util.regex.Pattern;  
  5.   
  6. import org.scribe.exceptions.OAuthException;  
  7. import org.scribe.model.Token;  
  8. import org.scribe.utils.Preconditions;  
  9.   
  10. /** 
  11.  * 用于获取微信返回的ACCESS_TOKEN 
  12.  * @author b2c021 
  13.  * 
  14.  */  
  15. public class WeiXinJsonTokenExtractor {  
  16.   
  17.     private Pattern accessTokenPattern = Pattern.compile("\"access_token\":\\s*\"(\\S*?)\"");  
  18.   
  19.     public Token extract(String response){  
  20.         Preconditions.checkEmptyString(response, "Cannot extract a token from a null or empty String");  
  21.         Matcher matcher = accessTokenPattern.matcher(response);  
  22.         if(matcher.find()){  
  23.             return new Token(matcher.group(1), "", response);  
  24.         }  
  25.         else{  
  26.             throw new OAuthException("Cannot extract an acces token. Response was: " + response);  
  27.         }  
  28.     }  
  29.   
  30. }  

5)、用于添加获取ACCESS_TOKEN与用户信息添加参数并请求微信
[java] view plain copy
  1. package org.jasig.cas.support.pac4j.plugin.weixin;  
  2.   
  3. import java.util.regex.Matcher;  
  4. import java.util.regex.Pattern;  
  5.   
  6. import org.scribe.builder.api.DefaultApi20;  
  7. import org.scribe.exceptions.OAuthException;  
  8. import org.scribe.model.OAuthConfig;  
  9. import org.scribe.model.OAuthConstants;  
  10. import org.scribe.model.OAuthRequest;  
  11. import org.scribe.model.ProxyOAuthRequest;  
  12. import org.scribe.model.Response;  
  13. import org.scribe.model.Token;  
  14. import org.scribe.model.Verifier;  
  15. import org.scribe.oauth.ProxyOAuth20ServiceImpl;  
  16.   
  17. /** 
  18.  * 用于添加获取ACCESS_TOKEN与用户信息添加参数并请求微信 
  19.  * @author b2c021 
  20.  * 
  21.  */  
  22. public class WeiXinOAuth20ServiceImpl extends ProxyOAuth20ServiceImpl {  
  23.   
  24.   
  25.     private static Pattern openIdPattern = Pattern.compile("\"openid\":\\s*\"(\\S*?)\"");  
  26.   
  27.     public WeiXinOAuth20ServiceImpl(DefaultApi20 api, OAuthConfig config, int connectTimeout, int readTimeout, String proxyHost, int proxyPort) {  
  28.         super(api, config, connectTimeout, readTimeout, proxyHost, proxyPort);  
  29.     }  
  30.   
  31.     /** 
  32.      * 获取account_token的http请求参数添加 
  33.      */  
  34.     @Override  
  35.     public Token getAccessToken(final Token requestToken, final Verifier verifier) {  
  36.         final OAuthRequest request = new ProxyOAuthRequest(this.api.getAccessTokenVerb(),  
  37.                                                            this.api.getAccessTokenEndpoint(), this.connectTimeout,  
  38.                                                            this.readTimeout, this.proxyHost, this.proxyPort);  
  39.         request.addBodyParameter("appid"this.config.getApiKey());  
  40.         request.addBodyParameter("secret"this.config.getApiSecret());  
  41.         request.addBodyParameter(OAuthConstants.CODE, verifier.getValue());  
  42.         request.addBodyParameter(OAuthConstants.REDIRECT_URI, this.config.getCallback());  
  43.         request.addBodyParameter("grant_type""authorization_code");  
  44.         final Response response = request.send();  
  45.         return this.api.getAccessTokenExtractor().extract(response.getBody());  
  46.     }  
  47.   
  48.     @Override  
  49.     public void signRequest(final Token accessToken, final OAuthRequest request) {  
  50.         request.addQuerystringParameter(OAuthConstants.ACCESS_TOKEN, accessToken.getToken());  
  51.         String response = accessToken.getRawResponse();  
  52.         Matcher matcher = openIdPattern.matcher(response);  
  53.         if(matcher.find()){  
  54.             request.addQuerystringParameter("openid", matcher.group(1));  
  55.         }  
  56.         else{  
  57.             throw new OAuthException("微信接口返回数据miss openid: " + response);  
  58.         }  
  59.     }  
  60. }  

6)、用于添加返回用户信息

[java] view plain copy
  1. package org.jasig.cas.support.pac4j.plugin.weixin;  
  2.   
  3. import org.pac4j.core.profile.AttributesDefinition;  
  4. import org.pac4j.oauth.profile.OAuth20Profile;  
  5.   
  6. /** 
  7.  * 用于添加返回用户信息 
  8.  * @author b2c021 
  9.  * 
  10.  */  
  11. public class WeiXinProfile extends OAuth20Profile {  
  12.   
  13.     private static final long serialVersionUID = -7969484323692570444L;  
  14.   
  15.     @Override  
  16.     protected AttributesDefinition getAttributesDefinition() {  
  17.         return new WeiXinAttributesDefinition();  
  18.     }  
  19.   
  20. }  


引用地址:http://jasig.github.io/cas/4.0.x/integration/Delegate-Authentication.html
原创粉丝点击