添加微信公众号认证扩展

This commit is contained in:
godchao 2019-06-22 12:08:27 +08:00
parent 8718506433
commit 60053c83ef
12 changed files with 350 additions and 10 deletions

View File

@ -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'
}

View File

@ -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<String, AuthWayService> 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[]{"退出登录成功"});
}
}

23
diboot-shiro-wx-mp/build.gradle Executable file
View File

@ -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'
}

View File

@ -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;
}

View File

@ -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<WxMpMember> {
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "./mybatis-3-mapper.dtd">
<mapper namespace="com.diboot.shiro.wx.mp.mapper.WxMpMemberMapper">
</mapper>

View File

@ -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<WxMpMember> {
}

View File

@ -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<String, Object> context, WxMpService wxMpService, WxSessionManager sessionManager) throws WxErrorException {
logger.info("\n接收到 {} 公众号请求消息,内容:{}", wxMpService.getWxMpConfigStorage().getAppId(), wxMpXmlMessage);
return null;
}
}).next();
}
public WxMpMessageRouter getWxMpMessageRouter(){
return wxMpMessageRouter;
}
}

View File

@ -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<WxMpMember> query = new QueryWrapper();
query.lambda()
.eq(WxMpMember::getOpenid, token.getAccount());
List<WxMpMember> 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;
}
}

View File

@ -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<WxMpMemberMapper, WxMpMember> implements WxMpMemberService {
}

View File

@ -53,16 +53,7 @@ public class ShiroConfig {
//用户访问未对其授权的资源时的错误提示页面
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
Map<String, String> 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<String, Filter> filters = new LinkedHashMap<>();
@ -73,6 +64,22 @@ public class ShiroConfig {
return shiroFilterFactoryBean;
}
@Bean
public Map filterChainDefinition(){
Map<String, String> 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生命周期处理器
*/

View File

@ -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'