1. ユーザー登録(携帯電話認証コード)
現在、携帯電話番号の収集は、ユーザープロモーションやビジネスプロモーションに非常に価値があるため、携帯電話番号登録が主流の方法です。前回の記事と組み合わせて、Aliyun SMS サービスを使用して携帯電話認証コードを送信し、ここでは統合により携帯電話番号によるユーザー登録を実現します。
アイデア: 携帯電話番号を入力した後、フロントエンドは携帯電話番号を検証する必要があります. ユーザーは SMS 検証コードを受信し、検証コード検証を完了する必要があります. 具体的な手順:
1. 現在の携帯電話番号が登録されているかどうかを判断する;
2. Alibaba Cloud SMS サービス API を呼び出して確認コードを送信する;
3. 確認コードが正常に送信され、redis キャッシュに保存され、確認コードがキャッシュ消去の仕組み(有効期限の設定)を利用することで実現 有効期限切れ;
4.認証コードの認証を通過すれば登録成功。
1.1 ユーザーエンティティクラス
package com.zhmsky.service_ucenter.entity;
import com.baomidou.mybatisplus.annotation.*;
import java.util.Date;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
* 会员表
* </p>
*
* @author zhmsky
* @since 2022-07-16
*/
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value="UcenterMember对象", description="会员表")
public class UcenterMember implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "会员id")
@TableId(value = "id", type = IdType.ASSIGN_ID)
private String id;
@ApiModelProperty(value = "微信openid")
private String openid;
@ApiModelProperty(value = "手机号")
private String mobile;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "昵称")
private String nickname;
@ApiModelProperty(value = "性别 1 女,2 男")
private Integer sex;
@ApiModelProperty(value = "年龄")
private Integer age;
@ApiModelProperty(value = "用户头像")
private String avatar;
@ApiModelProperty(value = "用户签名")
private String sign;
@ApiModelProperty(value = "是否禁用 1(true)已禁用, 0(false)未禁用")
private Boolean isDisabled;
@ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除")
private Boolean isDeleted;
@ApiModelProperty(value = "创建时间")
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@ApiModelProperty(value = "更新时间")
@TableField(fill = FieldFill.UPDATE)
private Date updateTime;
}
1.2 ユーザービューオブジェクトの登録
package com.zhmsky.service_ucenter.entity.VO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @author zhmsky
* @date 2022/7/16 17:18
*/
@Data
@ApiModel(value="注册对象", description="注册对象")
public class RegisterVO {
@ApiModelProperty(value = "昵称")
private String nickname;
@ApiModelProperty(value = "手机号")
private String mobile;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "验证码")
private String code;
}
1.3 SMS認証コードの送信
Alibaba Cloud SMS サービスを呼び出して SMS 確認コードを送信する
package com.zhmsky.msmService.service.impl;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.profile.DefaultProfile;
import com.zhmsky.msmService.service.MsmService;
import org.springframework.stereotype.Service;
/**
* @author zhmsky
* @date 2022/7/6 21:01
* 短信验证码实现类
*/
@Service
public class MsmServiceImpl implements MsmService {
/**
* 发送短信验证码
* @param phone 手机号
* @param code 被发送的验证码
* @return
*/
@Override
public String sendCodeMsg(String phone, String code) {
String checkCode="";
DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "LTAI5tDfHQPQ5WA5dBkrfFxu", "eRID7ZigveAH7fMRKbCDq92jjRl68R");
IAcsClient client = new DefaultAcsClient(profile);
SendSmsRequest request = new SendSmsRequest();
request.setSignName("阿里云短信测试");
request.setTemplateCode("SMS_154950909");
request.setPhoneNumbers(phone);
request.setTemplateParam("{\"code\":\""+code+"\"}");
try {
SendSmsResponse response = client.getAcsResponse(request);
checkCode = response.getCode();
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
System.out.println("ErrCode:" + e.getErrCode());
System.out.println("ErrMsg:" + e.getErrMsg());
System.out.println("RequestId:" + e.getRequestId());
}
return checkCode;
}
}
SMS 確認コード送信インターフェース
package com.zhmsky.msmService.controller;
import com.zhmsky.msmService.service.MsmService;
import com.zhmsky.msmService.utils.RandomUtil;
import com.zhmsky.result.Result;
import com.zhmsky.result.ResultUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @author zhmsky
* @date 2022/7/6 20:58
*/
@RestController
@Api("短信注册控制器")
@CrossOrigin
@RequestMapping("/msmService")
public class MsmController {
@Autowired
private MsmService msmService;
@Autowired
private RedisTemplate<String,String> redisTemplate;
@GetMapping("/send/{phone}")
@ApiOperation("发送短信验证码")
public Result<String> sendMsg(@PathVariable String phone){
//通过redis设置缓存时间实现验证码过期
String code = redisTemplate.opsForValue().get(phone);
if(!StringUtils.isEmpty(code)){
return new ResultUtil<String>().setSuccessMsg("请勿重复发送!");
}
/** 如果缓存里面没有那么就重新发送 **/
//生成随机验证码
String fourBitRandom = RandomUtil.getFourBitRandom();
//调用阿里云api实现短信发送
String checkCode = msmService.sendCodeMsg(phone, fourBitRandom);
if("OK".equals(checkCode)){
//将验证码保存到redis中并设置有效时间为5分钟
redisTemplate.opsForValue().set(phone,fourBitRandom,5, TimeUnit.MINUTES);
return new ResultUtil<String>().setData(fourBitRandom,"验证码发送成功!");
}else{
return new ResultUtil<String>().setErrorMsg("验证码发送给失败!");
}
}
}
1.4 登録機能の実現
1.インターフェース保護、パラメータ非空判定
2.携帯電話番号登録の有無確認
3.認証コード確認
4.保存
/**
* 用户注册
* @param registerVO
* @return
*/
@Override
public boolean register(RegisterVO registerVO) {
//获取注册数据,接口保护,参数校验
String code = registerVO.getCode();
String mobile = registerVO.getMobile();
String nickname = registerVO.getNickname();
String password = registerVO.getPassword();
if(StringUtils.isEmpty(mobile)||StringUtils.isEmpty(password)||StringUtils.isEmpty(code)||StringUtils.isEmpty(nickname)){
throw new MyException(20005,"注册失败!");
}
//判断手机号是否已注册
QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
wrapper.eq("mobile",mobile);
Long count = baseMapper.selectCount(wrapper);
if(count>0){
throw new MyException(20005,"注册失败!");
}
//验证码校验
//先从redis中获取验证码
String cacheCode = redisTemplate.opsForValue().get(mobile);
if(!code.equals(cacheCode)){
throw new MyException(20005,"注册失败!");
}
//入库
UcenterMember ucenterMember = new UcenterMember();
ucenterMember.setMobile(mobile);
ucenterMember.setNickname(nickname);
ucenterMember.setPassword(MD5.encrypt(password));
ucenterMember.setIsDisabled(false);
ucenterMember.setAvatar("http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132");
int insert = baseMapper.insert(ucenterMember);
if(insert>0){
return true;
}else{
return false;
}
}
2. ユーザーログイン
2.1 アカウントパスワードログイン
ログイン プロセス:
1. ログイン インターフェイスを呼び出し、トークン文字列を返します。 2. 返されたトークン文字列を Cookie に入れます
。
4. ヘッダーでインターフェースを
呼び出し、トークンに応じてユーザー情報を取得し、再度 Cookie に入れる (最初のページの表示用)
5. Cookie からユーザー情報を取り出して表示する
アイデア:
1 . インターフェース保護、パラメーター非 null チェック;
2. アカウントが登録されていることを確認します;
3. アカウントが無効であることを確認します;
4. パスワードを確認します;
5. ログインしてトークンを返します。
シングル サインオンを実現するために、トークン トークン メソッドを使用して jwt
jwt ツール クラスを導入します。
package com.zhmsky.jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
/**
* @author zhmsky
* @date 2022/7/6 17:53
*/
public class JwtUtils {
//设置token过期时间
public static final long EXPIRE = 1000 * 60 * 60 * 24; //一天
//签名加密密钥
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";
/**
* 生成token
* @param id
* @param nickname
* @return
*/
public static String getJwtToken(String id, String nickname){
String JwtToken = Jwts.builder()
//设置头信息
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
//设置token过期时间
.setSubject("user") //随便写
.setIssuedAt(new Date())
//当前时间加上设置的过期时间
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
//设置token主体,可存储用户信息
.claim("id", id)
.claim("nickname", nickname)
//签名哈希
.signWith(SignatureAlgorithm.HS256, APP_SECRET)
.compact();
return JwtToken;
}
/**
* 判断token是否存在与有效
* @param jwtToken
* @return
*/
public static boolean checkToken(String jwtToken) {
if(StringUtils.isEmpty(jwtToken)) return false;
try {
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 判断token是否存在与有效
* @param request
* @return
*/
public static boolean checkToken(HttpServletRequest request) {
try {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return false;
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 根据token获取会员id
* @param request
* @return
*/
public static String getMemberIdByJwtToken(HttpServletRequest request) {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
Claims claims = claimsJws.getBody();
return (String)claims.get("id");
}
}
ログイン事業:
/**
* 用户登录
* @param member
* @return token
*/
@Override
public String login(UcenterMember member) {
//获取账号和密码
String mobile = member.getMobile();
String password = member.getPassword();
if(StringUtils.isEmpty(mobile)||StringUtils.isEmpty(password)){
throw new MyException(20010,"账号和密码不能为空!");
}
//判断账号和密码是否存在
QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
wrapper.eq("mobile",mobile);
UcenterMember ucenterMember = baseMapper.selectOne(wrapper);
if(ObjectUtils.isEmpty(ucenterMember)){
throw new MyException(20011,"账号不存在,请重新输入!");
}
//判断该用户是否被禁用
Boolean isDisabled = ucenterMember.getIsDisabled();
if(isDisabled){
throw new MyException(20013,"该账号已禁用!");
}
//判断密码是否正确
//密码存储肯定是加密的,实际开发中数据库不会存储明文密码
//先将输入的密码加密,再和数据库密码比较
//MD5加密
String realPassword = ucenterMember.getPassword();
if(!MD5.encrypt(password).equals(realPassword)){
throw new MyException(20012,"密码错误,请重新输入!");
}
//登录成功,返回token(通过查出来的用户数据去生成token)
return JwtUtils.getJwtToken(ucenterMember.getId(), ucenterMember.getNickname());
}
ログイン インターフェイス:
@PostMapping ("/login")
@ApiOperation("用户登录")
public Result<String> login(@RequestBody(required = false) UcenterMember ucenterMember){
//登录生成token并返回
String token = memberService.login(ucenterMember);
return new ResultUtil<String>().setData(token);
}
ログインが成功した後、フロントエンドの各リクエストはトークンを運び、トークンはリクエスト オブジェクトから取得され、トークンが解析されてユーザー情報が取得されます。
@GetMapping("/getUserInfo")
@ApiOperation("根据token获取用户信息")
public Result<UcenterMember> getUserInfo(HttpServletRequest httpServletRequest){
//从request对象中获取token,再根据token获取用户信息
String userId = JwtUtils.getMemberIdByJwtToken(httpServletRequest);
//根据用户id获取用户信息
UcenterMember ucenterMember = memberService.getById(userId);
return new ResultUtil<UcenterMember>().setData(ucenterMember);
}
2.2 WeChat スキャン コード ログイン
2.2.1 準備:
ウェブアプリWeChatログインは、OAuth2.0プロトコル規格に基づくWeChat OAuth2.0認証ログインシステムです。
WeChat OAuth2を実行する前にWeChat OAuth2.0認証ログインアクセスを実行する前に、WeChatオープンプラットフォームに開発者アカウントを登録し、承認されたWebサイトアプリケーションを持っており、対応するAppIDとAppSecretを取得し、WeChatログインを申請して審査に合格しますその後、アクセスプロセスを開始できます。
2.2.2 認可プロセス
- サード パーティが WeChat 認証ログイン リクエストを開始します. WeChat ユーザーがサード パーティ アプリケーションの認証を許可した後、WeChat はアプリケーションを起動するか、サード パーティの Web サイトにリダイレクトし、認証一時チケット コード パラメータを取得します。
- コード パラメーターなどを介して AppID と AppSecret を追加し、API を介して access_token と交換します。
- access_token を使用してインターフェイス呼び出しを行い、基本的なユーザー データ リソースを取得したり、ユーザーが基本的な操作を実装できるようにします。
ステップ 1: WeChat QR コードの生成をリクエストする
公式文書に従って、WeChat オープン プラットフォームの固定アドレスを直接リクエストしますhttps://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state =STATE#wechat_redirect、4 つの必要なパラメーターがあります。
appid: WeChat オープン プラットフォーム アプリケーションが承認された後に発行される一意の識別子
redirect_uri: WeChat スキャン コード承認後のコールバック アドレス
response_type: コード
スコープ: 承認スコープ、Web アプリケーションの場合は snsapi_login を入力するだけです
上記パラメータを用意し、プロパティファイルに初期値設定を完了
wx.open.app_id=wxed9954c01bb89b47
wx.open.app_secret=a7482517235173ddb4083788de60b90e
wx.open.redirect_url=http://localhost:8160/api/ucenter/wx/callback
パラメータ初期化ツール クラスを記述します。
package com.zhmsky.service_ucenter.utils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @author zhmsky
* @date 2022/7/17 17:06
*/
@Component
public class ConstWxUtil implements InitializingBean {
@Value("${wx.open.app_id}")
private String appId;
@Value("${wx.open.app_secret}")
private String appSecret;
@Value("${wx.open.redirect_url}")
private String redirectUrl;
public static String WX_OPEN_APP_ID;
public static String WX_OPEN_APP_SECRET;
public static String WX_OPEN_REDIRECT_URL;
@Override
public void afterPropertiesSet() throws Exception {
WX_OPEN_APP_ID = appId;
WX_OPEN_APP_SECRET = appSecret;
WX_OPEN_REDIRECT_URL = redirectUrl;
}
}
二次元コード生成インターフェース:
@GetMapping("/getWxCode")
@ApiOperation("生成微信二维码")
public String getWxCode() {
// 微信开放平台授权baseUrl
String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
"?appid=%s" +
"&redirect_uri=%s" +
"&response_type=code" +
"&scope=snsapi_login" +
"&state=%s" +
"#wechat_redirect";
// 回调地址
String redirectUrl = ConstWxUtil.WX_OPEN_REDIRECT_URL;
try {
redirectUrl = URLEncoder.encode(redirectUrl, "UTF-8"); //url编码
} catch (UnsupportedEncodingException e) {
throw new MyException(20001, e.getMessage());
}
String state = "imhelen";
//生成qrcodeUrl
String qrcodeUrl = String.format(
baseUrl,
ConstWxUtil.WX_OPEN_APP_ID,
redirectUrl,
state);
return "redirect:" + qrcodeUrl;
}
ステップ 2: WeChat で QR コードをスキャンした後、コールバックを実行する
@GetMapping("/callback")
@ApiOperation("微信扫码确认后执行回调")
public String callback(String code,String state){
//TODO
return "redirect:http://localhost:3000";
}
次に、
ランダム コードを取得し、WeChat リソース サーバーの固定アドレスを要求して accessToken (アクセス資格情報) と openId (一意のユーザー ID) を取得します。次に、accessToken と openId を介して WeChat リソース サーバーの固定アドレスを要求します。コードスキャナーの基本情報を取得します。利用者の基本情報を取得後、本人確認を行い、保管等を完了させます。
@GetMapping("/callback")
@ApiOperation("微信扫码确认后执行回调")
public String callback(String code,String state){
//1、接收code值
//用code去请求微信的固定地址,得到accessToken和openId
//向认证服务器发送请求换取access_token
String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
"?appid=%s" +
"&secret=%s" +
"&code=%s" +
"&grant_type=authorization_code";
//带参数的真实认证服务器请求地址
String accessTokenUrl = String.format(baseAccessTokenUrl,
ConstWxUtil.WX_OPEN_APP_ID,
ConstWxUtil.WX_OPEN_APP_SECRET,
code
);
//2、请求认证服务器获取接口调用凭证access_token和用户唯一标识openId
try {
String accessTokenInfo = HttpClientUtils.get(accessTokenUrl);
//将上述json字符串转换成map对象
/*
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
*/
Gson gson = new Gson();
HashMap mapAccessTokenInfo = gson.fromJson(accessTokenInfo, HashMap.class);
//取出的access_token
String access_token = (String)mapAccessTokenInfo.get("access_token");
//取出的openid
String openid = (String)mapAccessTokenInfo.get("openid");
//3、再通过获取出来的access_token和openid去请求微信开放平台服务器获取扫码人信息
//访问微信的资源服务器,获取用户信息
String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
"?access_token=%s" +
"&openid=%s";
String userInfoUrl = String.format(baseUserInfoUrl, access_token, openid);
//发送请求获取扫码人基本信息
String userInfo = HttpClientUtils.get(userInfoUrl);
/*
{
"openid":"o3_SC5_eI--mIC9ikI2pvTuZhYnk",
"nickname":"Kong",
"sex":0,
"language":"",
"city":"",
"province":"",
"country":"",
"headimgurl":"https:\/\/thirdwx.qlogo.cn\/mmopen\/vi_32\/hAqkcbxzEJzic0WYl9pHDglAvYBI4iagLsSLXb2ialcxa3Au6UmwibSiadGMtbQia0oAzmzq26k2f1ES4q1HbS6aIYuA\/132",
"privilege":[],
"unionid":"oWgGz1OqVll-tTU4R_DM_zRp7Rjc"
}
*/
//将上面的json字符串转换成map对象
HashMap mapUserInfo = gson.fromJson(userInfo, HashMap.class);
//扫码人基础信息
String nickname = (String)mapUserInfo.get("nickname");
String headImgurl=(String)mapUserInfo.get("headimgurl");
String openId=(String)mapUserInfo.get("openid");
//扫码后自动注册(入库)
//先判断是否已注册
boolean isExist = memberService.getUserOpenId(openId);
if(!isExist){
//入库
UcenterMember member = new UcenterMember();
member.setNickname(nickname);
member.setOpenid(openid);
member.setAvatar(headImgurl);
memberService.save(member);
}
UcenterMember ucenterMember = memberService.getUserByOpenId(openId);
String token = JwtUtils.getJwtToken(ucenterMember.getId(), ucenterMember.getNickname());
//因为端口号不同存在跨域问题,cookie不能跨域,所以这里使用url重写
return "redirect:http://localhost:3000?token="+token;
} catch (Exception e) {
e.printStackTrace();
throw new MyException(20010,"登录失败!");
}
}
まとめ:
WeChat認証ログインの実装は宝箱を開けるようなもの. 合計3つのキーが必要. 最初のキーは appid (これはWeChatオープンプラットフォームに登録および認証され、プラットフォームによって発行される必要があります); 固定アドレスを要求します. appid を介して WeChat QR コードを生成できます; 認証のために QR コードをスキャンした後、ユーザーは 2 番目のキー コード (ランダムな一意の値) を取得します; 次に、コードを介して固定サーバー アドレスを要求し、3 番目のキーの openid (ユーザーの一意の識別子) を取得します。最後に、openid と accessToken を介して固定アドレスを要求することにより、WeChat ユーザーの基本情報を取得します。