一.什么事单点登录?
答:单点登录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依赖
<!--JWT--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> </dependency>
2.编写JWT工具类
package com.atguigu.commonutils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; import java.util.Date; /** * @author helen * @since 2019/10/16 */ public class JwtUtils { //定义两个常量 //token的过期时间 public static final long EXPIRE = 1000 * 60 * 60 * 24; //秘钥 public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO"; /*生成token字符串的方法*/ public static String getJwtToken(String id, String nickname){ String JwtToken = Jwts.builder() //设置token的头信息 .setHeaderParam("typ", "JWT") .setHeaderParam("alg", "HS256") //分类 .setSubject("guli-user") //设置token的过期时间 .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"); } }
六.登录实现
1.编写用户实体类
package com.atguigu.educenter.entity; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.IdType; import java.util.Date; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import java.io.Serializable; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; /** * <p> * 会员表 * </p> * * @author testjava * @since 2020-10-28 */ @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @ApiModel(value="UcenterMember对象", description="会员表") public class UcenterMember implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "会员id") @TableId(value = "id", type = IdType.ID_WORKER_STR) 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 gmtCreate; @ApiModelProperty(value = "更新时间") @TableField(fill = FieldFill.INSERT_UPDATE) private Date gmtModified; }
2.编写controller层
package com.atguigu.educenter.controller; import com.atguigu.commonutils.R; import com.atguigu.educenter.entity.UcenterMember; import com.atguigu.educenter.service.UcenterMemberService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; /** * <p> * 会员表 前端控制器 * </p> * * @author testjava * @since 2020-10-28 */ @RestController @RequestMapping("/educenter/member") @CrossOrigin public class UcenterMemberController { @Autowired UcenterMemberService memberService; //登录 @PostMapping("/login") public R userLogin(@RequestBody UcenterMember member){ //使用service中的方法实现登录 //返回token值,使用jwt生成 String token = memberService.login(member); return R.ok().data("token",token); } //注册 }
3.编写service层
package com.atguigu.educenter.service; import com.atguigu.educenter.entity.UcenterMember; import com.baomidou.mybatisplus.extension.service.IService; /** * <p> * 会员表 服务类 * </p> * * @author testjava * @since 2020-10-28 */ public interface UcenterMemberService extends IService<UcenterMember> { //实现登录 String login(UcenterMember member); }
4.编写serviceImpl
package com.atguigu.educenter.service.impl; import com.atguigu.commonutils.JwtUtils; import com.atguigu.commonutils.MD5; import com.atguigu.educenter.entity.UcenterMember; import com.atguigu.educenter.mapper.UcenterMemberMapper; import com.atguigu.educenter.service.UcenterMemberService; import com.atguigu.servicebase.exceptionhandler.GuliException; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; /** * <p> * 会员表 服务实现类 * </p> * * @author testjava * @since 2020-10-28 */ @Service public class UcenterMemberServiceImpl extends ServiceImpl<UcenterMemberMapper, UcenterMember> implements UcenterMemberService { //实现登录 @Override public String login(UcenterMember member) { //获取手机号和密码 String mobile = member.getMobile(); String password = member.getPassword(); //判断手机号和密码是否为空 if(StringUtils.isEmpty(mobile)|| StringUtils.isEmpty(password)){ throw new GuliException(20001,"手机号或者密码为空,登录失败"); } //判断手机号是否正确 QueryWrapper<UcenterMember> 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()); return jwtToken; } }
5.swagger测试
六.注册功能
1.编写实体类
package com.atguigu.educenter.entity.vo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** * author LiQinZhen * date 2020/10/29 * description: 注册实体类 */ @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; }
2.在controller中创建注册的方法
//注册 @RequestMapping("/register") public R registerUser(@RequestBody RegisterVo registerVo){ memberService.register(registerVo); return R.ok(); }
3.在service创建注册方法
public interface UcenterMemberService extends IService<UcenterMember> { //实现登录 String login(UcenterMember member); //注册 void register(RegisterVo registerVo); }
4.编写service的实现类Impl
package com.atguigu.educenter.service.impl; import com.atguigu.commonutils.JwtUtils; import com.atguigu.commonutils.MD5; import com.atguigu.educenter.entity.UcenterMember; import com.atguigu.educenter.entity.vo.RegisterVo; import com.atguigu.educenter.mapper.UcenterMemberMapper; import com.atguigu.educenter.service.UcenterMemberService; import com.atguigu.servicebase.exceptionhandler.GuliException; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; /** * <p> * 会员表 服务实现类 * </p> * * @author testjava * @since 2020-10-28 */ @Service public class UcenterMemberServiceImpl extends ServiceImpl<UcenterMemberMapper, UcenterMember> implements UcenterMemberService { //注入redis模板
@Autowired RedisTemplate<String,String> redisTemplate; //实现登录 @Override public String login(UcenterMember member) { //获取手机号和密码 String mobile = member.getMobile(); String password = member.getPassword(); //判断手机号和密码是否为空 if(StringUtils.isEmpty(mobile)|| StringUtils.isEmpty(password)){ throw new GuliException(20001,"手机号或者密码为空,登录失败"); } //判断手机号是否正确 QueryWrapper<UcenterMember> 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()); return jwtToken; } //注册方法 @Override public void register(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<UcenterMember> wrapper = new QueryWrapper<>(); wrapper.eq("mobile",mobile); Integer count = baseMapper.selectCount(wrapper); if(count>0){ throw new GuliException(20001,"手机号已存在,注册失败"); } //数据添加到数据库 UcenterMember member = new UcenterMember(); 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") public R getMemberInfo(HttpServletRequest request){ //调用jwt的方法,根据request对象获取头信息,返回用户id String memberId = JwtUtils.getMemberIdByJwtToken(request); //查询数据库,根据用户id获取用户信息 UcenterMember member = memberService.getById(memberId); return R.ok().data("userInfo",member); }