单点登录(token,JWT)

一.什么事单点登录?

答:单点登录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);
    }
原文地址:https://www.cnblogs.com/liqinzhen/p/13879724.html