shiro是一个很好的登录和权限管理框架,系统之前已做好shiro作为登录和权限控制,通过自定义实现realm实现用户名、密码验证登录。现在需要在此基础上实现微信小程序验证登录,也就是说需要再增加一套登录验证逻辑。了解下面内容之前,需要对shiro有一定的了解,知道是如何通过它来实现登录验证和权限控制的。
官网给出的登录逻辑图,挺好理解的。
通过小程序wx.login()获取到code,传给我们系统后台,系统后台再将这个code,和小程序appid+appsecret请求微信登录接口,得到微信用户唯一标识openid,到这一步为止,微信这块的登录已经走完验证了。得到了openid后,我们需要做的就是通过openid单点登录到我们系统。整体下来有两块内容要实现:小程序微信登录+shiro单点登录,shiro单点登录要解决的具体问题是,在原有账号密码登录验证前提下,再增加一套验证机制,支持两套验证,也就是多realm验证。
小程序传入后台服务code,请求微信服务器登录验证得到微信用户唯一标识openid
@Override
public WxLoginOutDto getWxLoginInfo(String code,String appId,String appSecret) throws Exception {
String result="";
try{//请求微信服务器,用code换取openid。HttpUtil是工具类
String url="https://api.weixin.qq.com/sns/jscode2session?appid="
+ appId + "&secret="
+ appSecret + "&js_code="
+ code
+ "&grant_type=authorization_code";
result = HttpUtil.doGet(url, null);
}
catch (Exception e) {
e.printStackTrace();
}
JSONObject jsonObj = JSONObject.parseObject(result);//解析从微信服务器上获取到的json字符串;
WxLoginOutDto wxOutDto=null;
String openId="";
String sessionKey="";
if(jsonObj.get("openid")!=null){
openId=jsonObj.get("openid").toString();
sessionKey=jsonObj.getString("session_key").toString();
wxOutDto=new WxLoginOutDto();
wxOutDto.setOpenId(openId);
wxOutDto.setSessionKey(sessionKey);
}else{
throw new Exception(jsonObj.toJSONString());
}
return wxOutDto;
}
首先,shiro是支持多realm的。查看shiro的ModularRealmAuthenticator可看到realm策略,当实现多个realm时,shiro执行的是多个realm权限验证。
多realm验证一共有三种策略:
1)AtLeastOneSuccessfulStrategy,只要有一个realm验证成功即可,与FirstSuccessfulStrategy区别是,将返回所有realm身份验证成功的认证信息。
2)FirstSuccessfulStrategy,只要有一个realm验证成功即可,只返回第一个realm身份验证成功的认证信息,其它忽略
3)AllSuccessfulStrategy:所有realm验证成功才算成功,且返回所有realm身份认证成功的认证信息,如果一个失败就失败了。
shiro默认使用的是AtLeastOneSuccessfulStrategy策略。符合我们的目标,微信单点认证成功就登录成功了。
(1) 实现微信openid单点登录的自定义realm(部分代码)
public class WechatRealm extends AuthorizingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
WeChatToken token = (WeChatToken) authcToken;
SUser userinfo=null;
try {
userinfo=userService.selectUserByOpenId(token.getOpenId());
userinfo.setSessionKey(token.getSessionKey());
}catch (UserNotExistException e){
throw new UnknownAccountException(e.getMessage(), e);
}catch (Exception e){
log.error("微信用户["+token.getPrincipal()+"]登录失败");
throw new AuthenticationException(e.getMessage(),e);
}
AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userinfo,null,this.getName());
return authenticationInfo;
}
}
(2)shiroConfig里增加新增的WechatRealm
@Configuration
public class ShiroConfig {
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm
List<Realm> realms =new ArrayList<Realm>();
realms.add(wechatRealm());
realms.add(myShiroRealm());
securityManager.setRealms(realms);
//记住我
securityManager.setRememberMeManager(rememberMeManager());
//session管理
securityManager.setSessionManager(sessionManager());
return securityManager;
}
}
这时候进行测试验证会出现如下错误,新定义的WeChatToken不能被任何已配置的realms所认证
org.apache.shiro.authc.AuthenticationException: Authentication token of type
[class com.borrowed.book.config.shiro.WeChatToken] could not be authenticated by any configured realms.
Please ensure that at least one realm can authenticate these tokens.
at org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy.afterAllAttempts(AtLeastOneSuccessfulStrategy.java:58)
at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doMultiRealmAuthentication(ModularRealmAuthenticator.java:241)
WeChatToken如下,用于存放微信认证返回的openid和sessionKey:
public class WeChatToken implements AuthenticationToken {
private String openId;
private String sessionKey;
public WeChatToken(String openId,String sessionKey){
this.openId=openId;
this.sessionKey=sessionKey;
}
@Override
public Object getPrincipal() {
return openId;
}
@Override
public Object getCredentials() {
return null;
}
}
解决办法:在新定义的WechatRealm里,重写supports方法,使其支持WeChatToken
/**
* 定义该Realm可以处理哪个类型的token
* @param token
* @return
*/
@Override
public boolean supports(AuthenticationToken token) {
return token!=null&&token instanceof WeChatToken;
}
到此为止,shiro下多realm认证完成。
代码较多,为全部贴出,有需要可留言联系。