博客 / 詳情

返回

【笑小楓的SpringBoot系列】【九】SpringBoot用户登錄功能實現

關於本文

其實用户登錄攔截的這塊不想這麼早寫,加個登錄後面好多東西就要考慮登錄狀態了,我其實想把這個系列寫成非必要關係,解耦性比較強的系列。但是,寫完redis,總是感覺登錄是對它最簡單的實踐,那就加上吧,反正後面很多文章也會用到,但大多文章我仍會不考慮登錄狀態。

這裏只是講明白登錄機制,如何實現。實際使用中會考慮很多別的,例如用户權限,登錄機制限制等等~這裏就先不做過多的敍述。

這裏只講技術和實現,不講任何業務場景哈,牽扯到場景的問題就會複雜N倍,而且通用性往往不盡人意~

本文依賴於redis和mybatis plus,這些都是最基礎的模塊,所以都放在最前面寫了,大家可以線過一下相關的文章。

[SpringBoot集成Redis]()

[SpringBoot集成Mybatis Plus]()

本文是基於jwt+redis來實現。接下來我們一起看看吧

什麼是JWT

什麼是JWT,JWT(全稱:Json Web Token)是一個開放標準(RFC 7519),它定義了一種緊湊的、自包含的方式,用於作為JSON對象在各方之間安全地傳輸信息。 該信息可以被驗證和信任,因為它是數字簽名的。

上面説法比較文縐縐,簡單點説就是一種認證機制,讓後台知道該請求是來自於受信的客户端。

JWT的優點

  • json格式的通用性,所以JWT可以跨語言支持,比如Java、JavaScript、PHP、Node等等。
  • 可以利用Payload存儲一些非敏感的信息。
  • 便於傳輸,JWT結構簡單,字節佔用小。
  • 不需要在服務端保存會話信息,易於應用的擴展。

JWT的缺點

  • 安全性沒法保證,所以jwt裏不能存儲敏感數據。因為jwt的payload並沒有加密,只是用Base64編碼而已。
  • 無法中途廢棄。因為一旦簽發了一個jwt,在到期之前始終都是有效的,如果用户信息發生更新了,只能等舊的jwt過期後重新簽發新的jwt。
  • 續簽問題。當簽發的jwt保存在客户端,客户端一直在操作頁面,按道理應該一直為客户端續長有效時間,否則當jwt有效期到了就會導致用户需要重新登錄。

補償JWT的缺點

  • 針對JWT的缺點,我們在使用的過程中,只儲存常用的無敏感數據,比如用户ID,用户角色等。
  • 中途廢棄和續簽問題,通過和redis配合使用,將token返回時,同步保存redis,通過控制token在redis的有效期來進行控制。
  • 還可以通過統計redis有效數據,對在線用户進行統計或強制下線等操作。

用户登錄流程

以用户登錄功能為例,程序流程如下:

用户登錄

笑小楓-www.xiaoxiaofeng.site

token認證訪問

笑小楓-www.xiaoxiaofeng.site

注:系統中採用JWT對用户登錄授權驗證。

基於Token的身份驗證流程

使用基於Token的身份驗證,在服務端不需要存儲用户的登錄記錄。大概的流程是這樣的:

1、客户端使用用户名或密碼請求登錄;

2、服務端收到請求,去驗證用户名與密碼;

3、驗證成功後,服務端會使用JWT簽發一個Token,保存到Redis中,同時再把這個Token發送給客户端;

4、客户端收到Token以後可以把它存儲起來,比如放在Cookie裏或者Local Storage裏;

5、客户端每次向服務端請求資源的時候需要在請求Header裏面帶着服務端簽發的Token;

6、服務端收到請求,然後去驗證客户端請求裏面帶着的Token,如果驗證成功,就向客户端返回請求的數據。驗證失敗,返回失敗原因。

功能實現

自動生成的User.javaUserMapper.javaUserMapper.xml...就不貼代碼了,沒有業務代碼,且佔的篇幅過大。在[SpringBoot集成Mybatis Plus]()文章中創建過就可以忽略了哈~

代碼生成見[SpringBoot集成Mybatis Plus]()一文。

涉及到的表sql

在[SpringBoot集成Mybatis Plus]()文章中創建過的就可以忽略了

