本文介绍: 1、前端调用登录接口,往接口里传入账号密码2、根据账号判断是否有这个用户,如果有则继续判断密码是否正确3、验证成功后,则是根据账号登录时间生成token(用JWT)4、将token存入Redis当中,用于token过期策略5、将token用户信息返回前端6、此后调用后端任何接口都会先判断发来请求token是否存在、有效(拦截器实现)7、然后继续接下来的正常调用

登录流程: 

        1、前端调用登录接口,往接口里传入账号,密码

        2、根据账号判断是否有这个用户,如果有则继续判断密码是否正确

        3、验证成功后,则是根据账号,登录时间生成token(用JWT)

        4、将token存入Redis当中,用于token过期策略

        5、将token和用户信息返回前端

        6、此后调用后端任何接口都会先判断发来请求token是否存在、有效(拦截器实现)

        7、然后继续接下来的正常调用

 具体思路:

        1、再登录接口中实现账号、密码的验证和token创建

        2、实现一个拦截器,拦截除登录接口外的其他所有接口

        3、再拦截器中从请求头中取出token对其进行正确性的验证

        4、然后redis<account,token&gt;里取出属于这个账号的token,如果取的出来则说明这个用户登录状态没有过期然后取出来的token进行对比,如果不一样则说明同一账号再其他地方进行了登录,则被挤出登录状态

        5、再这整个判断过程中所产生的异常(没有登录状态,不存在这个用户….)都是由全局异常处理器进行捕获然后返回前端

 pom文件要引的依赖

      <!--版本--&gt;  
    <properties&gt;
        <java.version&gt;1.8</java.version&gt;
        <lombok.version&gt;1.18.12</lombok.version>
        <hutool.version>5.8.8</hutool.version>
        <mybatis-plus.version>3.5.2</mybatis-plus.version>
        <JWT.version>6.0</JWT.version>
        </properties>



        <!--JWT-->
        <dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>nimbus-jose-jwt</artifactId>
            <version>${JWT.version}</version>
        </dependency>

        <dependency>
            <groupId>com.qcby</groupId>
            <artifactId>qcby-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>
        
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>

对于token我使用了JWT,有一个token的工具用于token的创建和 验证。(这个类里使用的redisUtil类大家可以网上随便找一个redis工具类,绑定自己reids)

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import com.qcby.framework.common.exception.ServiceException;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.text.ParseException;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Component
public class TokenUtil {
    @Resource
    RedisUtil redisUtil;

    /**
     * 创建秘钥
     */
    private static final byte[] SECRET = "qngChengBoYa-realtimeWuIngWangJiaQiZhangYv".getBytes();

    /**
     * 生成token
     * @param account
     * @return {@link String}
     */
    public  String buildToken(String account) {

        try {
            /**
             * 1.创建一个32-byte的密匙
             */
            MACSigner macSigner = new MACSigner(SECRET);
            /**
             * 2. 建立payload 载体
             */
            JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
                    .subject("login")
                    .claim("ACCOUNT",account)
                    .issueTime(new Date())
                    .build();

            /**
             * 3. 建立签名
             */
            SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claimsSet);
            signedJWT.sign(macSigner);

            /**
             * 4. 生成token
             */
            String token = signedJWT.serialize();
            redisUtil.setEx(account,token,10,TimeUnit.MINUTES);
            return token;
        } catch (KeyLengthException e) {
            e.printStackTrace();
        } catch (JOSEException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 校验token
     * @param token
     * @return
     */
    public  boolean verifyToken(String token) {

        try {
            SignedJWT jwt = SignedJWT.parse(token);
            JWSVerifier verifier = new MACVerifier(SECRET);

            /**
             * 校验是否有效
             */
            if (!jwt.verify(verifier)) {
                return false;
            }
            /**
             * 获取载体中的数据
             */
            String account = (String) jwt.getJWTClaimsSet().getClaim("ACCOUNT");
            //是否有
            if (Objects.isNull(account)){

                return false;
            }
            /**
             * 判断redis是否有account为key的值,如果有
             * 判断token是否redis里存的是是否一样,
             * 如果不一样说明已经有其他账号登录了,则回到登录页面
             * 如果一样,则给token续期
             */
            if (redisUtil.hasKey(account)){
                String s = redisUtil.get(account);
                if (s.equals(token)){
                    redisUtil.expire(account,10,TimeUnit.MINUTES);
                    return true;
                }
                throw new ServiceException("422","有其他设备登录");
            }
        } catch (ParseException e) {
            e.printStackTrace();
        } catch (JOSEException e) {
            e.printStackTrace();
        }
        return false;
    }



}

