本笔记用于记录微信扫码登录过程中遇到的问题
环境背景
目的:React 嵌入 iframe 微信扫码登录
流程
- 登录页,模态窗嵌入 Cas
- 登录页,控件触发后,扫码登录。显示出二维码
- 登录页,扫码后,跳转到 cas-login
- 成功后,login 成功后,跳转到客户端服务器
- 成功后,客户端服务器,验证,并跳转到静态 h5 界面,使用 message 通知成功
- 成功后,监听 message,获取 token 重新获取用户信息
内容 | 版本 |
---|---|
Cas | 5.3.1 |
微信 | – |
springboot | 1.5.13 |
服务器筹备
域名 | 作用 |
---|---|
https://cas.demo.com | Cas 服务端 |
https://gateway.demo.com | SpringCloud 网关 |
https://gateway.demo.com/app/… | Cas 客户端(由网关进行负载) |
Cas 服务端配置
添加依赖
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-pac4j-webflow</artifactId>
<version>${cas.version}</version>
</dependency>
配置文件
application.properties
#WeChat OAuth Login
cas.authn.pac4j.oauth2[0].id=wxac0f1c863937d887
#由于微信的不为 clinet_id,为 appid
cas.authn.pac4j.oauth2[0].customParams.appid=wxac0f1c863937d887
#微信 scope 登录为 snsapi_login
cas.authn.pac4j.oauth2[0].customParams.scope=snsapi_login
cas.authn.pac4j.oauth2[0].secret=cceedc350fe15f45315a0ab67643085e
cas.authn.pac4j.oauth2[0].authUrl=https://open.weixin.qq.com/connect/qrconnect
#todo 可以添加中转程序来做这个事情
cas.authn.pac4j.oauth2[0].tokenUrl=https://gateway.demo.com/app/auth/weixin/access_token
cas.authn.pac4j.oauth2[0].profileUrl=https://gateway.demo.com/app/auth/weixin/userinfo
cas.authn.pac4j.oauth2[0].clientName=WeChat
配置文件
cas.properties
cas.server.prefix=https://cas.demo.com
or
--cas.server.prefix=https://cas.demo.com/cas
注:此处不允许使用端口,不然会显示参数错误
分布式部署配置
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-webapp-session-redis</artifactId>
<version>${cas.version}</version>
</dependency>
application.properties
cas.ticket.registry.redis.host=${spring.redis.host}
cas.ticket.registry.redis.database=10
cas.ticket.registry.redis.port=${spring.redis.port}
cas.ticket.registry.redis.password=${spring.redis.password}
cas.ticket.registry.redis.timeout=2000
cas.ticket.registry.redis.useSsl=false
cas.ticket.registry.redis.usePool=true
cas.ticket.registry.redis.pool.max-active=20
cas.ticket.registry.redis.pool.maxIdle=8
cas.ticket.registry.redis.pool.minIdle=0
cas.ticket.registry.redis.pool.maxActive=8
cas.ticket.registry.redis.pool.maxWait=-1
cas.ticket.registry.redis.pool.numTestsPerEvictionRun=0
cas.ticket.registry.redis.pool.softMinEvictableIdleTimeMillis=0
cas.ticket.registry.redis.pool.minEvictableIdleTimeMillis=0
cas.ticket.registry.redis.pool.lifo=true
cas.ticket.registry.redis.pool.fairness=false
# 设置 ticket 的加密算法,以保证分布式部署适合 ticket 结果一致
cas.tgc.crypto.encryption.key=${encryption_key}
cas.tgc.crypto.signing.key=${signing_key}
# session 缓存化
cas.webflow.autoconfigure=true
cas.webflow.alwaysPauseRedirect=false
cas.webflow.refresh=true
cas.webflow.redirectSameState=false
cas.webflow.session.lockTimeout=30
cas.webflow.session.compress=false
cas.webflow.session.maxConversations=5
cas.webflow.session.storage=true
srping.session.store-type=redis
Cas 客户端配置
POM.xml
<dependency>
<groupId>net.unicon.cas</groupId>
<artifactId>cas-client-autoconfig-support</artifactId>
<version>1.4.0-GA</version>
</dependency>
<dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-core</artifactId>
<version>3.5.0</version>
</dependency>
application.yml
cas:
#cas 服务端前缀,不是登录地址
server-url-prefix: https://cas.demo.com
#cas 的登录地址
server-login-url: https://cas.demo.com/login
#当前客户端地址
client-host-url: https://gateway.demo.com/app/auth
#指定 ticket 校验器
validation-type: CAS3
authenticationUrlPatterns:
- /cas/login
- /app/auth/cas/login
validationUrlPatterns:
- /cas/login
- /app/auth/cas/login
requestWrapperUrlPatterns:
- /cas/login
- /app/auth/cas/login
auth:
wechat:
appid: wxac0f1c863937d887
secret: cceedc350fe15f45315a0ab67643085e
access_token: https://api.weixin.qq.com/sns/oauth2/access_token
userinfo: https://api.wexin.qq.com/sns/userinfo
loginComplete: /loginComplete
service: https://gateway.demo.com/app/auth/cas/login
CasConfig
@Configuration
public class CasConfig extends CasClientConfiggurerAdapter {@Value("${auth.service}")
private String AUTH_SERVICE;
@Override
public void configureValidationFilter(FilterRegistrationBean validationFilter){super.configureValidationFilter(validationFilter);
validationFilter.getInitParameters().put("redirectAfterValidation","false");
validationFilter.getInitParameters().remove("serverName");
validationFilter.getInitParameters().put("service",AUTH_SERVICE);
}
}
ProxyController
@RestController
@RequestMapping("")
public class ProxyController {
@Autowired
private StringRedisTemplate stringRedisTemplate
@Value("${auth.loginComplete}")
private String AUTH_LOGIN_COMPLETE;
@Value("${auth.wechat.appid}")
private String AUTH_WECHAT_APPID;
@Value("${auth.wechat.secret}")
private String AUTH_WECHAT_SECRET;
@Value("${auth.wechat.access_token}")
private String AUTH_WECHAT_ACCESS_TOKEN;
@Value("${auth.wechat.userinfo}")
private String AUTH_WECHAT_USERINFO;
@PostMapping(value = "/weixin/access_token")
public Object weixinAccessToken(@RequestBody String body){Map<String, String> param=RequestUrlParamUtls.getParam(body);
StringBuidder url= new StringBuilder();
url.append(AUTH_WECHAT_ACCESS_TOKEN+"?");
url.append("appid="+AUTH_WECHAT_APPID);
url.append("&secret="+AUTH_WECHAT_SECRET);
url.append("&code="+param.get("code"));
url.append("&grant_type=authorization_code");
RestTemplate restTemplate = RequestUrlParamUtils.getInstance();
String weAppBody=restTemplate.postForObject(url.toString(),null,String.class);
String weChatToken="auth_weChat_"+JSON.parseObject(weAppBody).getString("access_token");
stringRedisTemplate.opsForValue().set(weChatToken,weAppBody,1,TimeUnit.HOURS);
return JSON.parseObject(weAppBody,Map.class);
}
@PostMapping(value = "/weixin/userinfo")
public Object weixinUserinfo(@RequestBody String body){String access_token=body.split("=")[1];
StringBuilder url=new StringBuilder();
String weChatToken="auth_weChat_"+access_token;
String weAppBody=stringRedisTemplate.opsForValue().get(weChatToken);
url.append(AUTH_WECHAT_USERINFO+"?");
url.append("access_token=");
url.append(access_token);
url.append("&openid=");
url.append(JSON.parseObject(weAppBody).getSAtring("openid"));
RestTemplate restTemplate = RequestUrlParamUtils.getInstance();
String infoBody=restTemplate.postForObject(url.toString(),null,String.class);
return JSON.parseObject(infoBody,Map.class);
}
@GetMapping(value = "/cas/login")
public Object casLogin(HttpServletRequest request,HttpServletResponse response){AttributePrincipal ap=AssertionHolder.getAssertion().getPrincipal();
// TODO: 这里可以拿到用户信息,持久化到数据库。(此处待补充 rest 连接 cas)StringBuilder url=new StringBuilder();
url.append(AUTH_LOGIN_COMPLETE);
// TODO:此处将返回信息写到参数里
response.sendRedirect(url.toString());
return null;
}
}
- 以上额外需要根据 token 获取用户信息的方式
实现效果
一、嵌入访问连接
https://cas.demo.com/clientredirect?client_name=WeChat&service=https://gateway.demo.com/app/auth/cas/login
二、定向到微信扫码页
https://open.weixin.qq.com/connet/grconect?xxxxxx
三、扫码完成,重定向回到 cas
https://cas.demo.com/login?client_name=WeChat&codexxx&state=xxx
四、cas 校验完成后重定向到客户端服务器
https://gateway.demo.com/app/auth/cas/login?ticket=xxx
五、客户端服务器重定向到静态页
https://gateway.demo.com/app/auth/login_complate?status=up&token=
会自动跳转到微信扫码授权界面,并填写完成回调 url
参考文章
https://gitee.com/Kawhi-Carl/sso
http://www.ibloger.net/articl…
http://www.cassso-china.cn/ap…