一.什么事单点登录?
答:单点登录SSO(Single Sign On)说得简单点就是在一个多系统共存的环境下,用户在一处登录后,就不用在其他系统中登录,也就是用户的一次登录能得到其他所有系统的信任。
二.单点登录三种常见方式:
1.session广播机制实现:即session复制
2.使用cookie+redis实现:
(1)在项目中任何一个模块进行登录,登录之后,把用户数据放到两个redis和cookie两个地方:
1>redis:在key :生成唯一随机值(ip,用户id等),value:用户数据
2>把redis里面生成的key值放到cookie里面。
(2)访问项目其他模块,发送请求带着cookie进行发送,获取cookie,拿着cookie值进行做事情:
1>获取到cookie值,到redis中进行查询,根据key值进行查询,如果查到用户数据就是登录。
3.使用token实现
token:按照一定的规则生成字符串,字符串可以包含用户信息(Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码)。
实现方式:
(1)在项目的某个模块进行登录,登录之后,按照一定的规则生成字符串,将用户数据放到字符串中,将字符串进行返回:
1>可以通过cookie进行返回
2>可以通过地址栏进行返回
(2)再访问项目中的其他模块,每次访问都在地址栏带着生成的字符串,在访问模块里面获取地址栏字符串,根据字符串获取用户信息,如果可以获取到,就是登录。
三.JWT是什么?
JWT:Json Web Token,是基于Json的一个公开规范,这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息,他的两大使用场景是:认证和数据交换
使用起来就是,由服务端根据规范生成一个令牌(token),并且发放给客户端。此时客户端请求服务端的时候就可以携带者令牌,以令牌来证明自己的身份信息。
简单来说,JWT就是已经定好了规则,可以使用JWT生成字符串,可以包含用户信息。
四.JWT的规则
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOjYyNywiZXhwIjoxNTcwMDE0ODg1fQ.vPbQh4syxNCzkKXKPSM93LzzLqoJdzPDNeKz8tz9cFM4NzhIOdPrJcH2DG
-9-9MCUufCgrAhhGjuo85GKV4bOQ
1.JWT的头信息:
{'typ': 'JWT','alg': 'HS256'}
2.有效载荷:主体部分(包含用户信息)
3.签名哈希:防伪标志
五.JWT的使用
1.引入JWT依赖
io.jsonwebtoken
jjwt
2.编写JWT工具类
packagecom.atguigu.commonutils;importio.jsonwebtoken.Claims;importio.jsonwebtoken.Jws;importio.jsonwebtoken.Jwts;importio.jsonwebtoken.SignatureAlgorithm;importorg.springframework.http.server.reactive.ServerHttpRequest;importorg.springframework.util.StringUtils;importjavax.servlet.http.HttpServletRequest;importjava.util.Date;/***@authorhelen
*@since2019/10/16*/
public classJwtUtils {//定义两个常量//token的过期时间
public static final long EXPIRE = 1000 * 60 * 60 * 24;//秘钥
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";/*生成token字符串的方法*/
public staticString getJwtToken(String id, String nickname){
String JwtToken=Jwts.builder()//设置token的头信息
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")//分类
.setSubject("guli-user")//设置token的过期时间
.setIssuedAt(newDate())
.setExpiration(new Date(System.currentTimeMillis() +EXPIRE))//设置token的主体信息,存储用户信息
.claim("id", id)
.claim("nickname", nickname)//设置签名哈希
.signWith(SignatureAlgorithm.HS256, APP_SECRET)
.compact();returnJwtToken;
}/*** 判断token是否存在与有效
*@paramjwtToken
*@return
*/
public static booleancheckToken(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是否存在与有效
*@paramrequest
*@return
*/
public static booleancheckToken(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
*@paramrequest
*@return
*/
public staticString getMemberIdByJwtToken(HttpServletRequest request) {
String jwtToken= request.getHeader("token");if(StringUtils.isEmpty(jwtToken)) {return "";
}
Jws claimsJws =Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
Claims claims=claimsJws.getBody();return (String)claims.get("id");
}
}
六.登录实现
1.编写用户实体类
packagecom.atguigu.educenter.entity;importcom.baomidou.mybatisplus.annotation.FieldFill;importcom.baomidou.mybatisplus.annotation.IdType;importjava.util.Date;importcom.baomidou.mybatisplus.annotation.TableField;importcom.baomidou.mybatisplus.annotation.TableId;importjava.io.Serializable;importio.swagger.annotations.ApiModel;importio.swagger.annotations.ApiModelProperty;importlombok.Data;importlombok.EqualsAndHashCode;importlombok.experimental.Accessors;/***
* 会员表
*
*
*@authortestjava
*@since2020-10-28*/@Data
@EqualsAndHashCode(callSuper= false)
@Accessors(chain= true)
@ApiModel(value="UcenterMember对象", description="会员表")public class UcenterMember implementsSerializable {private static final long serialVersionUID = 1L;
@ApiModelProperty(value= "会员id")
@TableId(value= "id", type =IdType.ID_WORKER_STR)privateString id;
@ApiModelProperty(value= "微信openid")privateString openid;
@ApiModelProperty(value= "手机号")privateString mobile;
@ApiModelProperty(value= "密码")privateString password;
@ApiModelProperty(value= "昵称")privateString nickname;
@ApiModelProperty(value= "性别 1 女,2 男")privateInteger sex;
@ApiModelProperty(value= "年龄")privateInteger age;
@ApiModelProperty(value= "用户头像")privateString avatar;
@ApiModelProperty(value= "用户签名")privateString sign;
@ApiModelProperty(value= "是否禁用 1(true)已禁用, 0(false)未禁用")privateBoolean isDisabled;
@ApiModelProperty(value= "逻辑删除 1(true)已删除, 0(false)未删除")privateBoolean isDeleted;
@ApiModelProperty(value= "创建时间")
@TableField(fill=FieldFill.INSERT)privateDate gmtCreate;
@ApiModelProperty(value= "更新时间")
@TableField(fill=FieldFill.INSERT_UPDATE)privateDate gmtModified;
}
2.编写controller层
packagecom.atguigu.educenter.controller;importcom.atguigu.commonutils.R;importcom.atguigu.educenter.entity.UcenterMember;importcom.atguigu.educenter.service.UcenterMemberService;importorg.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;/***
* 会员表 前端控制器
*
*
*@authortestjava
*@since2020-10-28*/@RestController
@RequestMapping("/educenter/member")
@CrossOriginpublic classUcenterMemberController {
@Autowired
UcenterMemberService memberService;//登录
@PostMapping("/login")publicR userLogin(@RequestBody UcenterMember member){//使用service中的方法实现登录//返回token值,使用jwt生成
String token =memberService.login(member);return R.ok().data("token",token);
}//注册
}
3.编写service层
packagecom.atguigu.educenter.service;importcom.atguigu.educenter.entity.UcenterMember;importcom.baomidou.mybatisplus.extension.service.IService;/***
* 会员表 服务类
*
*
*@authortestjava
*@since2020-10-28*/
public interface UcenterMemberService extends IService{//实现登录
String login(UcenterMember member);
}
4.编写serviceImpl
packagecom.atguigu.educenter.service.impl;importcom.atguigu.commonutils.JwtUtils;importcom.atguigu.commonutils.MD5;importcom.atguigu.educenter.entity.UcenterMember;importcom.atguigu.educenter.mapper.UcenterMemberMapper;importcom.atguigu.educenter.service.UcenterMemberService;importcom.atguigu.servicebase.exceptionhandler.GuliException;importcom.baomidou.mybatisplus.core.conditions.query.QueryWrapper;importcom.baomidou.mybatisplus.extension.service.impl.ServiceImpl;importorg.springframework.stereotype.Service;importorg.springframework.util.StringUtils;/***
* 会员表 服务实现类
*
*
*@authortestjava
*@since2020-10-28*/@Servicepublic class UcenterMemberServiceImpl extends ServiceImpl implementsUcenterMemberService {//实现登录
@OverridepublicString login(UcenterMember member) {//获取手机号和密码
String mobile =member.getMobile();
String password=member.getPassword();//判断手机号和密码是否为空
if(StringUtils.isEmpty(mobile)||StringUtils.isEmpty(password)){throw new GuliException(20001,"手机号或者密码为空,登录失败");
}//判断手机号是否正确
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("mobile",mobile);
UcenterMember mobileMember=baseMapper.selectOne(wrapper);//判断查询的对象是否为空
if(mobileMember==null){//没有这个手机号
throw new GuliException(20001,"手机号不存在,登录失败");
}//判断密码是否正确
/**因为数据库中的密码是加密的,
* 所以需要先将传入的密码进行加密,
* 再和数据库的密码进行比较,
* 使用MD5加密*/
if(!MD5.encrypt(password).equals(mobileMember.getPassword())){throw new GuliException(20001,"密码不正确,登录失败");
}//判断用户是否禁用
if(mobileMember.getIsDisabled()){throw new GuliException(20001,"用户已禁用,登录失败");
}//登录成功,使用jwt工具类生成token
String jwtToken =JwtUtils.getJwtToken(mobileMember.getId(), mobileMember.getNickname());returnjwtToken;
}
}
5.swagger测试
六.注册功能
1.编写实体类
packagecom.atguigu.educenter.entity.vo;importio.swagger.annotations.ApiModel;importio.swagger.annotations.ApiModelProperty;importlombok.Data;/*** author LiQinZhen
* date 2020/10/29
* description: 注册实体类*/@Data
@ApiModel(value="注册对象", description="注册对象")public classRegisterVo {
@ApiModelProperty(value= "昵称")privateString nickname;
@ApiModelProperty(value= "手机号")privateString mobile;
@ApiModelProperty(value= "密码")privateString password;
@ApiModelProperty(value= "验证码")privateString code;
}
2.在controller中创建注册的方法
//注册
@RequestMapping("/register")publicR registerUser(@RequestBody RegisterVo registerVo){
memberService.register(registerVo);returnR.ok();
}
3.在service创建注册方法
public interface UcenterMemberService extends IService{//实现登录
String login(UcenterMember member);//注册
voidregister(RegisterVo registerVo);
}
4.编写service的实现类Impl
packagecom.atguigu.educenter.service.impl;importcom.atguigu.commonutils.JwtUtils;importcom.atguigu.commonutils.MD5;importcom.atguigu.educenter.entity.UcenterMember;importcom.atguigu.educenter.entity.vo.RegisterVo;importcom.atguigu.educenter.mapper.UcenterMemberMapper;importcom.atguigu.educenter.service.UcenterMemberService;importcom.atguigu.servicebase.exceptionhandler.GuliException;importcom.baomidou.mybatisplus.core.conditions.query.QueryWrapper;importcom.baomidou.mybatisplus.extension.service.impl.ServiceImpl;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.stereotype.Service;importorg.springframework.util.StringUtils;/***
* 会员表 服务实现类
*
*
*@authortestjava
*@since2020-10-28*/@Servicepublic class UcenterMemberServiceImpl extends ServiceImpl implementsUcenterMemberService {
//注入redis模板
@Autowired
RedisTemplateredisTemplate;//实现登录
@OverridepublicString login(UcenterMember member) {//获取手机号和密码
String mobile =member.getMobile();
String password=member.getPassword();//判断手机号和密码是否为空
if(StringUtils.isEmpty(mobile)||StringUtils.isEmpty(password)){throw new GuliException(20001,"手机号或者密码为空,登录失败");
}//判断手机号是否正确
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("mobile",mobile);
UcenterMember mobileMember=baseMapper.selectOne(wrapper);//判断查询的对象是否为空
if(mobileMember==null){//没有这个手机号
throw new GuliException(20001,"手机号不存在,登录失败");
}//判断密码是否正确
/**因为数据库中的密码是加密的,
* 所以需要先将传入的密码进行加密,
* 再和数据库的密码进行比较,
* 使用MD5加密*/
if(!MD5.encrypt(password).equals(mobileMember.getPassword())){throw new GuliException(20001,"密码不正确,登录失败");
}//判断用户是否禁用
if(mobileMember.getIsDisabled()){throw new GuliException(20001,"用户已禁用,登录失败");
}//登录成功,使用jwt工具类生成token
String jwtToken =JwtUtils.getJwtToken(mobileMember.getId(), mobileMember.getNickname());returnjwtToken;
}//注册方法
@Overridepublic voidregister(RegisterVo registerVo) {//获取注册的数据
String code = registerVo.getCode();//验证码
String mobile = registerVo.getMobile();//手机号
String nickname = registerVo.getNickname();//昵称
String password = registerVo.getPassword();//密码//非空判断
if(StringUtils.isEmpty(code)||StringUtils.isEmpty(mobile)||StringUtils.isEmpty(nickname)||StringUtils.isEmpty(password)){throw new GuliException(20001,"验证码,手机号,昵称或者密码为空,注册失败");
}//判断验证码是否正确,即发送到手机的验证码和数据库存的是否一样//获取redis中的验证码
String redisCode =redisTemplate.opsForValue().get(mobile);//比较输入的验证码和redis中的验证码是否一样
if(!code.equals(redisCode)){throw new GuliException(20001,"验证码不正确,注册失败");
}//判断手机号是否相同,如果表里面存在相同的手机号则不进行添加//先根据手机号在数据库查询数据
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("mobile",mobile);
Integer count=baseMapper.selectCount(wrapper);if(count>0){throw new GuliException(20001,"手机号已存在,注册失败");
}//数据添加到数据库
UcenterMember member = newUcenterMember();
member.setMobile(mobile);
member.setNickname(nickname);
member.setPassword(MD5.encrypt(password));
member.setAvatar("http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132");
member.setIsDisabled(false);
baseMapper.insert(member);
}
}
5.根据token获取用户信息
//根据token获取用户信息
@GetMapping("/getMemberInfo")publicR getMemberInfo(HttpServletRequest request){//调用jwt的方法,根据request对象获取头信息,返回用户id
String memberId =JwtUtils.getMemberIdByJwtToken(request);//查询数据库,根据用户id获取用户信息
UcenterMember member =memberService.getById(memberId);return R.ok().data("userInfo",member);
}