1.流程图
本地认证服务需要:
- 定义接口,接受微信下发的授权码
- 受到授权码以后,调用微信的接口申请令牌
- 接受到令牌以后,申请获得用户的信息
- 获取用户信息以后将信息存储到本地的数据库
- 重定向到浏览器登录界面,自动登录
注意:由于微信属于外网服务,所以要添加穿透器。
2.定义接口
2.1定义controller
调用service去查询用户的信息
package com.xuecheng.auth.controller;
import com.xuecheng.ucenter.model.po.XcUser;
import com.xuecheng.ucenter.service.impl.WxAuthServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
/**
* @author YCL
* @version 1.0
* @description 微信登录接口
* @date 2023-02-28 7:54
*/
@Slf4j
@Controller
public class WxLoginController {
@Autowired
WxAuthServiceImpl wxAuthService;
@PostMapping("/wxLogin")
public String wxLogin(String code, String state) {
//拿授权码申请令牌。查询用户
XcUser xcUser = wxAuthService.wxAuth(code);
if(xcUser == null){
//重定向到错误页面
return "redirect:http://www.xuecheng-plus.com/error.html";
}
String username = xcUser.getName();
return "redirect:http://www.xuecheng-plus.com/sign.html?username="+username+"&authType=wx";
}
}
2.2定义service
1.拿着授权码去请求微信获取令牌
//请求微信获取令牌
/**
* 微信接口响应结果
* {
* "access_token":"ACCESS_TOKEN",
* "expires_in":7200,
* "refresh_token":"REFRESH_TOKEN",
* "openid":"OPENID",
* "scope":"SCOPE",
* "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
* }
*/
private Map<String, String> getAccss_token(String code){
String appid = "appid";
String serect = "serect";
String url_template = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
String url = String.format(url_template, appid, serect, code);
//请求微信获取令牌
ResponseEntity<String> exchange = restTemplate.exchange(url, HttpMethod.POST,
null, String.class);
//得到相应串
String body = exchange.getBody();
Map map = JSON.parseObject(body, Map.class);
return map;
}
2.携带令牌去查询用户信息
//携带令牌查询用户信息
//http请求方式: GET
//https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID
/**
{
"openid":"OPENID",
"nickname":"NICKNAME",
"sex":1,
"province":"PROVINCE",
"city":"CITY",
"country":"COUNTRY",
"headimgurl": "https://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
"privilege":[
"PRIVILEGE1",
"PRIVILEGE2"
],
"unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
*/
private Map<String,String> getUserInfo(String access_token, String open_id){
String url_template = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s";
String url = String.format(url_template, access_token, open_id);
ResponseEntity<String> exchange = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
String body = exchange.getBody();
//结果转换成map
Map map = JSON.parseObject(body, Map.class);
return map;
}
3. 写入数据,将数据写入到本地数据库中
@Transactional
public XcUser addWxUser(Map userInfo_map){
//先去除unionid
String unionid = (String) userInfo_map.get("unionid");
XcUser xcUser = xcUserMapper.selectOne(new LambdaQueryWrapper<XcUser>().
eq(XcUser::getWxUnionid, unionid));
if (xcUser != null){
return xcUser;
}
xcUser = new XcUser();
String id = UUID.randomUUID().toString();
xcUser.setId(id);
xcUser.setWxUnionid(unionid);
//记录从微信得到的昵称
xcUser.setNickname(userInfo_map.get("nickname").toString());
xcUser.setUserpic(userInfo_map.get("headimgurl").toString());
xcUser.setName(userInfo_map.get("nickname").toString());
xcUser.setUsername(unionid);
xcUser.setPassword(unionid);
xcUser.setUtype("101001");//学生类型
xcUser.setStatus("1");//用户状态
xcUser.setCreateTime(LocalDateTime.now());
xcUserMapper.insert(xcUser);
XcUserRole xcUserRole = new XcUserRole();
xcUserRole.setId(UUID.randomUUID().toString());
xcUserRole.setUserId(id);
xcUserRole.setRoleId("17");//学生角色
userRoleMapper.insert(xcUserRole);
return xcUser;
}
4.重写execute方法,查询本地数据库中的数据
/***
* @description 微信认证方法
* @param authParamsDto
* @return
* @author
* @date
*/
@Override
public XcUserExt execute(AuthParamsDto authParamsDto) {
String username = authParamsDto.getUsername();
XcUser xcUser = xcUserMapper.selectOne(new LambdaQueryWrapper<XcUser>()
.eq(XcUser::getName, username
));
if(xcUser == null){
throw new RuntimeException("微信授权过程的用户不存在");
}
XcUserExt xcUserExt = new XcUserExt();
BeanUtils.copyProperties(xcUser,xcUserExt);
return xcUserExt;
}
5.授权方法的调用函数
注意调用存储到本地数据库的代码的时候,由于数据库方法加了@Transactional,所以是事务,而调用函数本身并不是事务控制,所以要在service中注入自己为代理,防止事务失效,注入自己的代码如下:(关于事务的详细解释在spring专栏中)
@Autowired
WxAuthServiceImpl agent;
public XcUser wxAuth(String code){
//拿着授权码code申请令牌,查询用户
Map<String, String> accss_token = getAccss_token(code);
//拿着令牌查询用户信息
String access_token = accss_token.get("access_token");
String openid = accss_token.get("openid");
Map<String, String> userInfo = getUserInfo(access_token, openid);
//添加用户到数据库
XcUser xcUser = agent.addWxUser(userInfo);
return xcUser;
}
2.3 service完整代码
package com.xuecheng.ucenter.service.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xuecheng.ucenter.mapper.XcUserMapper;
import com.xuecheng.ucenter.mapper.XcUserRoleMapper;
import com.xuecheng.ucenter.model.dto.AuthParamsDto;
import com.xuecheng.ucenter.model.dto.XcUserExt;
import com.xuecheng.ucenter.model.po.XcUser;
import com.xuecheng.ucenter.model.po.XcUserRole;
import com.xuecheng.ucenter.service.AuthService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.UUID;
/**
* @author YCL
* @version 1.0
* @description 微信登录认证
* @date 2023-02-28 7:42
*/
@Service("wx_authservice")
@Slf4j
public class WxAuthServiceImpl implements AuthService {
@Autowired
XcUserMapper xcUserMapper;
@Autowired
RestTemplate restTemplate;
@Autowired
XcUserRoleMapper userRoleMapper;
@Autowired
WxAuthServiceImpl agent;
public XcUser wxAuth(String code){
//拿着授权码code申请令牌,查询用户
Map<String, String> accss_token = getAccss_token(code);
//拿着令牌查询用户信息
String access_token = accss_token.get("access_token");
String openid = accss_token.get("openid");
Map<String, String> userInfo = getUserInfo(access_token, openid);
//添加用户到数据库
XcUser xcUser = agent.addWxUser(userInfo);
return xcUser;
}
/***
* @description 微信认证方法
* @param authParamsDto
* @return
* @author
* @date
*/
@Override
public XcUserExt execute(AuthParamsDto authParamsDto) {
String username = authParamsDto.getUsername();
XcUser xcUser = xcUserMapper.selectOne(new LambdaQueryWrapper<XcUser>()
.eq(XcUser::getName, username
));
if(xcUser == null){
throw new RuntimeException("微信授权过程的用户不存在");
}
XcUserExt xcUserExt = new XcUserExt();
BeanUtils.copyProperties(xcUser,xcUserExt);
return xcUserExt;
}
//请求微信获取令牌
/**
* 微信接口响应结果
* {
* "access_token":"ACCESS_TOKEN",
* "expires_in":7200,
* "refresh_token":"REFRESH_TOKEN",
* "openid":"OPENID",
* "scope":"SCOPE",
* "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
* }
*/
private Map<String, String> getAccss_token(String code){
String appid = "appid";
String serect = "serect";
String url_template = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
String url = String.format(url_template, appid, serect, code);
//请求微信获取令牌
ResponseEntity<String> exchange = restTemplate.exchange(url, HttpMethod.POST,
null, String.class);
//得到相应串
String body = exchange.getBody();
Map map = JSON.parseObject(body, Map.class);
return map;
}
//携带令牌查询用户信息
//http请求方式: GET
//https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID
/**
{
"openid":"OPENID",
"nickname":"NICKNAME",
"sex":1,
"province":"PROVINCE",
"city":"CITY",
"country":"COUNTRY",
"headimgurl": "https://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
"privilege":[
"PRIVILEGE1",
"PRIVILEGE2"
],
"unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
*/
private Map<String,String> getUserInfo(String access_token, String open_id){
String url_template = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s";
String url = String.format(url_template, access_token, open_id);
ResponseEntity<String> exchange = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
String body = exchange.getBody();
//结果转换成map
Map map = JSON.parseObject(body, Map.class);
return map;
}
@Transactional
public XcUser addWxUser(Map userInfo_map){
//先去除unionid
String unionid = (String) userInfo_map.get("unionid");
XcUser xcUser = xcUserMapper.selectOne(new LambdaQueryWrapper<XcUser>().
eq(XcUser::getWxUnionid, unionid));
if (xcUser != null){
return xcUser;
}
xcUser = new XcUser();
String id = UUID.randomUUID().toString();
xcUser.setId(id);
xcUser.setWxUnionid(unionid);
//记录从微信得到的昵称
xcUser.setNickname(userInfo_map.get("nickname").toString());
xcUser.setUserpic(userInfo_map.get("headimgurl").toString());
xcUser.setName(userInfo_map.get("nickname").toString());
xcUser.setUsername(unionid);
xcUser.setPassword(unionid);
xcUser.setUtype("101001");//学生类型
xcUser.setStatus("1");//用户状态
xcUser.setCreateTime(LocalDateTime.now());
xcUserMapper.insert(xcUser);
XcUserRole xcUserRole = new XcUserRole();
xcUserRole.setId(UUID.randomUUID().toString());
xcUserRole.setUserId(id);
xcUserRole.setRoleId("17");//学生角色
userRoleMapper.insert(xcUserRole);
return xcUser;
}
}