shiro+微信小程序单点登录

背景

shiro是一个很好的登录和权限管理框架,系统之前已做好shiro作为登录和权限控制,通过自定义实现realm实现用户名、密码验证登录。现在需要在此基础上实现微信小程序验证登录,也就是说需要再增加一套登录验证逻辑。了解下面内容之前,需要对shiro有一定的了解,知道是如何通过它来实现登录验证和权限控制的。

整体登录逻辑

官网给出的登录逻辑图,挺好理解的。
shiro+微信小程序单点登录_第1张图片
通过小程序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是支持多realm的。查看shiro的ModularRealmAuthenticator可看到realm策略,当实现多个realm时,shiro执行的是多个realm权限验证。
shiro+微信小程序单点登录_第2张图片

多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认证完成。

代码较多,为全部贴出,有需要可留言联系。

你可能感兴趣的:(java,小程序,shiro)