CREATE TABLE `usc_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `account` varchar(30) DEFAULT NULL COMMENT '用户賬號',
  `user_name` varchar(30) DEFAULT NULL COMMENT '用户姓名',
  `nick_name` varchar(30) DEFAULT NULL COMMENT '用户暱稱',
  `user_type` varchar(2) DEFAULT '00' COMMENT '用户類型(00系統用户,01小程序用户)',
  `email` varchar(50) DEFAULT '' COMMENT '用户郵箱',
  `phone` varchar(11) DEFAULT '' COMMENT '手機號碼',
  `sex` char(1) DEFAULT '0' COMMENT '用户性別(0男 1女 2未知)',
  `avatar` varchar(100) DEFAULT '' COMMENT '頭像地址',
  `salt` varchar(32) DEFAULT NULL COMMENT '用户加密鹽值',
  `password` varchar(100) DEFAULT '' COMMENT '密碼',
  `status` char(1) DEFAULT '0' COMMENT '帳號狀態(0正常 1停用)',
  `create_id` bigint(20) DEFAULT NULL COMMENT '創建人id',
  `create_name` varchar(64) DEFAULT '' COMMENT '創建者',
  `create_time` datetime DEFAULT NULL COMMENT '創建時間',
  `update_id` bigint(20) DEFAULT NULL COMMENT '更新人id',
  `update_name` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新時間',
  `delete_flag` tinyint(1) DEFAULT '0' COMMENT '刪除標誌',
  `remark` varchar(500) DEFAULT NULL COMMENT '備註',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户中心-用户信息表';

引入依賴

首先我們在pom文件中引入依賴

<!-- 引入JWT相關 -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.18.3</version>
</dependency>

通用類

先把我們的通用類創建一下,一些常量,我們統一放在一個配置中,方便日後維護。

  • 在config.bean創建通用類GlobalConfig.java
package com.maple.demo.config.bean;

/**
 * @author 笑小楓
 * @date 2022/7/20
 */
public class GlobalConfig {

    private GlobalConfig() {

    }

    /**
     * 用户儲存在redis中的過期時間
     */
    public static final long EXPIRE_TIME = 60 * 60 * 12L;

    /**
     * 生成token的私鑰
     */
    public static final String SECRET = "maple123";
    
    /**
     * 前端傳遞token的header名稱
     */
    public static final String TOKEN_NAME = "Authorization";

    /**
     * 用户登錄token保存在redis的key值
     *
     * @param account 用户登錄帳號
     * @return token保存在redis的key
     */
    public static String getRedisUserKey(String account) {
        return "MAPLE_ADMIN:" + account;
    }
}
  • config.bean創建TokenBean.java保存的jwt的信息
package com.maple.demo.config.bean;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;

/**
 * @author 笑小楓
 * @date 2022/7/20
 */
@Data
@Builder
@RequiredArgsConstructor
@AllArgsConstructor
public class TokenBean {
    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 用户賬號
     */
    private String account;

    /**
     * 用户類型
     */
    private String userType;
}

JWT工具類

在我們的util包下創建JwtUtil.java工具類👇

package com.maple.demo.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.maple.demo.config.bean.ErrorCode;
import com.maple.demo.config.bean.GlobalConfig;
import com.maple.demo.config.bean.TokenBean;
import com.maple.demo.config.exception.MapleCheckException;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Objects;

/**
 * Jwt常用操作
 *
 * @author 笑小楓
 * @date 2022/7/20
 */
public class JwtUtil {

    private static final String ACCOUNT = "account";
    private static final String USER_ID = "userId";
    private static final String USER_TYPE = "userType";

    /**
     * 校驗token是否正確
     *
     * @param token 密鑰
     * @return 是否正確
     */
    public static boolean verify(String token, String account) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(GlobalConfig.SECRET);
            JWTVerifier verifier = JWT.require(algorithm).withClaim(ACCOUNT, account).build();
            verifier.verify(token);
            return true;
        } catch (Exception exception) {
            return false;
        }
    }

    /**
     * 獲得token中的信息無需secret解密也能獲得
     *
     * @return token中包含的用户登錄帳號
     */
    public static String getAccount() {
        try {
            DecodedJWT jwt = getJwt();
            if (jwt == null) {
                return null;
            }
            return jwt.getClaim(ACCOUNT).asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 獲得token中的信息無需secret解密也能獲得
     *
     * @return token中包含的用户登錄帳號
     */
    public static String getAccount(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim(ACCOUNT).asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    public static Long getUserId() {
        try {
            DecodedJWT jwt = getJwt();
            if (jwt == null) {
                return null;
            }
            return jwt.getClaim(USER_ID).asLong();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 獲得token中的信息無需secret解密也能獲得
     *
     * @return token中包含的用户ID
     */
    public static Long getUserId(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim(USER_ID).asLong();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    public static TokenBean getTokenMsg() {
        TokenBean tokenBean = new TokenBean();
        try {
            DecodedJWT jwt = getJwt();
            if (jwt == null) {
                return tokenBean;
            }
            tokenBean.setUserId(jwt.getClaim(USER_ID).asLong());
            tokenBean.setAccount(jwt.getClaim(ACCOUNT).asString());
            return tokenBean;

        } catch (JWTDecodeException e) {
            return tokenBean;
        }
    }

    private static DecodedJWT getJwt() {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (Objects.isNull(servletRequestAttributes)) {
            throw new MapleCheckException(ErrorCode.PARAM_ERROR);
        }
        HttpServletRequest request = servletRequestAttributes.getRequest();

        String authorization = request.getHeader(GlobalConfig.TOKEN_NAME);
        if (authorization == null) {
            return null;
        }
        return JWT.decode(authorization);
    }

    /**
     * 校驗token是否有效
     *
     * @param token token信息
     * @return 返回結果
     */
    public static boolean verifyToken(String token) {
        try {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(GlobalConfig.SECRET)).build();
            DecodedJWT jwt = verifier.verify(token);
            jwt.getClaims();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 創建token
     *
     * @param tokenBean token保存的信息
     * @return token
     */
    public static String createToken(TokenBean tokenBean) {
        Algorithm algorithm = Algorithm.HMAC256(GlobalConfig.SECRET);
        return JWT.create()
                .withClaim(USER_ID, tokenBean.getUserId())
                .withClaim(ACCOUNT, tokenBean.getAccount())
                .withClaim(USER_TYPE, tokenBean.getUserType())
                .sign(algorithm);
    }
}

Filter登錄攔截器

牽扯到了三個異常code,我們可以在ErrorCode.java裏面補充一下,如果沒有引入自定義異常,可以手動throw new RuntimeException("笑小楓的異常信息")替換一下就行了。

NO_TOKEN("1001", "用户未登錄"),
TOKEN_EXPIRE("1002", "登陸超時,請重新登錄"),
TOKEN_EXCHANGE("1003", "賬號在其他地方登錄,賬號被踢出"),
USER_LOGIN_ERROR("2001", "用户名或密碼錯誤"),
USER_STATUS_ERROR("2002", "用户已被停用,請聯繫管理員"),

首先在MapleDemoApplication.java啓動項上添加註解@ServletComponentScan

SpringBootApplication 上使用@ServletComponentScan 註解後

Servlet可以直接通過@WebServlet註解自動註冊

Filter可以直接通過@WebFilter註解自動註冊

Listener可以直接通過@WebListener 註解自動註冊

創建一個存放攔截器的包filter,然後在包內創建我們的攔截器JwtFilter,代碼如下:👇

package com.maple.demo.filter;

import com.alibaba.fastjson.JSON;
import com.maple.demo.config.bean.ErrorCode;
import com.maple.demo.config.bean.GlobalConfig;
import com.maple.demo.util.JwtUtil;
import com.maple.demo.util.RedisUtil;
import com.maple.demo.util.ResultJson;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

/**
 * 判斷用户登錄token
 *
 * @author 笑小楓
 * @date 2022/07/20
 */
@WebFilter(filterName = "jwtFilter", urlPatterns = {"/*"})
@AllArgsConstructor
@Order(1)
public class JwtFilter implements Filter {

    private final List<String> excludedUrlList;

    @Override
    public void init(FilterConfig filterConfig) {
        excludedUrlList.addAll(Arrays.asList(
                "/sso/login",
                "/sso/logout",
                "/example/*",
                "/webjars/**",
                "/swagger/**",
                "/v2/api-docs",
                "/doc.html",
                "/swagger-ui.html",
                "/swagger-resources/**",
                "/swagger-resources"
        ));
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String url = ((HttpServletRequest) request).getRequestURI();
        boolean isMatch = false;
        for (String excludedUrl : excludedUrlList) {
            if (Pattern.matches(excludedUrl.replace("*", ".*"), url)) {
                isMatch = true;
                break;
            }
        }
        if (isMatch) {
            chain.doFilter(request, response);
        } else {
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            //處理跨域問題,跨域的請求首先會發一個options類型的請求
            if (httpServletRequest.getMethod().equals(HttpMethod.OPTIONS.name())) {
                chain.doFilter(request, response);
            }
            BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
            RedisUtil redisService = (RedisUtil) factory.getBean("redisUtil");
            String account;
            String authorization = httpServletRequest.getHeader(GlobalConfig.TOKEN_NAME);
            // 判斷token是否存在,不存在代表未登錄
            if (StringUtils.isEmpty(authorization)) {
                writeRsp(httpServletResponse, ErrorCode.NO_TOKEN);
                return;
            } else {
                account = JwtUtil.getAccount(authorization);
                String token = (String) redisService.get(GlobalConfig.getRedisUserKey(account));
                // 判斷token是否存在,不存在代表登陸超時
                if (StringUtils.isEmpty(token)) {
                    writeRsp(httpServletResponse, ErrorCode.TOKEN_EXPIRE);
                    return;
                } else {
                    // 判斷token是否相等,不相等代表在其他地方登錄
                    if (!token.equalsIgnoreCase(authorization)) {
                        writeRsp(httpServletResponse, ErrorCode.TOKEN_EXCHANGE);
                        return;
                    }
                }
            }
            // 保存redis,每次調用成功都刷新過期時間
            redisService.set(GlobalConfig.getRedisUserKey(account), authorization, GlobalConfig.EXPIRE_TIME);
            chain.doFilter(httpServletRequest, httpServletResponse);
        }
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }

    private void writeRsp(HttpServletResponse response, ErrorCode errorCode) {
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        response.setHeader("content-type", "application/json;charset=UTF-8");
        try {
            response.getWriter().println(JSON.toJSON(new ResultJson(errorCode.getCode(), errorCode.getMsg())));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

密碼加密與驗證

首先再自定義一個異常吧,創建一個通用點的MapleCommonException.java,後續都偷懶統一拋這個異常了

代碼如下:

package com.maple.demo.config.exception;

import com.maple.demo.config.bean.ErrorCode;

/**
 * 通用異常,偷懶就拋出此異常吧
 *
 * @author 笑小楓
 * @date 2022/07/20
 */
public class MapleCommonException extends MapleBaseException {

    public MapleCommonException(String code, String errorMsg) {
        super(code, errorMsg);
    }

    public MapleCommonException(ErrorCode code) {
        super(code);
    }

    public MapleCommonException(ErrorCode code, String errorMsg) {
        super(code, errorMsg);
    }
}

密碼加密就簡單的使用md5加鹽值吧,代碼如下👇

package com.maple.demo.util;

import com.maple.demo.config.bean.ErrorCode;
import com.maple.demo.config.exception.MapleCommonException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;


/**
 * MD5撒鹽加密 及MD5加密
 *
 * @author 笑小楓
 * @date 2022/7/20
 */
@Slf4j
public class Md5Util {

    private Md5Util() {

    }

    /**
     * 密碼加密處理
     *
     * @param password 密碼明文
     * @param salt     鹽
     * @return 加密後密文
     */
    public static String encrypt(String password, String salt) {
        if (StringUtils.isEmpty(password) || StringUtils.isEmpty(salt)) {
            log.error("密碼加密失敗原因: password and salt cannot be empty");
            throw new MapleCommonException(ErrorCode.PARAM_ERROR);
        }
        return DigestUtils.md5DigestAsHex((salt + password).getBytes());
    }

    /**
     * 校驗密碼
     *
     * @param target 待校驗密碼
     * @param source 原密碼
     * @param salt   加密原密碼的鹽
     */
    public static boolean verifyPassword(String target, String source, String salt) {
        if (StringUtils.isEmpty(target) || StringUtils.isEmpty(source) || StringUtils.isEmpty(salt)) {
            log.info("校驗密碼失敗,原因 target ={}, source ={}, salt ={}", target, source, salt);
            return false;
        }
        String targetEncryptPwd = encrypt(target, salt);
        return targetEncryptPwd.equals(source);
    }

    public static void main(String[] args) {
        log.info(encrypt("admin111", "123456"));
    }
}

通過main方法生成一個加密後的值吧,然後把鹽值和密碼都扔到數據庫裏面,後面我們就根據賬號(account)和密碼(password)進行登錄。

注意:創建用户的時候先隨機生成一個鹽(salt),後續根據鹽值再去生成密碼。

用户登錄接口

model和param

創建一個vo包吧,後續的model和query對象統一放在這裏了~

在vo包下創建一個query包,然後創建登錄請求對象LoginQuery.java

package com.maple.demo.vo.query;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @author 笑小楓
 * @date 2022/7/20
 */
@Data
@ApiModel(value = "用户登錄請求對象", description = "用户中心-用户登錄請求對象")
public class LoginQuery {
    @ApiModelProperty(value = "登錄賬號")
    private String account;

    @ApiModelProperty(value = "登錄密碼")
    private String password;
}

在vo包下創建一個model包,然後創建返回的用户信息對象UserModel.java

package com.maple.demo.vo.model;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;

/**
 * 用户中心-用户信息
 *
 * @author 笑小楓
 * @date 2022/7/20
 */
@Data
@Builder
@RequiredArgsConstructor
@AllArgsConstructor
@ApiModel(value = "用户視圖對象", description = "用户中心-用户信息")
public class UserModel {

    @ApiModelProperty(value = "用户ID")
    private Long id;

    @ApiModelProperty(value = "用户賬號")
    private String account;

    @ApiModelProperty(value = "用户姓名")
    private String userName;

    @ApiModelProperty(value = "用户暱稱")
    private String nickName;

    @ApiModelProperty(value = "用户類型")
    private String userType;

    @ApiModelProperty(value = "用户郵箱")
    private String email;

    @ApiModelProperty(value = "手機號碼")
    private String phone;

    @ApiModelProperty(value = "用户性別")
    private String sex;

    @ApiModelProperty(value = "頭像地址")
    private String avatar;

    @ApiModelProperty(value = "帳號狀態")
    private String status;

    @ApiModelProperty(value = "備註")
    private String remark;

    @ApiModelProperty(value = "用户驗證Token")
    private String token;
}

controller

package com.maple.demo.controller;

import com.maple.demo.service.IUserService;
import com.maple.demo.vo.model.UserModel;
import com.maple.demo.vo.query.LoginQuery;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;


/**
 * 系統登錄
 *
 * @author 笑小楓
 * @date 2022/7/20
 */
@Api(tags = "管理系統-系統登錄操作")
@RestController
@AllArgsConstructor
@RequestMapping(value = "/sso")
public class LoginController {

    private final IUserService userService;

    @ApiOperation(value = "用户登錄")
    @PostMapping("/login")
    public UserModel login(@RequestBody LoginQuery req) {
        return userService.login(req);
    }

    @ApiOperation(value = "用户退出登錄")
    @GetMapping("/logout")
    public void logout() {
        userService.logout();
    }
}

service

package com.maple.demo.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.maple.demo.entity.User;
import com.maple.demo.vo.model.UserModel;
import com.maple.demo.vo.query.LoginQuery;

/**
 * <p>
 * 用户中心-用户信息表 服務類
 * </p>
 *
 * @author 笑小楓
 * @since 2022-07-11
 */
public interface IUserService extends IService<User> {
    /**
     * 用户登錄
     *
     * @param req 用户信息
     * @return 用户登錄信息
     */
    UserModel login(LoginQuery req);

    /**
     * 退出系統,清除用户token
     */
    void logout();
}

serviceImpl

package com.maple.demo.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.maple.demo.config.bean.ErrorCode;
import com.maple.demo.config.bean.GlobalConfig;
import com.maple.demo.config.bean.TokenBean;
import com.maple.demo.config.exception.MapleCheckException;
import com.maple.demo.entity.User;
import com.maple.demo.mapper.UserMapper;
import com.maple.demo.service.IUserService;
import com.maple.demo.util.JwtUtil;
import com.maple.demo.util.Md5Util;
import com.maple.demo.util.RedisUtil;
import com.maple.demo.vo.model.UserModel;
import com.maple.demo.vo.query.LoginQuery;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.util.Objects;


/**
 * <p>
 * 用户中心-用户信息表 服務實現類
 * </p>
 *
 * @author Maple
 * @since 2022-07-11
 */
@Service
@AllArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    private final UserMapper userMapper;

    private final RedisUtil redisUtil;

    @Override
    public UserModel login(LoginQuery req) {
        User user = userMapper.selectOne(Wrappers.lambdaQuery(User.class)
                .eq(User::getAccount, req.getAccount())
                .last("LIMIT 1"));
        if (Objects.isNull(user)) {
            throw new MapleCheckException(ErrorCode.USER_LOGIN_ERROR);
        }
        if ("1".equals(user.getStatus())) {
            throw new MapleCheckException(ErrorCode.USER_STATUS_ERROR);
        }
        if (!Md5Util.verifyPassword(req.getPassword(), user.getPassword(), user.getSalt())) {
            throw new MapleCheckException(ErrorCode.USER_LOGIN_ERROR);
        }

        TokenBean tokenBean = TokenBean.builder()
                .userId(user.getId())
                .userType(user.getUserType())
                .account(user.getUserName())
                .build();

        UserModel userModel = new UserModel();
        BeanUtils.copyProperties(user, userModel);
        String token;
        try {
            token = JwtUtil.createToken(tokenBean);
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new MapleCheckException(ErrorCode.COMMON_ERROR);
        }
        userModel.setToken(token);
        redisUtil.set(GlobalConfig.getRedisUserKey(user.getAccount()), token);
        return userModel;
    }

    @Override
    public void logout() {
        redisUtil.remove(GlobalConfig.getRedisUserKey(JwtUtil.getAccount()));
    }
}

使用JWT的用户信息

直接使用JwtUtil.class工具類裏的方法,即可拿到對應的數據

JwtUtil.getUserId();
JwtUtil.getAccount();

功能測試

測試之前需要在數據中添加一條數據

INSERT INTO `maple`.`usc_user`( `account`, `user_name`, `nick_name`, `user_type`, `email`, `phone`, `sex`, `avatar`, `salt`, `password`, `status`, `create_id`, `create_name`, `create_time`, `update_id`, `update_name`, `update_time`, `delete_flag`, `remark`) VALUES ('admin', 'admin', '笑小楓', '00', '1150640979@qq.com', '18300000001', '0', '', '123456', 'e9c764f9b51772f00af80a54d38a692e', '0', 1, '笑小楓', '2022-07-11 13:48:44', 1, '笑小楓', '2022-07-11 13:48:44', 0, '管理員');

直接在LoginController.java裏面添加getUserId方法進行測試,詳細代碼如下:

package com.maple.demo.controller;

import com.maple.demo.service.IUserService;
import com.maple.demo.util.JwtUtil;
import com.maple.demo.vo.model.UserModel;
import com.maple.demo.vo.query.LoginQuery;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;


/**
 * 系統登錄
 *
 * @author 笑小楓
 * @date 2022/7/20
 */
@Api(tags = "管理系統-系統登錄操作")
@RestController
@AllArgsConstructor
@RequestMapping(value = "/sso")
public class LoginController {

    private final IUserService userService;

    @ApiOperation(value = "用户登錄")
    @PostMapping("/login")
    public UserModel login(@RequestBody LoginQuery req) {
        return userService.login(req);
    }

    @ApiOperation(value = "用户退出登錄")
    @GetMapping("/logout")
    public void logout() {
        userService.logout();
    }

    @ApiOperation(value = "獲取登錄用户信息")
    @GetMapping("/getUserId")
    public String getUserId() {
        return "當前登錄用户的ID為" + JwtUtil.getUserId();
    }
}

在未登錄狀態請求接口,返回信息如下:

image-20220721100745892

調用登錄接口,進行用户登錄

image-20230311215349100

登錄後,拿到token,在請求頭設置Authorization參數

image-20220721105652349

添加完之後,記得要把tab頁關閉,再打開,然後header參數才會生效,在請求頭部可以看到

再次調用,返回信息如下:

image-20220721105838920

可以看到,到此我們的登錄攔截功能就已經完全實現了。

小結

好啦,本文就到這裏了,我們簡單的總結一下,主要介紹了以下內容👇👇

  • 介紹了什麼是JWT
  • 用户登錄攔截
  • 用户登錄實現

關於笑小楓💕

本章到這裏結束了,喜歡的朋友關注一下我呦😘😘,大夥的支持,就是我堅持寫下去的動力。
老規矩,懂了就點贊收藏;不懂就問,日常在線,我會就會回覆哈~🤪
微信公眾號:笑小楓
笑小楓個人博客:https://www.xiaoxiaofeng.com
CSDN:https://zhangfz.blog.csdn.net
本文源碼:https://github.com/hack-feng/maple-demo
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.