登录功能
- 一.账户登录介绍
- 二.社交登录介绍
- 三.多系统登录-单点登录
一.账户登录介绍
1.介绍
1.1 功能需求- 数据:用户名(手机)+密码
1.2 登录-数据的封装的实体类(auth)
@Data
public class UserLoginVo {
//loginAccount
private String loginacct;
private String password;
}
1.3 登录(远程member)
1.3.1 登录的实体类
@Data
public class MemberLoginVo {
private String loginacct;
private String password;
}
1.3.2 登录的controller
//登录服务
@PostMapping("/login")
public R login(@RequestBody MemberLoginVo vo) {
MemberEntity entity= memberService.login(vo);
if (entity!=null){
System.out.println("member的远程MemberController登录成功");
return R.ok().put("member",entity);
}else {
System.out.println("MemberController登录失败");
return R.error(BizCodeEnume.LOGINACCT_PASSWORD_EXCEPTION.getCode(),BizCodeEnume.LOGINACCT_PASSWORD_EXCEPTION.getMsg());
}
}
1.3.3 登录的service实现类
//2.登录服务
@Override
public MemberEntity login(MemberLoginVo vo) {
String loginacct = vo.getLoginacct();
String password = vo.getPassword();
//1.去数据库查询
QueryWrapper<MemberEntity> wrapper = new QueryWrapper<MemberEntity>().eq("username", loginacct).or().eq("mobile", loginacct);
MemberEntity memberEntity = baseMapper.selectOne(wrapper);
if (memberEntity == null) {
System.out.println("登录失败!"+"数据库 数据不存在");
return null;
} else {
//2.判断密码是否正确
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
boolean matches = passwordEncoder.matches(password, memberEntity.getPassword());
if (matches) {
System.out.println("member的实现类,登录成功");
return memberEntity;
} else {
System.out.println("登录失败!"+"密码错误");
return null;
}
}
}
1.4 登录(auth)
1.4.1 登录的feign的service(auth)
@FeignClient("gulimall-member")
public interface MemberFeignService {
//由于MemberLoginVo 与UserLoginVo 使用相同属性,所以使用auth的UserLoginVo
@PostMapping("/member/member/login")
//public R login(@RequestBody MemberLoginVo vo)
R login(@RequestBody UserLoginVo vo);
}
1.4.2 登录的controller(auth)
//login 功能
@PostMapping("/login")
public String login(UserLoginVo vo,RedirectAttributes redirectAttributes){
System.out.println("vo: "+vo);
//远程登录member
R r = memberFeignService.login(vo);
if (r.getCode()==0){
//成功
System.out.println("auth的LoginController登录成功");
return "redirect:http://gulimall.com";
}else {
System.out.println("auth的LoginController登录失败");
Map<String, String> errors = new HashMap<>();
errors.put("msg", r.getData("msg",new TypeReference<String>() {}));
redirectAttributes.addFlashAttribute("errors", errors);
return "redirect:http://auth.gulimall.com/login.html";
}
}
1.4.3 登录的页面渲染login.html(auth)
1.4.4 登录的测试 ok
二.社交登录介绍
1.介绍
2.OAuth2.0
OAuth: OAuth(开放授权)是一个开放标准,允许用户授权第三方网站访问他们存储
在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或分享他们 数据的所有内容。
OAuth2.0:对于用户相关的OpenAPI(例如获取用户信息,动态同步,照片,日志,分 享等),为了保护用户数据的安全和隐私,第三方网站访问用户数据前都需要显式的向用户征求授权。
官方版流程:
(A)用户打开客户端以后,客户端要求用户给予授权。
(B)用户同意给予客户端授权。
(C)客户端使用上一步获得的授权,向认证服务器申请令牌。
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源。
(F)资源服务器确认令牌无误,同意向客户端开放资源。
3.微博登陆 准备
3.1 进入微博开放平台
3.1.1 步骤
OAuth2.0授权服务,查看文档
3.1.2 修改login.html
1). 引导用户到授权页
https://api.weibo.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI
<a href="https://api.weibo.com/oauth2/authorize?client_id=3133671081&response_type=code&redirect_uri=http://auth.gulimall.com/oauth2.0/weibo/success">
<img style="width: 50px;height: 18px" src="/static/login/JD_img/weibo.png" />
</a>
client_id:设置自己的client_id
redirect_uri:设置自己的redirect_uri
2). 用户授权成功,页面跳转至
YOUR_REGISTERED_REDIRECT_URI/?code=CODE
3) . 换取Access Token
login.html页面,点击微博登录按钮,先登录
获取:code
再 换取Access Token
https://api.weibo.com/oauth2/access_token?client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&grant_type=authorization_code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI&code=CODE
其中client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET可以使用basic方式加入header中,返回值 Access Token为:
4). 使用获得的Access Token调用API
5). 其他端口
6). 注意:
3.2 社交登录回调-代码
3.2.1 member项目
1).controller
//社交登录-登录服务
@PostMapping("/oauth2/login")
public R oauthLogin(@RequestBody SocialUser socialUser) throws Exception {
MemberEntity memberEntity= memberService.oauthLogin(socialUser);
if (memberEntity!=null){
//TODO 1.成功登录处理
return R.ok().setData(memberEntity);
}else {
return R.error(BizCodeEnume.LOGINACCT_PASSWORD_EXCEPTION.getCode(),BizCodeEnume.LOGINACCT_PASSWORD_EXCEPTION.getMsg());
}
}
2).service实现类MemberServiceImpl
//3.社交登录-登录服务
@Override
public MemberEntity oauthLogin(SocialUser socialUser) throws Exception {
//登录和注册合并逻辑
String uid = socialUser.getUid();
//1.判断当前社交用户,是否登录过系统
MemberEntity memberEntity = baseMapper.selectOne(new QueryWrapper<MemberEntity>().eq("social_uid", uid));
if (memberEntity != null) {
//此用户已经注册,更新数据
MemberEntity update = new MemberEntity();
update.setId(memberEntity.getId());
update.setAccessToken(socialUser.getAccess_token());
update.setExpiresIn(socialUser.getExpires_in());
baseMapper.updateById(update);
memberEntity.setAccessToken(socialUser.getAccess_token());
memberEntity.setExpiresIn(socialUser.getExpires_in());
return memberEntity;
} else {
//2.数据库没此数据,注册一个(说明没有登录过)
MemberEntity register = new MemberEntity();
//3.查询当前社交用户的社交账号信息(昵称性别等)--查询微博后台数据
try {
HashMap<String, String> query = new HashMap<>();
query.put("access_token", socialUser.getAccess_token());
query.put("uid", socialUser.getUid());
//从后台获取用户的信息
HttpResponse response = HttpUtils.doGet("https://api.weibo.com", "/2/users/show.json", "get", new HashMap<String, String>(), query);
if (response.getStatusLine().getStatusCode() == 200) {
//查询成功
String json = EntityUtils.toString(response.getEntity());
JSONObject jsonObject = JSON.parseObject(json);
//昵称
String name = jsonObject.getString("name");
String gender = jsonObject.getString("gender");
//省略其他信息....
register.setNickname(name);
register.setUsername(name);
register.setLevelId(1L);
register.setCreateTime(new Date());
register.setGender("m".equals(gender) ? 1 : 0);
//省略其他信息....
}
} catch (Exception e) {
e.printStackTrace();
}
register.setSocialUid(socialUser.getUid());
register.setAccessToken(socialUser.getAccess_token());
register.setExpiresIn(socialUser.getExpires_in());
System.out.println("register: "+register);
baseMapper.insert(register);
return register;
}
}
3.2.2 auth项目
1).feigin接口–(远程调用member的社交登录)
@FeignClient("gulimall-member")
public interface MemberFeignService {
//社交登录-登录服务(微博)
@PostMapping("/member/member/oauth2/login")
public R oauthLogin(@RequestBody SocialUser socialUser) throws Exception;
}
2).MemberRespVo(登录后的响应数据)
@Data
@ToString
public class MemberRespVo implements Serializable {
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;
/** 社交登录UID
*/
private String socialUid;
/** 社交登录TOKEN
*/
private String accessToken;
/** 社交登录过期时间
*/
private Long expiresIn;
}
3).Oauth2Controller
@Autowired
private MemberFeignService memberFeignService;
//社交登录
@GetMapping("/oauth2.0/weibo/success")
public String weibo(@RequestParam("code") String code, HttpSession session) throws Exception {
Map<String, String> map = new HashMap<>();
map.put("client_id","3133671081");
map.put("client_secret","99b103a33786d6af71338bc796bc242f");
map.put("grant_type","authorization_code");
map.put("redirect_uri","http://auth.gulimall.com/oauth2.0/weibo/success");
map.put("code",code);
System.out.println("code: "+code);
//1、根据用户授权返回的code换取access_token
HttpResponse response = HttpUtils.doPost("https://api.weibo.com", "/oauth2/access_token", "post", new HashMap<>(), map, new HashMap<>());
//2、处理
System.out.println("response.getStatusLine().getStatusCode(): "+response.getStatusLine().getStatusCode());
if (response.getStatusLine().getStatusCode() == 200) {
//获取到了access_token,转为通用社交登录对象
String json = EntityUtils.toString(response.getEntity());
//String json = JSON.toJSONString(response.getEntity());
SocialUser socialUser = JSON.parseObject(json, SocialUser.class);
//知道了哪个社交用户
//1)、当前用户如果是第一次进网站,自动注册进来(为当前社交用户生成一个会员信息,以后这个社交账号就对应指定的会员)
//登录或者注册这个社交用户
System.out.println(socialUser.getAccess_token());
//调用远程服务
R oauthLogin = memberFeignService.oauthLogin(socialUser);
if (oauthLogin.getCode() == 0) {
MemberRespVo data = oauthLogin.getData("data", new TypeReference<MemberRespVo>() {});
log.info("登录成功:用户信息:{}",data.toString());
//2、登录成功跳回首页
return "redirect:http://gulimall.com";
} else {
System.out.println("登录失败1");
return "redirect:http://auth.gulimall.com/login.html";
}
} else {
System.out.println("登录失败");
return "redirect:http://auth.gulimall.com/login.html";
}
}
4).login.html
<li>
<a href="https://api.weibo.com/oauth2/authorize?client_id=3133671081&response_type=code&redirect_uri=http://auth.gulimall.com/oauth2.0/weibo/success">
<img style="width: 50px;height: 18px" src="/static/login/JD_img/weibo.png" />
</a>
</li>
5).测试,ok
点击登录,跳转登录页面,点击微博,授权后,登录成功,返回商品首页(gulimall.com)
4.session的数据共享-跨域
4.1 问题描述。登录成功后在产品首页数据显示问题,如何携带数据进行跨域?
当社交登录微博成功之后,会跳转到产品首页(gulimall.com),(auth项目—>product项目)。那么如何携带数据,首页显示登录后的数据呢?
4.2 解决方案
4.2.1.方案一:session,不合适,session不能在不同域名之间共享
由于微博登录后,从auth项目,跳转到product项目,跨了域名。因为session不能跨域名共享,所以session不可用。
1).session共享原理
2).session的问题
a.session不能跨不同域共享
b.同一个服务,复制多份,session不同步问题
c.不同服务,session不能共享问题
4.2.2.方案二:分布式session
1).问题
2).session共享问题解决
a. session复制(占内存,不可取)
session传输需要时间,占内存
b. 客户端存储(不安全,不可取)
不安全,cookie长度有限制,不能保存大量信息
c. hash一致性(有缺点,但可用)
缺点:web_server不能
d.统一存储(可用)–使用springSession
缺点:修改大量代码
优点:不担心服务器的水平扩展,服务器宕机问题
e.springSession整合了session统一存储
4.2.3.方案二:Springsession
5.SpringSession
SpringSession文档
HttpSession with Redis文档
1.介绍,子域向父域扩展
1.1 需求描述
auth的微博登录后,跳转到product的首页。auth.gulimall.com—>gulimall.com
1.2 问题描述,session共享问题,子域session共享
2.应用–auth、product
2.1.导入依赖
<!--整合spring-session完成Session共享问题-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.2.配置
#配置redis
spring.redis.host=192.168.56.10
spring.redis.port=6379
#session保存到redis
spring.session.store-type=redis
#设置session过期时间
server.servlet.session.timeout=30m
2.3.启动类
@EnableRedisHttpSession //整合Redis作为session存储
2.4.配置类
@Configuration
public class SessionConfig {
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("GULISESSION");
// serializer.setCookiePath("/");
serializer.setDomainName("gulimall.com");
return serializer;
}
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer(){
return new GenericJackson2JsonRedisSerializer();
}
}
2.5.auth的controller使用session
public String weibo(@RequestParam("code") String code, HttpSession session){
session.setAttribute("LOGIN_USER",data);
}
2.6.product的index.html使用session数据
你好,请登录: [[${session.LOGIN_USER==null?'':session.LOGIN_USER.nickname}]]
3.springSession原理
4.springSession其他操作
1).product的首页index.html页面修改
<li>
<a th:if="${session.loginUser!=null}" style="color: darkcyan">欢迎您,[[${session.loginUser==null?'':session.loginUser.nickname}]]</a>
<a class="loginOut">退出</a>
<a href="http://auth.gulimall.com/login.html" th:if="${session.loginUser==null}" style="color: crimson">请您登录!</a>
</li>
<li>
<a href="http://auth.gulimall.com/reg.html" class="li_2" th:if="${session.loginUser==null}" style="color: crimson">免费注册</a>
</li>
2).product的首页index.html的退出操作
a.退出标签+js
<a class="loginOut">退出</a>
//登录退出,清除session
$(".loginOut").click(function () {
var result = confirm("确定要退出吗?");
if(result){
location.href="http://auth.gulimall.com/logout";
}
})
b.退出的方法
//login退出 功能
@RequestMapping("logout")
public String tologout(HttpServletRequest request){
/* 清除session的方案一 */
Enumeration em = request.getSession().getAttributeNames();
while(em.hasMoreElements()){
request.getSession().removeAttribute(em.nextElement().toString());
request.getSession().removeAttribute(AuthServerConstant.LOGIN_USER);
}
/* 清除session的方案二 */
// HttpSession session = request.getSession();
// session.invalidate();
return "redirect:http://gulimall.com";
}
3).登录跳转(如果已经登录成功,再次刷新登录页面,直接跳转到首页)
//登录页
@GetMapping("/login.html")
public String loginPage(HttpSession session){
Object attribute = session.getAttribute(AuthServerConstant.LOGIN_USER);
if (attribute==null){
//未登录。返回登录页
return "login";
}else {
//登录成功,重定向到商品首页
return "redirect:http://gulimall.com";
}
}
4).search的页面关于登录注册的修改
a.导入依赖
<!--整合spring-session完成Session共享问题-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
b.配置
#配置redis
spring.redis.host=192.168.56.10
spring.redis.port=6379
#session保存到redis
spring.session.store-type=redis
#设置session过期时间
server.servlet.session.timeout=30m
c.启动类
@EnableRedisHttpSession //整合Redis作为session存储
d.配置类
@Configuration
public class SessionConfig {
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("GULISESSION");
// serializer.setCookiePath("/");
serializer.setDomainName("gulimall.com");
return serializer;
}
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer(){
return new GenericJackson2JsonRedisSerializer();
}
}
e.list.html修改 登录、注册
<li>
<a class="li_2" th:if="${session.loginUser!=null}" style="color: cadetblue">欢迎您,[[${session.loginUser==null?'':session.loginUser.nickname}]]</a>
<a href="http://auth.gulimall.com/login.html" class="li_2" th:if="${session.loginUser==null}" style="color: red">主人,请登录</a>
</li>
<li>
<a href="http://auth.gulimall.com/reg.html" th:if="${session.loginUser==null}" style="color: red">免费注册</a>
</li>
三.多系统登录-单点登录
1.介绍
在谷粒商城项目呢,实现了微博、账号登录,仅仅是单系统登录,多系统登录怎么办呢?
多系统登录,实现一个登录,多系统使用。