热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

认证服务功能实现逻辑

认证服务分为两种,一种是社交登录,一种是单点登录,都交给认证中心(OAuth2.0)来处理,用一

认证服务分为两种,一种是社交登录,一种是单点登录,都交给认证中心(OAuth2.0)来处理,用一个专门的服务来作这个认证中心,所有的登录注册都是由这个认证中心来处理的,由于注册要发短信,短信又是调用了阿里云的接口, 这个发验证码短信的组件放到了第三方服务中,所以又需要调用第三方服务来发短信,最终的登录注册实现是写在用户服务里的,所以还需要远程调用用户服务


单点登录注册


一、用户注册功能(登录和注册都是以表单提交发的请求)


1、实现发送手机验证码功能(手机验证码是存到Redis缓存中)

(1)前端实现发送短信后倒计时效果和发送请求/sms/sendCode给后端处理

发送验证码

/*** 发送验证码请求给后端*/$(function () {$("#sendCode").click(function () {//2、倒计时//只有class里有disabled就表示还在倒计时中,什么都不干(用于防止在倒计时期间重复发送验证码)if($(this).hasClass("disabled")) {//正在倒计时中} else {//1、给指定手机号发送验证码$.get("/sms/sendCode?phOne=" + $("#phoneNum").val(),function (data) {if(data.code != 0) {alert(data.msg);}});timeoutChangeStyle();}});});/*** 实现验证码倒计时效果* @type {number}*/var num = 60; //倒计时60sfunction timeoutChangeStyle() {//点击发送验证码后就设置其class为disabled(用于防止在倒计时期间重复发送验证码)$("#sendCode").attr("class","disabled");//倒计时结束if(num == 0) {$("#sendCode").text("发送验证码");num = 60;//清空前面加的class$("#sendCode").attr("class","");} else {var str = num + "s 后再次发送";$("#sendCode").text(str);//1s后再调用自身方法来实现倒计时setTimeout("timeoutChangeStyle()",1000);}num --;}

(2)在认证服务的LoginController来处理请求/sms/sendCode(发短信验证码)

分流程:(其实就是把短信验证码以下面的格式存到缓存再真正发送短信验证码

Redis缓存中验证码的格式sms:code:123456789->123456_1646981054661 ,123456789是手机号,123456表示验证码,1646981054661表示存入缓存的时间

1>判断Redis缓存里有没有这个手机号对应的验证码信息

2>如果Redis缓存中有这个验证码信息那就要判断时间合法性(其实就是验证码有没有过期)

3>创建验证码并且拼装成存入Redis缓存中的value值格式

4>调用第三方服务来真正发送验证码

/**
* 短信验证码
* @param phone
* @return
*/
@ResponseBody
@GetMapping(value = "/sms/sendCode")
public R sendCode(@RequestParam("phone") String phone) {/*** 接口防刷*///把验证码从缓存中提取出来//(缓存中验证码的格式sms:code:123456789->123456_1646981054661 ,123456789是手机号,123456表示验证码,1646981054661表示存入缓存的时间)String redisCode = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone);if (!StringUtils.isEmpty(redisCode)) {//把存入缓存的验证码的值给提取出来(格式是123456_1646981054661 ,123456表示验证码,1646981054661表示存入缓存的时间)long currentTime = Long.parseLong(redisCode.split("_")[1]);//活动存入redis的时间,用当前时间减去存入redis的时间,判断用户手机号是否在60s内发送验证码if (System.currentTimeMillis() - currentTime <60000) {//60s内不能再发// BizCodeEnum.SMS_CODE_EXCEPTION=10002 BizCodeEnum.SMS_CODE_EXCEPTION = 10002return R.error(BizCodeEnum.SMS_CODE_EXCEPTION.getCode(),BizCodeEnum.SMS_CODE_EXCEPTION.getMessage());}}/*** 2、创建验证码存入redis.存key-phone,value-code*/int code = (int) ((Math.random() * 9 + 1) * 100000);String codeNum = String.valueOf(code);//存入缓存的验证码格式是123456_1646981054661 加系统时间是为了防止多次刷新验证码String redisStorage = codeNum + "_" + System.currentTimeMillis();//存入redis,防止同一个手机号在60秒内再次发送验证码(存入缓存的格式sms:code:123456789->123456 ,其中123456789是手机号,123456是验证码)stringRedisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREFIX+phone,redisStorage,10, TimeUnit.MINUTES);//AuthServerConstant.SMS_CODE_CACHE_PREFIX = sms:code:// String codeNum = UUID.randomUUID().toString().substring(0,6);thirdPartFeignService.sendCode(phone, codeNum);return R.ok();}

注意:这里的短信验证码不是第三方服务发送的,具体短信验证码的内容是上面的sendCode方法随机生成的,这个验证码甚至可以自定义为666666,所以这个验证码的内容在上面sendCode方法就已经存到Redis缓存中,方便后面进行验证码校验

(3)远程调用第三方服务来真正发送验证码

package com.saodai.saodaimall.auth.feign;import com.saodai.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;/*** 远程调用第三方服务来发送验证码**/@FeignClient("saodaimall-third-party")
public interface ThirdPartFeignService {@GetMapping(value = "/sms/sendCode")R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code);}
package com.saodai.saodaimall.thirdparty.controller;import com.saodai.common.utils.R;
import com.saodai.saodaimall.thirdparty.component.SmsComponent;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;/*** 短信验证码的控制器**/@RestController
@RequestMapping(value = "/sms")
public class SmsSendController {@Resourceprivate SmsComponent smsComponent;/*** 提供给别的服务进行调用* @param phone* @param code* @return*/@GetMapping(value = "/sendCode")public R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code) {//发送验证码smsComponent.sendCode(phone,code);return R.ok();}}
package com.saodai.saodaimall.thirdparty.component;import com.saodai.saodaimall.thirdparty.util.HttpUtils;
import lombok.Data;
import org.apache.http.HttpResponse;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;import java.util.HashMap;
import java.util.Map;/*** 短信验证码发送的组件类(提供发送短信验证码的接口)**/@ConfigurationProperties(prefix = "spring.cloud.alicloud.sms")
@Data
@Component
public class SmsComponent {private String host;private String path;private String appcode;public void sendCode(String phone,String code) {String method = "POST";Map headers = new HashMap();//最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105headers.put("Authorization", "APPCODE " + appcode);//根据API的要求,定义相对应的Content-Typeheaders.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");Map querys = new HashMap();Map bodys = new HashMap();//注意这里的"code:"是必须要有的,正确的格式是"code:6666",没加code:就会报400错误,expire_at:6表示验证码有效时间为6分钟bodys.put("content", "code:"+code+",expire_at:6");bodys.put("phone_number", phone);//使用的是自定义的模板(content需要两个参数)bodys.put("template_id", "TPL_09229");try {HttpResponse respOnse= HttpUtils.doPost(host, path, method, headers, querys, bodys);System.out.println(response.toString());//获取response的body//System.out.println(EntityUtils.toString(response.getEntity()));} catch (Exception e) {e.printStackTrace();}}}

package com.saodai.saodaimall.thirdparty.util;import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;/**
* 阿里云短信验证码接口要用的工具类
*/
public class HttpUtils {/*** get* * @param host* @param path* @param method* @param headers* @param querys* @return* @throws Exception*/public static HttpResponse doGet(String host, String path, String method, Map headers, Map querys)throws Exception { HttpClient httpClient = wrapClient(host);HttpGet request = new HttpGet(buildUrl(host, path, querys));for (Map.Entry e : headers.entrySet()) {request.addHeader(e.getKey(), e.getValue());}return httpClient.execute(request);}/*** post form* * @param host* @param path* @param method* @param headers* @param querys* @param bodys* @return* @throws Exception*/public static HttpResponse doPost(String host, String path, String method, Map headers, Map querys, Map bodys)throws Exception { HttpClient httpClient = wrapClient(host);HttpPost request = new HttpPost(buildUrl(host, path, querys));for (Map.Entry e : headers.entrySet()) {request.addHeader(e.getKey(), e.getValue());}if (bodys != null) {List nameValuePairList = new ArrayList();for (String key : bodys.keySet()) {nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key)));}UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");request.setEntity(formEntity);}return httpClient.execute(request);} /*** Post String* * @param host* @param path* @param method* @param headers* @param querys* @param body* @return* @throws Exception*/public static HttpResponse doPost(String host, String path, String method, Map headers, Map querys, String body)throws Exception { HttpClient httpClient = wrapClient(host);HttpPost request = new HttpPost(buildUrl(host, path, querys));for (Map.Entry e : headers.entrySet()) {request.addHeader(e.getKey(), e.getValue());}if (StringUtils.isNotBlank(body)) {request.setEntity(new StringEntity(body, "utf-8"));}return httpClient.execute(request);}/*** Post stream* * @param host* @param path* @param method* @param headers* @param querys* @param body* @return* @throws Exception*/public static HttpResponse doPost(String host, String path, String method, Map headers, Map querys, byte[] body)throws Exception { HttpClient httpClient = wrapClient(host);HttpPost request = new HttpPost(buildUrl(host, path, querys));for (Map.Entry e : headers.entrySet()) {request.addHeader(e.getKey(), e.getValue());}if (body != null) {request.setEntity(new ByteArrayEntity(body));}return httpClient.execute(request);}/*** Put String* @param host* @param path* @param method* @param headers* @param querys* @param body* @return* @throws Exception*/public static HttpResponse doPut(String host, String path, String method, Map headers, Map querys, String body)throws Exception { HttpClient httpClient = wrapClient(host);HttpPut request = new HttpPut(buildUrl(host, path, querys));for (Map.Entry e : headers.entrySet()) {request.addHeader(e.getKey(), e.getValue());}if (StringUtils.isNotBlank(body)) {request.setEntity(new StringEntity(body, "utf-8"));}return httpClient.execute(request);}/*** Put stream* @param host* @param path* @param method* @param headers* @param querys* @param body* @return* @throws Exception*/public static HttpResponse doPut(String host, String path, String method, Map headers, Map querys, byte[] body)throws Exception { HttpClient httpClient = wrapClient(host);HttpPut request = new HttpPut(buildUrl(host, path, querys));for (Map.Entry e : headers.entrySet()) {request.addHeader(e.getKey(), e.getValue());}if (body != null) {request.setEntity(new ByteArrayEntity(body));}return httpClient.execute(request);}/*** Delete* * @param host* @param path* @param method* @param headers* @param querys* @return* @throws Exception*/public static HttpResponse doDelete(String host, String path, String method, Map headers, Map querys)throws Exception { HttpClient httpClient = wrapClient(host);HttpDelete request = new HttpDelete(buildUrl(host, path, querys));for (Map.Entry e : headers.entrySet()) {request.addHeader(e.getKey(), e.getValue());}return httpClient.execute(request);}private static String buildUrl(String host, String path, Map querys) throws UnsupportedEncodingException {StringBuilder sbUrl = new StringBuilder();sbUrl.append(host);if (!StringUtils.isBlank(path)) {sbUrl.append(path);}if (null != querys) {StringBuilder sbQuery = new StringBuilder();for (Map.Entry query : querys.entrySet()) {if (0 }


com.alibaba.cloudspring-cloud-starter-alicloud-oss2.2.0.RELEASE

spring:cloud:alicloud:#短信验证码的配置sms:host: https://dfsns.market.alicloudapi.compath: /data/send_smsappcode: dbc663defb63426caefc7be0a8fe8cdf

整个配置文件里就只有appcode是自己购买短信服务后唯一的码,其他的都是固定配置

AppCode获取地址:阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台


2、注册用户

(1)前端以表单的形式发送请求/register给后端



错误提示区域,如果出错了就会从后端获取报错信息
发送验证码

(2)在认证服务的LoginController来处理请求/register

分流程:

1>获取用户输入的验证码和缓存里的验证码(这里要判断缓存中验证码为不为空)进行比较,看是否相同

2>验证码相同就立马删除验证码(令牌机制),验证码不同报错同时重定向到注册界面

3>远程调用会员服务进行真正的用户注册

4>远程调用成功就重定向到首页,失败就把错误消息封装成map同时重定向到注册界面

/**
* 用户注册
* TODO: 重定向携带数据:利用session原理,将数据放在session中。
* TODO:只要跳转到下一个页面取出这个数据以后,session里面的数据就会删掉
* TODO:分布下session问题
* RedirectAttributes:重定向也可以保留数据,不会丢失
*
* @return
*/
@PostMapping(value = "/register")
public String register(@Valid UserRegisterVo vos, BindingResult result,RedirectAttributes attributes) {//1、效验验证码String code = vos.getCode();//获取存入Redis里的验证码(redisCode的格式是123456_1646981054661 ,123456表示验证码,1646981054661表示存入缓存的时间)String redisCode = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + vos.getPhone());//判断redis的对应的验证码是否为空if (!StringUtils.isEmpty(redisCode)) {//判断验证码是否相同if (code.equals(redisCode.split("_")[0])) {//删除验证码;令牌机制stringRedisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX+vos.getPhone());//验证码通过,真正注册,调用远程服务进行注册R register = memberFeignService.register(vos);//register.getCode() == 0表示远程调用成功if (register.getCode() == 0) {//成功return "redirect:http://saodaimall.com";} else {//失败Map errors = new HashMap<>();errors.put("msg", register.getData("msg",new TypeReference(){}));attributes.addFlashAttribute("errors",errors);return "redirect:http://auth.saodaimall.com/reg.html";}} else {//效验出错回到注册页面Map errors = new HashMap<>();errors.put("code","验证码错误");attributes.addFlashAttribute("errors",errors);return "redirect:http://auth.saodaimall.com/reg.html";}} else {//redis的对应的验证码为空表示效验出错回到注册页面Map errors = new HashMap<>();errors.put("code","验证码错误");attributes.addFlashAttribute("errors",errors);return "redirect:http://auth.saodaimall.com/reg.html";}
}

package com.saodai.saodaimall.auth.vo;import lombok.Data;
import org.hibernate.validator.constraints.Length;import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;/*** 用户注册信息封装类**/@Data
public class UserRegisterVo {@NotEmpty(message = "用户名不能为空")@Length(min = 6, max = 19, message="用户名长度在6-18字符")private String userName;@NotEmpty(message = "密码必须填写")@Length(min = 6,max = 18,message = "密码必须是6—18位字符")private String password;@NotEmpty(message = "手机号不能为空")@Pattern(regexp = "^[1]([3-9])[0-9]{9}$", message = "手机号格式不正确")private String phone;@NotEmpty(message = "验证码不能为空")private String code;}

(3)远程调用会员服务来实现真正的注册

/*** 会员注册功能* @param vo* @return*/@PostMapping(value = "/register")public R register(@RequestBody MemberUserRegisterVo vo) {try {memberService.register(vo);} catch (PhoneException e) {//BizCodeEnum.PHONE_EXIST_EXCEPTION=存在相同的手机号 15002return R.error(BizCodeEnum.PHONE_EXIST_EXCEPTION.getCode(),BizCodeEnum.PHONE_EXIST_EXCEPTION.getMessage());} catch (UsernameException e) {//BizCodeEnum.USER_EXIST_EXCEPTION=商品库存不足 21000return R.error(BizCodeEnum.USER_EXIST_EXCEPTION.getCode(),BizCodeEnum.USER_EXIST_EXCEPTION.getMessage());}return R.ok();}/*** 会员注册*/@Overridepublic void register(MemberUserRegisterVo vo) {MemberEntity memberEntity = new MemberEntity();//设置默认等级MemberLevelEntity levelEntity = memberLevelDao.getDefaultLevel();memberEntity.setLevelId(levelEntity.getId());//设置其它的默认信息//检查用户名和手机号是否唯一。感知异常,异常机制(异常机制就是问题就抛出具体异常,没问题就继续执行下面的语句)checkPhoneUnique(vo.getPhone());checkUserNameUnique(vo.getUserName());memberEntity.setNickname(vo.getUserName());memberEntity.setUsername(vo.getUserName());//密码进行MD5盐值加密(盐值加密同一个数据的每次加密结果是不一样的,通过match方法来密码校验)// (注意这里不能用md5直接加密放数据库,因为彩虹表可以破解md5,所谓彩虹表就是通过大量的md5数据反向退出md5// 注意MD5是不可逆,但是可暴力通过彩虹表破解)BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();String encode = bCryptPasswordEncoder.encode(vo.getPassword());memberEntity.setPassword(encode);memberEntity.setMobile(vo.getPhone());memberEntity.setGender(0);memberEntity.setCreateTime(new Date());//保存数据this.baseMapper.insert(memberEntity);}/*** 检查手机号是否重复的异常机制方法* @param phone* @throws PhoneException*/@Overridepublic void checkPhoneUnique(String phone) throws PhoneException {Long phOneCount= this.baseMapper.selectCount(new QueryWrapper().eq("mobile", phone));//usernameCount > 0表示手机号已经存在if (phoneCount > 0) {throw new PhoneException();}}/*** 检查用户名是否重复的异常机制方法* @param userName* @throws UsernameException*/@Overridepublic void checkUserNameUnique(String userName) throws UsernameException {Long usernameCount = this.baseMapper.selectCount(new QueryWrapper().eq("username", userName));//usernameCount > 0表示用户名已经存在if (usernameCount > 0) {throw new UsernameException();}}

分流程:(封装MemberEntity对象)

1>设置默认等级

package com.saodai.saodaimall.member.entity;import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.io.Serializable;
import java.math.BigDecimal;/*** 会员等级*/
@Data
@TableName("ums_member_level")
public class MemberLevelEntity implements Serializable {private static final long serialVersiOnUID= 1L;/*** id*/@TableIdprivate Long id;/*** 等级名称*/private String name;/*** 等级需要的成长值*/private Integer growthPoint;/*** 是否为默认等级[0->不是;1->是]*/private Integer defaultStatus;/*** 免运费标准*/private BigDecimal freeFreightPoint;/*** 每次评价获取的成长值*/private Integer commentGrowthPoint;/*** 是否有免邮特权*/private Integer priviledgeFreeFreight;/*** 是否有会员价格特权*/private Integer priviledgeMemberPrice;/*** 是否有生日特权*/private Integer priviledgeBirthday;/*** 备注*/private String note;}

2>检查用户名和手机号是否唯一,通过异常机制来感知异常(异常机制就是问题就抛出具体异常,没问题就继续执行下面的语句)package com.saodai.saodaimall.member.exception;public class UsernameException extends RuntimeException {public UsernameException() {super("存在相同的用户名");}
}
package com.saodai.saodaimall.member.exception;public class PhoneException extends RuntimeException {public PhoneException() {super("存在相同的手机号");}
}

3>密码进行MD5盐值加密

4>保存用户信息到数据库中

package com.saodai.saodaimall.member.vo;import lombok.Data;/*** 会员注册类**/@Data
public class MemberUserRegisterVo {private String userName;private String password;private String phone;}
package com.saodai.saodaimall.member.entity;import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.io.Serializable;
import java.util.Date;/*** 会员*/
@Data
@TableName("ums_member")
public class MemberEntity implements Serializable {private static final long serialVersiOnUID= 1L;/*** id*/@TableIdprivate Long id;/*** 会员等级id*/private Long levelId;/*** 用户名*/private String username;/*** 密码*/private String password;/*** 昵称*/private String nickname;/*** 手机号码*/private String mobile;/*** 邮箱*/private String email;/*** 头像*/private String header;/*** 性别*/private Integer gender;/*** 生日*/private Date birth;/*** 所在城市*/private String city;/*** 职业*/private String job;/*** 个性签名*/private String sign;/*** 用户来源*/private Integer sourceType;/*** 积分*/private Integer integration;/*** 成长值*/private Integer growth;/*** 启用状态*/private Integer status;/*** 注册时间*/private Date createTime;/*** 社交登录用户的ID*/private String socialId;/*** 社交登录用户的名称*/private String socialName;/*** 社交登录用户的自我介绍*/private String socialBio;}

二、用户登录功能

(1)前端以表单的形式发送请求/login给后端


(2)在认证服务的LoginController来处理请求/login

/**
* 普通手机账号登录
* @param vo
* @param attributes
* @param session
* @return
*/
@PostMapping(value = "/login")
public String login(UserLoginVo vo, RedirectAttributes attributes, HttpSession session) {//远程登录R login = memberFeignService.login(vo);//login.getCode() == 0表示远程调用成功if (login.getCode() == 0) {MemberResponseVo data = login.getData("data", new TypeReference() {});session.setAttribute(LOGIN_USER,data);return "redirect:http://saodaimall.com";} else {Map errors = new HashMap<>();errors.put("msg",login.getData("msg",new TypeReference(){}));attributes.addFlashAttribute("errors",errors);return "redirect:http://auth.saodaimall.com/login.html";}}

package com.saodai.saodaimall.auth.vo;import lombok.Data;/*** 用户登录信息封装类**/@Data
public class UserLoginVo {//账户private String loginacct;//密码private String password;
}

package com.saodai.common.vo;import lombok.Data;
import lombok.ToString;import java.io.Serializable;
import java.util.Date;/***会员信息**/@ToString
@Data
public class MemberResponseVo implements Serializable {private static final long serialVersiOnUID= 5573669251256409786L;private Long id;/*** 会员等级id*/private Long levelId;/*** 用户名*/private String username;/*** 密码*/private String password;/*** 昵称*/private String nickname;/*** 手机号码*/private String mobile;/*** 邮箱*/private String email;/*** 头像*/private String header;/*** 性别*/private Integer gender;/*** 生日*/private Date birth;/*** 所在城市*/private String city;/*** 职业*/private String job;/*** 个性签名*/private String sign;/*** 用户来源*/private Integer sourceType;/*** 积分*/private Integer integration;/*** 成长值*/private Integer growth;/*** 启用状态*/private Integer status;/*** 注册时间*/private Date createTime;/*** 社交登录用户的ID*/private String socialId;/*** 社交登录用户的名称*/private String socialName;/*** 社交登录用户的自我介绍*/private String socialBio;}

(3)远程调用会员服务来实现真正的登录

/*** 会员登录功能* @param vo* @return*/@PostMapping(value = "/login")public R login(@RequestBody MemberUserLoginVo vo) {//MemberUserLoginVo就是上面的UserLoginVo只是类名不一样MemberEntity memberEntity = memberService.login(vo);if (memberEntity != null) {return R.ok().setData(memberEntity);} else {return R.error(BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getCode(),BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getMessage());}}/*** 会员登录功能* @param vo* @return*/@Overridepublic MemberEntity login(MemberUserLoginVo vo) {//获取用户登录账号String loginacct = vo.getLoginacct();String password = vo.getPassword();//1、去数据库查询 SELECT * FROM ums_member WHERE username = ? OR mobile = ?//通过用户名或手机号登录都是可以的MemberEntity memberEntity = this.baseMapper.selectOne(new QueryWrapper().eq("username", loginacct).or().eq("mobile", loginacct));if (memberEntity == null) {//登录失败return null;} else {//获取到数据库里的passwordString password1 = memberEntity.getPassword();BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();//进行密码匹配boolean matches = passwordEncoder.matches(password, password1);if (matches) {//登录成功return memberEntity;}}return null;}

package com.saodai.saodaimall.member.vo;import lombok.Data;/*** 会员登录**/@Data
public class MemberUserLoginVo {private String loginacct;private String password;}

社交登录(OAuth2.0)

这里由于微博开发者权限申请太慢了就使用gitee来实现社交登录


(1)前端调用第三方应用作为社交登录

这个跟以往的模式不一样,以往是前段直接给后端发送请求,然后后端处理请求,这个是先调用第三方应用作为社交登录(也就是先跳转到gitee的登录授权页面),然后用户登录自己的gitee账号密码进行授权,授权成功后会跳转到指定的应用回调地址,然后在后端来处理这个应用回调地址的请求

gitee开发者后台管理链接:https://gitee.com/oauth/applications/16285

调用gitee第三方登录的url地址:Client ID&redirect_uri=自己应用的成功回调地址&response_type=code&state=1">



  • (2)社交服务OAuth2Controller来处理应用回调地址/callback请求

    分流程:(其实就只有三行代码是要自己配置的(OAuth2Controller的gitee的42-44行),其他的基本上是固定的)

    1>封装AccessTokenDTO对象然后发给码云服务器,如果AccessTokenDTO对象正确的话就返回一个通行令牌(其实准确来说是用户授权后会返回一个code,然后通过code来去找码云服务器获取到一个通行令牌,最后通过这个通行令牌去找码云服务器要这个用户在gitee上公开的资料信息)

    2>获取到了access_token通行令牌,转为通用gitee社交登录GiteeUser对象

    3>远程调用会员服务来进行社交登录

    package com.saodai.saodaimall.auth.controller;import com.alibaba.fastjson.TypeReference;
    import com.saodai.common.utils.R;
    import com.saodai.common.vo.MemberResponseVo;
    import com.saodai.saodaimall.auth.component.GitheeProvider;
    import com.saodai.saodaimall.auth.feign.MemberFeignService;
    import com.saodai.saodaimall.auth.vo.AccessTokenDTO;
    import com.saodai.saodaimall.auth.vo.GiteeUser;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.util.StringUtils;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;import javax.servlet.http.HttpSession;import static com.saodai.common.constant.AuthServerConstant.LOGIN_USER;/**
    * 社交第三方授权登录
    **/@Slf4j
    @Controller
    public class OAuth2Controller {@Autowiredprivate MemberFeignService memberFeignService;@Autowiredprivate AccessTokenDTO accessTokenDTO;@Autowiredprivate GitheeProvider githeeProvider;@GetMapping(value = "/callback")// /callback?code=e867a1f4575d4a6161e3249423a0403898253bc593e4b031a8771739ee6769f5&state=1public String gitee(@RequestParam(name = "code") String code,@RequestParam(name = "state") String state, HttpSession session) throws Exception {System.out.println(code);//下面三行代码都是自己应用的值,可以在gitee的第三方应用中看到对应的值accessTokenDTO.setClient_id("32459f971ce6d89cfb9f70899525455d0653cb804f16b38a304e3447dc97d673");accessTokenDTO.setClient_secret("f3046c911c03cadcded986062708150d4232af3ca6aef0259e5a0198d2c15ba5");accessTokenDTO.setRedirect_uri("http://auth.saodaimall.com/callback");accessTokenDTO.setCode(code);accessTokenDTO.setState(state);String accessToken = githeeProvider.getAccessToken(accessTokenDTO);//2、处理if (!StringUtils.isEmpty(accessToken)) {//获取到了access_token,转为通用gitee社交登录对象GiteeUser giteeUser = githeeProvider.getGiteeUser(accessToken);//知道了哪个社交用户//1)、当前用户如果是第一次进网站,自动注册进来(为当前社交用户生成一个会员信息,以后这个社交账号就对应指定的会员)//登录或者注册这个社交用户//调用远程服务R oauthLogin = memberFeignService.oauthLogin(giteeUser);if (oauthLogin.getCode() == 0) {MemberResponseVo memberRespOnseVo= oauthLogin.getData("data", new TypeReference() {});log.info("登录成功:用户信息:{}",memberResponseVo.toString());//1、第一次使用session,命令浏览器保存卡号,JSESSIONID这个COOKIE//以后浏览器访问哪个网站就会带上这个网站的COOKIE//TODO 1、默认发的令牌。当前域(解决子域session共享问题)//TODO 2、使用JSON的序列化方式来序列化对象到Redis中session.setAttribute(LOGIN_USER,memberResponseVo);//2、登录成功跳回首页return "redirect:http://saodaimall.com";} else {return "redirect:http://auth.saodaimall.com/login.html";}} else {return "redirect:http://auth.saodaimall.com/login.html";}}}

    package com.saodai.saodaimall.auth.vo;/*** AccessTokenDTO对象封装(gitee社交登录令牌)*/import org.springframework.stereotype.Component;@Component
    public class AccessTokenDTO {private String client_id;private String client_secret;private String code;private String redirect_uri;private String state;public String getClient_id() {return client_id;}public void setClient_id(String client_id) {this.client_id = client_id;}public String getClient_secret() {return client_secret;}public void setClient_secret(String client_secret) {this.client_secret = client_secret;}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public String getRedirect_uri() {return redirect_uri;}public void setRedirect_uri(String redirect_uri) {this.redirect_uri = redirect_uri;}public String getState() {return state;}public void setState(String state) {this.state = state;}
    }

    package com.saodai.saodaimall.auth.component;import com.alibaba.fastjson.JSON;
    import com.saodai.saodaimall.auth.vo.AccessTokenDTO;
    import com.saodai.saodaimall.auth.vo.GiteeUser;
    import okhttp3.*;
    import org.springframework.stereotype.Component;import java.io.IOException;/*** 请求码云服务器*/
    @Component
    public class GitheeProvider {//发起post请求获取AccessTokenpublic String getAccessToken(AccessTokenDTO accessTokenDTO){MediaType mediaType= MediaType.get("application/json; charset=utf-8");OkHttpClient client = new OkHttpClient();RequestBody body = RequestBody.create(mediaType, JSON.toJSONString(accessTokenDTO));Request request = new Request.Builder().url("https://gitee.com/oauth/token?grant_type=authorization_code&code="+accessTokenDTO.getCode()+"&client_id="+accessTokenDTO.getClient_id()+"&redirect_uri="+accessTokenDTO.getRedirect_uri()+"&client_secret="+accessTokenDTO.getClient_secret()).post(body).build();try (Response respOnse= client.newCall(request).execute()) {String string = response.body().string();System.out.println(string);String str1 = string.split(":")[1];String str2 = str1.split("\"")[1];return str2;} catch (IOException e) {e.printStackTrace();}return null;}//发起get请求返回GitUser对象,public GiteeUser getGiteeUser(String token){OkHttpClient client = new OkHttpClient();Request request = new Request.Builder().url("https://gitee.com/api/v5/user?access_token="+token).build();try (Response respOnse= client.newCall(request).execute()) {String string=response.body().string();GiteeUser giteeUser = JSON.parseObject(string, GiteeUser.class);return giteeUser;} catch (IOException e) {e.printStackTrace();}return null;}
    }

    package com.saodai.saodaimall.auth.vo;import lombok.Data;/*** GiteeUser对象封装(社交登录的gitee对象)*/
    @Data
    public class GiteeUser {//gitee用户名称private String name;//gitee用户idprivate String id;//gitee用户自我介绍private String bio;}

    (3)远程调用会员服务来进行社交登录

    /*** 社交登录* @param giteeUser* @return* @throws Exception*/@PostMapping(value = "/oauth2/login")public R oauthLogin(@RequestBody GiteeUser giteeUser) throws Exception {MemberEntity memberEntity = memberService.login(giteeUser);if (memberEntity != null) {return R.ok().setData(memberEntity);} else {return R.error(BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getCode(),BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getMessage());}}/*** 社交登录* @param giteeUser* @return* @throws Exception*/@Overridepublic MemberEntity login(GiteeUser giteeUser) throws Exception {//获取gitee用户唯一idString giteeUserId = giteeUser.getId();//1、判断当前社交用户是否已经登录过系统MemberEntity memberEntity = this.baseMapper.selectOne(new QueryWrapper().eq("social_id", giteeUserId));//这个用户已经注册过if (memberEntity != null) {return memberEntity;} else {//2、没有查到当前社交用户对应的记录我们就需要注册一个MemberEntity register = new MemberEntity();//社交gitee登录的id作为会员idregister.setId(Long.valueOf(giteeUserId));register.setSocialName(giteeUser.getName());register.setUsername(giteeUser.getName());register.setNickname(giteeUser.getName());register.setCreateTime(new Date());register.setSocialBio(giteeUser.getBio());register.setSocialId(giteeUserId);//把用户信息插入到数据库中this.baseMapper.insert(register);return register;}}

    package com.saodai.saodaimall.member.entity;import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.Data;import java.io.Serializable;
    import java.util.Date;/*** 会员*/
    @Data
    @TableName("ums_member")
    public class MemberEntity implements Serializable {private static final long serialVersiOnUID= 1L;/*** id*/@TableIdprivate Long id;/*** 会员等级id*/private Long levelId;/*** 用户名*/private String username;/*** 密码*/private String password;/*** 昵称*/private String nickname;/*** 手机号码*/private String mobile;/*** 邮箱*/private String email;/*** 头像*/private String header;/*** 性别*/private Integer gender;/*** 生日*/private Date birth;/*** 所在城市*/private String city;/*** 职业*/private String job;/*** 个性签名*/private String sign;/*** 用户来源*/private Integer sourceType;/*** 积分*/private Integer integration;/*** 成长值*/private Integer growth;/*** 启用状态*/private Integer status;/*** 注册时间*/private Date createTime;/*** 社交登录用户的ID*/private String socialId;/*** 社交登录用户的名称*/private String socialName;/*** 社交登录用户的自我介绍*/private String socialBio;}


    推荐阅读
    • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
    • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
      像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
    • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
    • 开发笔记:加密&json&StringIO模块&BytesIO模块
      篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
    • 计算机存储系统的层次结构及其优势
      本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
    • 开发笔记:Java是如何读取和写入浏览器Cookies的
      篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
    • Java中包装类的设计原因以及操作方法
      本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
    • React项目中运用React技巧解决实际问题的总结
      本文总结了在React项目中如何运用React技巧解决一些实际问题,包括取消请求和页面卸载的关联,利用useEffect和AbortController等技术实现请求的取消。文章中的代码是简化后的例子,但思想是相通的。 ... [详细]
    • 本文介绍了在iOS开发中使用UITextField实现字符限制的方法,包括利用代理方法和使用BNTextField-Limit库的实现策略。通过这些方法,开发者可以方便地限制UITextField的字符个数和输入规则。 ... [详细]
    • 本文介绍了Composer依赖管理的重要性及使用方法。对于现代语言而言,包管理器是标配,而Composer作为PHP的包管理器,解决了PEAR的问题,并且使用简单,方便提交自己的包。文章还提到了使用Composer能够避免各种include的问题,避免命名空间冲突,并且能够方便地安装升级扩展包。 ... [详细]
    • Android自定义控件绘图篇之Paint函数大汇总
      本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
    • Todayatworksomeonetriedtoconvincemethat:今天在工作中有人试图说服我:{$obj->getTableInfo()}isfine ... [详细]
    • 本文介绍了ASP.NET Core MVC的入门及基础使用教程,根据微软的文档学习,建议阅读英文文档以便更好理解,微软的工具化使用方便且开发速度快。通过vs2017新建项目,可以创建一个基础的ASP.NET网站,也可以实现动态网站开发。ASP.NET MVC框架及其工具简化了开发过程,包括建立业务的数据模型和控制器等步骤。 ... [详细]
    • 本文介绍了在Android开发中使用软引用和弱引用的应用。如果一个对象只具有软引用,那么只有在内存不够的情况下才会被回收,可以用来实现内存敏感的高速缓存;而如果一个对象只具有弱引用,不管内存是否足够,都会被垃圾回收器回收。软引用和弱引用还可以与引用队列联合使用,当被引用的对象被回收时,会将引用加入到关联的引用队列中。软引用和弱引用的根本区别在于生命周期的长短,弱引用的对象可能随时被回收,而软引用的对象只有在内存不够时才会被回收。 ... [详细]
    • 本文讨论了在ASP中创建RazorFunctions.cshtml文件时出现的问题,即ASP.global_asax不存在于命名空间ASP中。文章提供了解决该问题的代码示例,并详细解释了代码中涉及的关键概念,如HttpContext、Request和RouteData等。通过阅读本文,读者可以了解如何解决该问题并理解相关的ASP概念。 ... [详细]
    author-avatar
    Sunshine5585
    这个家伙很懒,什么也没留下!
    PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
    Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有