乐趣区

Cas5-springcloud-微信扫码登录

本笔记用于记录微信扫码登录过程中遇到的问题

环境背景

目的: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…

退出移动版