單點登錄(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);
    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM