添加微信公众号认证扩展
This commit is contained in:
parent
8718506433
commit
60053c83ef
|
@ -2,6 +2,7 @@ dependencies {
|
||||||
|
|
||||||
compile project(":diboot-core")
|
compile project(":diboot-core")
|
||||||
compile project(":diboot-shiro")
|
compile project(":diboot-shiro")
|
||||||
|
compile project(":diboot-shiro-wx-mp")
|
||||||
|
|
||||||
testCompile group: 'junit', name: 'junit', version: '4.12'
|
testCompile group: 'junit', name: 'junit', version: '4.12'
|
||||||
}
|
}
|
|
@ -1,11 +1,18 @@
|
||||||
package com.diboot.example.controller;
|
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.JsonResult;
|
||||||
import com.diboot.core.vo.Status;
|
import com.diboot.core.vo.Status;
|
||||||
import com.diboot.shiro.jwt.BaseJwtAuthenticationToken;
|
import com.diboot.shiro.jwt.BaseJwtAuthenticationToken;
|
||||||
import com.diboot.shiro.config.AuthType;
|
import com.diboot.shiro.config.AuthType;
|
||||||
import com.diboot.shiro.entity.SysUser;
|
import com.diboot.shiro.entity.SysUser;
|
||||||
import com.diboot.shiro.service.AuthWayService;
|
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.SecurityUtils;
|
||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -23,9 +30,14 @@ public class AuthTokenController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(AuthTokenController.class);
|
private static final Logger logger = LoggerFactory.getLogger(AuthTokenController.class);
|
||||||
|
|
||||||
|
private static final String STATE = BaseConfig.getProperty("wechat.state");
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Map<String, AuthWayService> authWayServiceMap;
|
private Map<String, AuthWayService> authWayServiceMap;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private WxMpServiceExt wxMpService;
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* 用户名密码登录接口
|
* 用户名密码登录接口
|
||||||
* @param sysUser
|
* @param sysUser
|
||||||
|
@ -57,6 +69,95 @@ public class AuthTokenController {
|
||||||
return new JsonResult(Status.FAIL_INVALID_TOKEN, errorMsg);
|
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")
|
@PostMapping("/logout")
|
||||||
public JsonResult logout(HttpServletRequest request, HttpServletResponse response) throws Exception{
|
public JsonResult logout(HttpServletRequest request, HttpServletResponse response) throws Exception{
|
||||||
Subject subject = SecurityUtils.getSubject();
|
Subject subject = SecurityUtils.getSubject();
|
||||||
|
@ -66,4 +167,5 @@ public class AuthTokenController {
|
||||||
|
|
||||||
return new JsonResult(Status.OK, new String[]{"退出登录成功"});
|
return new JsonResult(Status.OK, new String[]{"退出登录成功"});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
|
@ -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> {
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
|
@ -53,16 +53,7 @@ public class ShiroConfig {
|
||||||
//用户访问未对其授权的资源时的错误提示页面
|
//用户访问未对其授权的资源时的错误提示页面
|
||||||
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
|
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
|
||||||
|
|
||||||
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
|
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinition());
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// 设置过滤器
|
// 设置过滤器
|
||||||
Map<String, Filter> filters = new LinkedHashMap<>();
|
Map<String, Filter> filters = new LinkedHashMap<>();
|
||||||
|
@ -73,6 +64,22 @@ public class ShiroConfig {
|
||||||
return shiroFilterFactoryBean;
|
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生命周期处理器
|
* Shiro生命周期处理器
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -11,5 +11,6 @@ rootProject.name = 'diboot-v2'
|
||||||
include 'diboot-core'
|
include 'diboot-core'
|
||||||
include 'diboot-example'
|
include 'diboot-example'
|
||||||
include 'diboot-shiro'
|
include 'diboot-shiro'
|
||||||
|
include 'diboot-shiro-wx-mp'
|
||||||
include 'diboot-docs'
|
include 'diboot-docs'
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue