你的项目里不会还没用单点登录吧

一. 单点登录

如图,下图为运用token来实现的登录系统,客户端第一次请求使用密码与账户来服务端换取token,拿到token后存在浏览器缓存中,然后每次请求新的资源携带上当前的token来验证是否为已登录的用户,JWT在上一篇博客中已经介绍很详细了,家人们不懂的话可以去看看JWT博客
在这里插入图片描述
有人问我为什么要单点登录,不会吧不会吧,真就敢数据罗奔啊,当你用户数据被篡改了,你学生数据库出现一个迪迦奥特曼你就开心了~
在这里插入图片描述

二. 废话不多说,上代码

首先通过Springboot的AOP切面来实现两个注解,分别代表接口需要Token验证,或者不要Token验证
在这里插入图片描述
AOP不会还有人不知道吧,面试官说你自己走还是我帮你
面向切面编程,举一个简单的例子,你服务是不是需要记录日志,或者就是验证登录,你不可能每个controller的接口都整个验证程序吧,每个controller都传一个token参数,这谁看到代码不恶心死啊~

在这里插入图片描述

2.1 AOP代码

PassToken注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
    boolean required() default true;
}

UserLoginToken注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
    boolean required() default true;
}

重头戏在AuthJWT 的handler类

package com.fehead.diseaseCare.aop;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.fehead.diseaseCare.controller.BaseController;
import com.fehead.diseaseCare.error.BusinessException;
import com.fehead.diseaseCare.error.EmBusinessError;
import com.fehead.diseaseCare.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

@Component
@Aspect
@Slf4j
public class AuthJWT {

    //定义切面,所有的controller层都会监控 家人们切面表达式可得多学学了
    @Pointcut("execution(* com.fehead.diseaseCare.controller..*.*(..))")
    public void doHander() { }


    @Around("doHander()")
    public Object exception(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            //进入controller层前
            beforePoint(joinPoint);
            //放行
            Object result = joinPoint.proceed();
            //返回数据前
            afterPoint(joinPoint, result);
            return result;
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            throw  new BusinessException(EmBusinessError.COMMON_ERROR,e.getMessage());
        }
    }

    private Boolean beforePoint(ProceedingJoinPoint joinPoint) throws Exception {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();

        // 从 http 请求头中取出 token
        String token = request.getHeader("token");

        //得到要进入的是哪个controller方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        //检查是否有passtoken注释,有则跳过认证,所以在controller层加了@Passtoken注解,这里我就不校验
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                return true;
            }
        }

        //加了@UserLoginToken注解的我要进行校验
        if (method.isAnnotationPresent(UserLoginToken.class)) {
            UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
            if (userLoginToken.required()) {
                // 执行认证
                if (StringUtils.isEmpty(token)) {
                    throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"token为空" );
                }
                JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("xxxxx4234{}d]]fxxxxdsadasdasdsad")).build();
                try {
                    //验证token
                    jwtVerifier.verify(token);
                } catch (JWTVerificationException e) {
                    throw new BusinessException(EmBusinessError.USER_AUTH_ERROR);
                }
                return true;
            }
        }
        return true;
    }


    private void afterPoint(ProceedingJoinPoint joinPoint, Object result) throws Exception {

    }

}

理一理逻辑(AOP注解版需要自己掌握)

  1. 首先@Component加入Sping容器 @Aspect变成一个切面类
  2. @Pointcut(“execution(* com.fehead.diseaseCare.controller….(…))”) 通过这个方法定义切入点来整合切面表达式,我们这里是设置了全部controller
  3. @Around(“doHander()”) 环绕通知,通过 joinPoint 执行 joinPoint.proceed(); 方法时控制在这个方法前面或者后面或者异常时做什么,这块就把你想要在其他方法执行前,可以先执行你自定义的方法
  4. 拿到方法上的自定义注解 method.isAnnotationPresent(UserLoginToken.class) 去验证token
  5. 从header获取token,然后 jwtVerifier.verify(token); 验证token

大致流程就是这样,简化版的小单点登录,但是这里就又有一个问题了,那我们之前不是说可以在token里设置一些不重要的信息嘛,例如userId这种,那我们在后端是不是可以不用再传一个userId了,这里有几种方法

  • 1. 使用threadLocal存储从Token中获取的UserId,通过静态方法从threadLocal中获取UserId,当方法结束后,再手动清除threadlocal就行,这种方法非常简便,但是需要及时清空,不然会造成内存泄漏,因为TreadLocal类似于一个线程Map,但是key是弱引用,一旦GCkey就会清空,但Value是强引用不会清楚,就~~
  • 方法二,手动去再次获取Token,然后封装成静态方法去解析UserId,这种方法是麻烦了点,就是多做一次获取验证token
package com.fehead.diseaseCare.utility;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fehead.diseaseCare.entities.User;
import com.fehead.diseaseCare.entities.model.UserIdRoleInfo;
import com.fehead.diseaseCare.error.BusinessException;
import com.fehead.diseaseCare.error.EmBusinessError;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

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

public class JwtUtil {

    public static String makeToken(User user) {
        //withAudience()存入需要保存在token的信息,这里我把用户ID存入token中
        String token="";
        Date date = new Date(System.currentTimeMillis()+1000*60*2*24 *30);
        token= JWT.create()
                .withClaim("userId",user.getId())
                .withClaim("role",user.getRole())
                .withExpiresAt(date)
                .sign(Algorithm.HMAC256("xxxxx4234{}d]]fxxxxdsadasdasdsad"));
        return token;
    }

    public static UserIdRoleInfo getUserIdByToken(){
        ServletRequestAttributes requestAttributes = ServletRequestAttributes.class.
                cast(RequestContextHolder.getRequestAttributes());
        HttpServletRequest req = requestAttributes.getRequest();
        String token = req.getHeader("token");
        UserIdRoleInfo userIdRoleInfo=new UserIdRoleInfo();
        JWTVerifier build = JWT.require(Algorithm.HMAC256("xxxxx4234{}d]]fxxxxdsadasdasdsad")).build();
        DecodedJWT jwt=null;
        try {
            jwt = build.verify(token);
        }catch (Exception e){
            throw new BusinessException(EmBusinessError.USER_AUTH_ERROR);
        }
        int  userId = jwt.getClaim("userId").asInt();
        int  role =jwt.getClaim("role").asInt();
        userIdRoleInfo.setUserId(userId);
        userIdRoleInfo.setRole(role);
        return userIdRoleInfo;
    }
}

  • withClaim(“userId”,user.getId()) 设置token的明文数据
  • withExpiresAt(date) 设置过期时间
  • sign(Algorithm.HMAC256(“xxxxx4234{}d]]fxxxxdsadasdasdsad”)) 设置密钥加解密,密钥就是中间的字符串,也可以使用用户密码作为密钥
  • 使用JwtUtil封装静态求情

在这里插入图片描述
至此,整个项目大概结构就介绍完了,家人们不会都没长脑袋吧

我拿这篇博客我不点赞,我收藏,唉就是玩儿~
在这里插入图片描述

制作不易,转载请标注~

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值