学妹教我写代码【四】--写一个简单的鉴权系统

我们平时开发的程序一般都会涉及到用户模块,有时候的业务必须要用户登录才能实现,涉及到登录那么就不可避免的要涉及到鉴权,那么一个简单的鉴权应该怎么做?下面来介绍一下。

主要思路

在用户登录的时候返回一个token给前端,往后前端的每次请求都用请求拦截器拦截,并且在请求头添加一个 Authorization ,值就是之前后端返回的token,后端对每次请求进行拦截并且鉴权。

实现

前端

下面是axios的实例化和对应的请求拦截器。

const service = axios.create({
  baseURL: "http://localhost:8080", // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  headers: {
    "content-type": "application/x-www-form-urlencoded"
  },
  withCredentials: true, // 允许携带凭证
  timeout: 5000 // request timeout
})

// request interceptor
service.interceptors.request.use(
  config => {
    if (getToken()) {
      config.headers['Authorization'] = getToken()
    }
    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

对token的操作我们可以封装成函数

const TokenKey = 'token'

export function getToken() {
  return localStorage.getItem(TokenKey);
}

export function setToken(token) {
  return localStorage.setItem(TokenKey, token);
}

export function removeToken() {
  return localStorage.removeItem(TokenKey);
}

上面就是前端的实现啦,简单点来说前端只负责接收token并且储存,在每次发请求的时候带上就行

后端

后端鉴权的机制比较多,我们首先来想一下,token如何来?里面的内容是什么?答案是用jwt等安全框架造出来,里面的内容可以我们自己来定(比如储存好用户的id,过期时间等)
ok,有了上面的基础,我们可以猜到肯定需要一个工具类来完成加密信息产生token,解密token,验证token,刷新token等。
下面是工具类代码(需要加密什么信息可以自己添加,算法也比较基础,可以更换加密机制)


package com.sky.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;

import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JwtUtil {
    private static final int EXPIRATION_HOURS = 24;
    private static final String SECRET = "demoCat";

    /**
     * 生成token
     *
     * @param userId         用户id
     * @param expiresInHours 有效时间
     * @return JWT token
     */
    public static String createToken(Long userId, int expiresInHours) {
        Calendar nowTime = Calendar.getInstance();
        nowTime.add(Calendar.HOUR, expiresInHours);
        Date expiresDate = nowTime.getTime();

        Map<String, Object> map = new HashMap<>();
        map.put("typ", "JWT");
        map.put("alg", "HS256");

        return JWT.create()
                .withHeader(map)
                .withClaim("id", userId)
                .withExpiresAt(expiresDate)
                .withIssuedAt(new Date())
                .sign(Algorithm.HMAC256(SECRET));
    }

    /**
     * 验证token是否过期
     *
     * @param token JWT token
     * @return true 如果 token 有效,否则 false
     */
    public static Boolean verifyToken(String token) {
        if (token == null || token.isEmpty()) {
            return false; // Token 为空
        }

        try {
            Algorithm algorithm = Algorithm.HMAC256(SECRET);
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT jwt = verifier.verify(token);
            Date expirationDate = jwt.getExpiresAt();
            Date currentDate = new Date();
            return !expirationDate.before(currentDate);
        } catch (JWTVerificationException e) {
            return false;
        }
    }

    /**
     * 解析token
     *
     * @param token JWT token
     * @return 包含 JWT 令牌声明的映射
     * @throws Exception 如果解析失败
     */
    public static Map<String, Claim> parseToken(String token) throws Exception {
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
        DecodedJWT jwt = verifier.verify(token);
        return jwt.getClaims();
    }

    /**
     * 刷新token
     *
     * @param token JWT token
     * @return 刷新后的 JWT token
     */
    public static String refreshToken(String token) {
        try {
            DecodedJWT decodedJWT = JWT.decode(token);
            Long userId = decodedJWT.getClaim("id").asLong();
            return createToken(userId, EXPIRATION_HOURS);
        } catch (JWTVerificationException e) {
            // Token 解码失败或签名验证失败,处理异常
            return null;
        }
    }
}

ok,有了这个工具,下面就要来编写我们的业务了。

首先我们每次都要拦截,那么肯定需要一个拦截器了,我们先来写一个拦截器并且注册到系统中

package com.sky.interceptor;

import com.auth0.jwt.interfaces.Claim;
import com.sky.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

/**
 * jwt令牌校验的拦截器
 * 相当于系统的锁
 */
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {


    /**
     * 校验jwt
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //TODO 写一篇博客如何做一个权限系统shiro(权限五表,包括vip也可以直接用的那种)
        //判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }
        if ("/user/login".equals(request.getRequestURI())
                ||"/upload".equals(request.getRequestURI())) {
            return true;
        }

        //1、从请求头中获取令牌
        String token = request.getHeader("Authorization");

        Boolean loggedIn = JwtUtil.verifyToken(token);

        //2、校验令牌
        try {
            //TODO 思考如何为系统搭建一个日志系统,如何打日志为佳,
            log.info("jwt校验:{}", token);
            if (token != null && loggedIn) {
                Map<String, Claim> stringClaimMap = JwtUtil.parseToken(token);
                Long userId = stringClaimMap.get("id").asLong();
                request.setAttribute("current_user", userId.intValue());
                System.out.println("当前用户id:"+userId);
                return true;
            }else {
                throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Unauthorized access");
            }

            //3、通过,放行
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(403);
            return false;
        }
    }




}

然后注册拦截器

@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {

    @Autowired
    private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;

    /**
     * 注册自定义拦截器
     *
     * @param registry
     */
    protected void addInterceptors(InterceptorRegistry registry) {
        log.info("开始注册自定义拦截器...");
        registry.addInterceptor(jwtTokenAdminInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/login");
    }
}

到目前为止,我们的鉴权就初步打造完成啦,还有最后一步就是用户登录的时候发令牌给用户。这个我们结合自己的业务就行啦。

上面就是我们实现的简单的鉴权系统啦,为什么简单?因为我们还可以在这个基础上加入权限(比如vip,管理员等),加密算法也可以更完善,还有token也没有实现刷新机制。
当然,这些都是随业务的变化而变化的。我们知道了基础,要用的时候再加就行啦!
今天的鉴权就到这里,下次见!

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谢林365

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值