 登录拦截器实现,要先创建一个类并实现HandlerInterceptor这个接口,然后再创建一个拦截器配置类令其实现WebmvcConfigurer这个接口,重新addInterceptors方法,再这个方法中将之前实现的登录拦截器注册进去,并配置这个拦截器的拦截路径,拦截优先级等等。

/**
 * 请求拦截器
 * @author MI
 * @date 2023/10/03
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {
    private static Logger log = Logger.getLogger(LoginInterceptor.class);
    /***
     * 在请求处理之前进行调用(Controller方法调用之前)
     @param request
     @param response
     @param handler
     @return boolean
     @throws Exception
     */
    @Resource
    TokenUtil tokenUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        /**
         * 从请求头中取出token,并判断其是否存在和合法
         */
            String token = request.getHeader("token");
        if (token != null &amp;&amp; tokenUtil.verifyToken(token)) {
                return true;
            }else {
                throw new ServiceException("100","还未登录");
            }
    }

    /***
     * 请求处理之后进行调用,但是在视图渲染之前(Controller方法调用之后)
     @param request
     @param response
     @param handler
     @param modelAndView
     @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }

    /***
     * 整个请求结束之后被调用,也就是在DispatchServlet渲染对应视图之后执行(主要用于进行资源清理工作)
     @param request
     @param response
     @param handler
     @param ex
     @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }
}



import javax.annotation.Resource;
@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
    @Resource
    LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        /**
         * 登录拦截器
         * */
      registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/login").
              order(1);

    }
}

        全局异常处理器,它能捕获到全部的异常(前提是要把异常抛出),所有异常都会在controller层反应出来,因为执行方法的原头在controller。我之前就是用trycatch处理,然后一直一直捕获不到,然后网上说正常的实现的全局异常器只能捕获controller层的异常,所以拦截器里的异常捕获不到,这句话对也不对。拦截器里异常确实捕获不到,但只要咱把它抛出去就能再controller层显现了。

具体实现就是我们要加@ControllerAdvice注解, @ExceptionHandler根据这个注解具体绑定处理哪个异常。

/**
 * 全局异常处理器
 * @author MI
 * @date 2023/10/02
 */
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler{
    /**
     * 自定义异常拦截器
     * @param req
     * @param e
     * @return {@link Result}
     */
    @ResponseBody
    @ExceptionHandler(value =ServiceException.class)
    public Result exceptionHandler(HttpServletRequest req, ServiceException e){
        log.info("发送{}异常",e.getMessage());
        return Result.getBusinessException(e.getLocalizedMessage(),e.getCode());
    }


    @ResponseBody
    @ExceptionHandler(value =Exception.class)
    public Result exceptionHandler(HttpServletRequest req, Exception e){
        log.info("发送{}异常",e.getMessage());
        return Result.getBusinessException(e.getLocalizedMessage());
    }
}

Server层实现:

@Service
@Slf4j
public class LoginServiceImpl implements ILoginService {

    @Resource
    UserMapper userMapper;
    @Resource
    TokenUtil tokenUtil;
    @Resource
    UserRoleMapper userRoleMapper;

    /**
     * 登录实现
     * @param loginDto
     * @return {@link LoginVo}
     */
    @Override
    public LoginVo login(LoginDto loginDto) {
        UserPo userPo = userMapper.selectOne(new LambdaQueryWrapper<UserPo>().eq(UserPo::getAccount, loginDto.getAccount()));
        if (userPo!=null){
                if (userPo.getPassword().equals(loginDto.getPassword())){
                    /**
                     * 构建token
                     */
                    String token = tokenUtil.buildToken(userPo.getAccount());
                    LoginVo loginVo = new LoginVo();
                    loginVo.setToken(token);
                    loginVo.setRoleId(userRoleMapper.selectOne(
                            new LambdaQueryWrapper<UserRolePo>().eq(UserRolePo::getUserId,userPo.getUserId())).getRoleId());
                    loginVo.setAccount(userPo.getAccount());
                    return loginVo;
                }else{
                    throw new ServiceException("422","密码错误");
                }
        }else {
            throw new ServiceException("422", "用户不存在");
        }
    }
}

后面的Controller、Mapper、实体层大家要根据自己需求字段来进行具体实现,我就不贴出来了。

 具体用法,像这个异常咱直接抛出来就行,全局异常处理类都能捕获,就不会继续往下执行了,它会把报错信息返回前端

如图所示

                                

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注