From 60053c83ef39c566dd009bd4faf88557baa927d3 Mon Sep 17 00:00:00 2001 From: godchao Date: Sat, 22 Jun 2019 12:08:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=BE=AE=E4=BF=A1=E5=85=AC?= =?UTF-8?q?=E4=BC=97=E5=8F=B7=E8=AE=A4=E8=AF=81=E6=89=A9=E5=B1=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- diboot-example/build.gradle | 1 + .../controller/AuthTokenController.java | 102 ++++++++++++++++++ diboot-shiro-wx-mp/build.gradle | 23 ++++ .../diboot/shiro/wx/mp/entity/WxMpMember.java | 20 ++++ .../shiro/wx/mp/mapper/WxMpMemberMapper.java | 14 +++ .../shiro/wx/mp/mapper/WxMpMemberMapper.xml | 5 + .../wx/mp/service/WxMpMemberService.java | 13 +++ .../shiro/wx/mp/service/WxMpServiceExt.java | 61 +++++++++++ .../service/impl/WxMpAuthWayServiceImpl.java | 74 +++++++++++++ .../service/impl/WxMpMemberServiceImpl.java | 19 ++++ .../com/diboot/shiro/config/ShiroConfig.java | 27 +++-- settings.gradle | 1 + 12 files changed, 350 insertions(+), 10 deletions(-) create mode 100755 diboot-shiro-wx-mp/build.gradle create mode 100755 diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/entity/WxMpMember.java create mode 100755 diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/mapper/WxMpMemberMapper.java create mode 100755 diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/mapper/WxMpMemberMapper.xml create mode 100755 diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/service/WxMpMemberService.java create mode 100755 diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/service/WxMpServiceExt.java create mode 100755 diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/service/impl/WxMpAuthWayServiceImpl.java create mode 100755 diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/service/impl/WxMpMemberServiceImpl.java diff --git a/diboot-example/build.gradle b/diboot-example/build.gradle index 1663ce7..02fd3c5 100644 --- a/diboot-example/build.gradle +++ b/diboot-example/build.gradle @@ -2,6 +2,7 @@ dependencies { compile project(":diboot-core") compile project(":diboot-shiro") + compile project(":diboot-shiro-wx-mp") testCompile group: 'junit', name: 'junit', version: '4.12' } \ No newline at end of file diff --git a/diboot-example/src/main/java/com/diboot/example/controller/AuthTokenController.java b/diboot-example/src/main/java/com/diboot/example/controller/AuthTokenController.java index d4c05f2..7c31a53 100644 --- a/diboot-example/src/main/java/com/diboot/example/controller/AuthTokenController.java +++ b/diboot-example/src/main/java/com/diboot/example/controller/AuthTokenController.java @@ -1,11 +1,18 @@ package com.diboot.example.controller; +import com.diboot.core.config.BaseConfig; +import com.diboot.core.util.V; import com.diboot.core.vo.JsonResult; import com.diboot.core.vo.Status; import com.diboot.shiro.jwt.BaseJwtAuthenticationToken; import com.diboot.shiro.config.AuthType; import com.diboot.shiro.entity.SysUser; import com.diboot.shiro.service.AuthWayService; +import com.diboot.shiro.util.JwtHelper; +import com.diboot.shiro.wx.mp.service.WxMpServiceExt; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; +import me.chanjar.weixin.mp.bean.result.WxMpUser; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; @@ -23,9 +30,14 @@ public class AuthTokenController { private static final Logger logger = LoggerFactory.getLogger(AuthTokenController.class); + private static final String STATE = BaseConfig.getProperty("wechat.state"); + @Autowired private Map authWayServiceMap; + @Autowired + private WxMpServiceExt wxMpService; + /*** * 用户名密码登录接口 * @param sysUser @@ -57,6 +69,95 @@ public class AuthTokenController { return new JsonResult(Status.FAIL_INVALID_TOKEN, errorMsg); } + + /** + * 创建OAuth认证链接(为了获取授权所需code) + * @param request + * @return + * @throws Exception + */ + @GetMapping("/buildOAuthUrl") + public JsonResult buildOAuthUrl4mp(HttpServletRequest request) throws Exception{ + String url = request.getParameter("url"); + if (V.isEmpty(url)){ + return new JsonResult(Status.FAIL_OPERATION, new String[]{"url为空,获取OAuth链接失败"}); + } + + String oauthUrl = wxMpService.oauth2buildAuthorizationUrl(url, WxConsts.OAuth2Scope.SNSAPI_USERINFO, STATE); + return new JsonResult(Status.OK, oauthUrl, new String[]{"获取OAuth链接成功"}); + } + + /** + * 微信公众号的回调授权登录认证 + * @param request + * @return + * @throws Exception + */ + @PostMapping("/apply") + public JsonResult applyTokenByOAuth2mp(HttpServletRequest request) throws Exception{ + String code = request.getParameter("code"); + String state = request.getParameter("state"); + String openid = ""; + if (JwtHelper.isRequestTokenEffective(request)){ + String account = JwtHelper.getAccountFromToken(JwtHelper.getRequestToken(request)); + if (account == null){ + // 如果有code并且token已过期,则使用code获取openid + if (V.isEmpty(code)){ + return new JsonResult(Status.FAIL_INVALID_TOKEN, new String[]{"token已过期"}); + } + } else { + openid = account; + } + } + + // 如果openid没有通过token获取到,则通过code获取 + if (V.isEmpty(openid)){ + // 校验STATE + if (V.notEmpty(STATE) && !STATE.equals(state)){ + return new JsonResult(Status.FAIL_OPERATION, new String[]{"非法来源"}); + } + // 获取wxMpService + WxMpOAuth2AccessToken wxMpOAuth2AccessToken = wxMpService.oauth2getAccessToken(code); + if (!wxMpService.oauth2validateAccessToken(wxMpOAuth2AccessToken)){ + wxMpOAuth2AccessToken = wxMpService.oauth2refreshAccessToken(wxMpOAuth2AccessToken.getRefreshToken()); + } + WxMpUser wxMpUser = wxMpService.oauth2getUserInfo(wxMpOAuth2AccessToken, null); + + openid = wxMpUser.getOpenId(); + } + + // 如果没有获取到wechat,则返回提示信息 + if (V.isEmpty(openid)){ + return new JsonResult(Status.FAIL_INVALID_TOKEN, new String[]{"获取信息失败"}); + } + + // 设置token + BaseJwtAuthenticationToken authToken = new BaseJwtAuthenticationToken(authWayServiceMap, openid, AuthType.WX_MP); + // 获取当前的Subject + Subject subject = SecurityUtils.getSubject(); + String token = null; + String errorMsg = null; + + try { + subject.login(authToken); + //验证是否登录成功 + if(subject.isAuthenticated()){ + token = (String)authToken.getCredentials(); + logger.debug("openid[" + openid + "]申请token成功!authtoken="+token); + } + } + catch(Exception e){ + logger.error("登录失败", e); + } + + if (V.isEmpty(token)){ + String msg = V.notEmpty(errorMsg) ? errorMsg : "申请token失败"; + return new JsonResult(Status.FAIL_INVALID_TOKEN, new String[]{msg}); + } + + return new JsonResult(Status.OK, token, new String[]{"申请token成功"}); + } + @PostMapping("/logout") public JsonResult logout(HttpServletRequest request, HttpServletResponse response) throws Exception{ Subject subject = SecurityUtils.getSubject(); @@ -66,4 +167,5 @@ public class AuthTokenController { return new JsonResult(Status.OK, new String[]{"退出登录成功"}); } + } diff --git a/diboot-shiro-wx-mp/build.gradle b/diboot-shiro-wx-mp/build.gradle new file mode 100755 index 0000000..b749ebe --- /dev/null +++ b/diboot-shiro-wx-mp/build.gradle @@ -0,0 +1,23 @@ +plugins { + id 'java' +} + +group 'com.diboot' +version '2.0-alpha' + +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + + compile project(":diboot-core") + compile project(":diboot-shiro") + + // 微信开发组件 + compile("com.github.binarywang:weixin-java-mp:3.2.0") + + testCompile group: 'junit', name: 'junit', version: '4.12' +} diff --git a/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/entity/WxMpMember.java b/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/entity/WxMpMember.java new file mode 100755 index 0000000..e17ae49 --- /dev/null +++ b/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/entity/WxMpMember.java @@ -0,0 +1,20 @@ +package com.diboot.shiro.wx.mp.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.diboot.core.entity.BaseEntity; +import lombok.Data; + +/*** + * @author Wangyl + * @version v2.0 + * @date 2019/6/10 + */ +@Data +public class WxMpMember extends BaseEntity { + + private static final long serialVersionUID = -106928701430810778L; + + @TableField + private String openid; + +} diff --git a/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/mapper/WxMpMemberMapper.java b/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/mapper/WxMpMemberMapper.java new file mode 100755 index 0000000..a57f832 --- /dev/null +++ b/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/mapper/WxMpMemberMapper.java @@ -0,0 +1,14 @@ +package com.diboot.shiro.wx.mp.mapper; + +import com.diboot.core.mapper.BaseCrudMapper; +import com.diboot.shiro.wx.mp.entity.WxMpMember; + +/*** + * @author Wangyl + * @version v2.0 + * @date 2019/6/10 + */ +public interface WxMpMemberMapper extends BaseCrudMapper { + +} + diff --git a/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/mapper/WxMpMemberMapper.xml b/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/mapper/WxMpMemberMapper.xml new file mode 100755 index 0000000..d9de206 --- /dev/null +++ b/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/mapper/WxMpMemberMapper.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/service/WxMpMemberService.java b/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/service/WxMpMemberService.java new file mode 100755 index 0000000..4fd804a --- /dev/null +++ b/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/service/WxMpMemberService.java @@ -0,0 +1,13 @@ +package com.diboot.shiro.wx.mp.service; + +import com.diboot.core.service.BaseService; +import com.diboot.shiro.wx.mp.entity.WxMpMember; + +/*** + * @author Wangyl + * @version v2.0 + * @date 2019/6/10 + */ +public interface WxMpMemberService extends BaseService { + +} diff --git a/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/service/WxMpServiceExt.java b/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/service/WxMpServiceExt.java new file mode 100755 index 0000000..bd51d6e --- /dev/null +++ b/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/service/WxMpServiceExt.java @@ -0,0 +1,61 @@ +package com.diboot.shiro.wx.mp.service; + +import com.diboot.core.config.BaseConfig; +import com.diboot.core.util.V; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage; +import me.chanjar.weixin.mp.api.WxMpMessageHandler; +import me.chanjar.weixin.mp.api.WxMpMessageRouter; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.util.Map; + +@Service +public class WxMpServiceExt extends WxMpServiceImpl { + private static final Logger logger = LoggerFactory.getLogger(WxMpServiceExt.class); + + private WxMpMessageRouter wxMpMessageRouter; + + private static final String appId = BaseConfig.getProperty("wechat.appId"); + private static final String secret = BaseConfig.getProperty("wechat.secret"); + private static final String token = BaseConfig.getProperty("wechat.token"); + private static final String aesKey = BaseConfig.getProperty("wechat.aesKey"); + + @PostConstruct + public void init(){ + WxMpInMemoryConfigStorage inMemoryConfigStorage = new WxMpInMemoryConfigStorage(); + + if (V.isEmpty(appId) || V.isEmpty(secret)){ + logger.warn("服务号相关的appid或secret为空,请检查application.properties配置文件"); + } + + inMemoryConfigStorage.setAppId(appId); + inMemoryConfigStorage.setSecret(secret); + inMemoryConfigStorage.setToken(token); + inMemoryConfigStorage.setAesKey(aesKey); + + setWxMpConfigStorage(inMemoryConfigStorage); + + wxMpMessageRouter = new WxMpMessageRouter(this); + wxMpMessageRouter.rule().handler(new WxMpMessageHandler() { + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map context, WxMpService wxMpService, WxSessionManager sessionManager) throws WxErrorException { + logger.info("\n接收到 {} 公众号请求消息,内容:{}", wxMpService.getWxMpConfigStorage().getAppId(), wxMpXmlMessage); + return null; + } + }).next(); + } + + public WxMpMessageRouter getWxMpMessageRouter(){ + return wxMpMessageRouter; + } + +} diff --git a/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/service/impl/WxMpAuthWayServiceImpl.java b/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/service/impl/WxMpAuthWayServiceImpl.java new file mode 100755 index 0000000..8f7a329 --- /dev/null +++ b/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/service/impl/WxMpAuthWayServiceImpl.java @@ -0,0 +1,74 @@ +package com.diboot.shiro.wx.mp.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.diboot.core.entity.BaseEntity; +import com.diboot.core.util.V; +import com.diboot.shiro.config.AuthType; +import com.diboot.shiro.jwt.BaseJwtAuthenticationToken; +import com.diboot.shiro.service.AuthWayService; +import com.diboot.shiro.wx.mp.entity.WxMpMember; +import com.diboot.shiro.wx.mp.service.WxMpMemberService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/*** + * 微信公众号认证实现 + * @author Wangyl + * @version v2.0 + * @date 2019/6/10 + */ +@Service +public class WxMpAuthWayServiceImpl implements AuthWayService { + + @Autowired + private WxMpMemberService wxMpMemberService; + + private AuthType authType = AuthType.WX_MP; + + private BaseJwtAuthenticationToken token; + + @Override + public AuthType authType() { + return authType; + } + + @Override + public void initByToken(BaseJwtAuthenticationToken token) { + this.token = token; + } + + @Override + public BaseEntity getUser() { + QueryWrapper query = new QueryWrapper(); + query.lambda() + .eq(WxMpMember::getOpenid, token.getAccount()); + + List wxMpMemberList = wxMpMemberService.getEntityList(query); + if (V.isEmpty(wxMpMemberList)){ + return null; + } + return wxMpMemberList.get(0); + } + + @Override + public boolean requirePassword() { + return authType.isRequirePassword(); + } + + @Override + public boolean isPasswordMatch() { + return true; + } + + @Override + public boolean isPreliminaryVerified() { + return false; + } + + @Override + public Long getExpiresInMinutes() { + return null; + } +} diff --git a/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/service/impl/WxMpMemberServiceImpl.java b/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/service/impl/WxMpMemberServiceImpl.java new file mode 100755 index 0000000..cf29c44 --- /dev/null +++ b/diboot-shiro-wx-mp/src/main/java/com/diboot/shiro/wx/mp/service/impl/WxMpMemberServiceImpl.java @@ -0,0 +1,19 @@ +package com.diboot.shiro.wx.mp.service.impl; + +import com.diboot.core.service.impl.BaseServiceImpl; +import com.diboot.shiro.wx.mp.entity.WxMpMember; +import com.diboot.shiro.wx.mp.mapper.WxMpMemberMapper; +import com.diboot.shiro.wx.mp.service.WxMpMemberService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/*** + * @author Wangyl + * @version v2.0 + * @date 2019/6/10 + */ +@Service +@Slf4j +public class WxMpMemberServiceImpl extends BaseServiceImpl implements WxMpMemberService { + +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java b/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java index 681eed0..6eeac91 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java @@ -53,16 +53,7 @@ public class ShiroConfig { //用户访问未对其授权的资源时的错误提示页面 shiroFilterFactoryBean.setUnauthorizedUrl("/error"); - Map filterChainDefinitionMap = new LinkedHashMap<>(); - - filterChainDefinitionMap.put("/", "anon"); - filterChainDefinitionMap.put("/static/**", "anon"); - filterChainDefinitionMap.put("/auth/login", "anon"); - filterChainDefinitionMap.put("/error", "anon"); - filterChainDefinitionMap.put("/auth/logout", "logout"); - filterChainDefinitionMap.put("/**", "jwt"); - - shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); + shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinition()); // 设置过滤器 Map filters = new LinkedHashMap<>(); @@ -73,6 +64,22 @@ public class ShiroConfig { return shiroFilterFactoryBean; } + @Bean + public Map filterChainDefinition(){ + Map filterChainDefinitionMap = new LinkedHashMap<>(); + + filterChainDefinitionMap.put("/", "anon"); + filterChainDefinitionMap.put("/static/**", "anon"); + filterChainDefinitionMap.put("/auth/login", "anon"); + filterChainDefinitionMap.put("/auth/buildOAuthUrl", "anon"); + filterChainDefinitionMap.put("/auth/apply", "anon"); + filterChainDefinitionMap.put("/error", "anon"); + filterChainDefinitionMap.put("/auth/logout", "logout"); + filterChainDefinitionMap.put("/**", "jwt"); + + return filterChainDefinitionMap; + } + /** * Shiro生命周期处理器 */ diff --git a/settings.gradle b/settings.gradle index c167669..ddeadc4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,5 +11,6 @@ rootProject.name = 'diboot-v2' include 'diboot-core' include 'diboot-example' include 'diboot-shiro' +include 'diboot-shiro-wx-mp' include 'diboot-docs'