一种自定义注解的用户鉴权方式

一种自定义注解的用户鉴权方式

相关技术

  1. SpringBoot
  2. JWT

用户鉴权

在一些小项目中,对于不同的REST接口,需要限定用户身份,即哪类用户可访问,哪类不可访问。

常规做法

在一个接口接收一个请求后,自行解析其用户身份

@Resource
private HttpServletRequest request;

@GetMapping("/hello")
public String hello() {
    String token = request.getHeader("token");
    String userIdentify = JwtUtils.verify(token).getClaim(USER_IDENTIFY).asString();
    if (!userIdentify.equals("STUDENT")){
        return "无权操作";
    }
    
    return "hello";
}

这样可以实现功能需求,但是存在一个问题,每有一个需要鉴权的接口,就需要写一次鉴权代码。

当然,也可以封装成一个公用方法,大概形式如下:

public boolean auth(List<String> permissionList, HttpServletRequest request) {
    String token = request.getHeader("token");
    String userIdentify = JwtUtils.verify(token).getClaim(USER_IDENTIFY).asString();
    return permissionList.contains(userIdentify);
}

但每次调用时,仍需要写大量重复代码

public String test2() {
    List<String> authList = Arrays.asList("STUDENT");
    if (!auth(authList)) {
        return "无权操作";
    }
    
    return "hello";
}

那么,这个重复代码,能不能避免呢。答案很显然是可以的,由于每次都在数据处理之前进行鉴权,因此可以考虑使用拦截器。

自定义注解的用户鉴权方式

使用拦截器时,拦截器只有一套,但接口有多个,这时就需要一个参数的传递,使拦截器知道需要放行哪些身份的用户。

这时,需要先定义一个注解,需要鉴权时,只需要在接口上加入该注解,并设定预定值,即可告知拦截器需要放行的用户。

自定义注解
/**
 * @author MingYi
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auth {
    String[] identify() default "STUDENT";
}
拦截器
/**
 * @author MingYi
 */
@Component
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Auth auth = handlerMethod.getMethodAnnotation(Auth.class);

            if (auth == null) {
                return true;
            }

            String[] identify = auth.identify();
            String token = request.getHeader("token");
			// 这里仅做比对,如有权限等级等需求,可自行修改校验逻辑
            String userIdentify = JwtUtils.verify(token).getClaim(USER_IDENTIFY).asString();
            for (String i : identify) {
                if (userIdentify.equals(i)) {
                    return true;
                }
            }

            //返回错误信息
            String json = new ObjectMapper().writeValueAsString("无权操作"));
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().println(json);
            return false;
        }

        return HandlerInterceptor.super.preHandle(request, response, handler);
    }
}

在SpringBoot项目中,拦截器需要进行配置才可生效

配置
/**
 * @author MingYi
 */
@Configuration
public class AuthConfig implements WebMvcConfigurer {
    @Resource
    private AuthInterceptor authInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/user/**"); //放行user路径下的接口,否则可能无法进行注册及登录
    }
}
使用

此时,我们再写一个接口,只需要在接口上方加上一个注解,即可完成权限校验操作。

e.g.

@Resource
private HttpServletRequest request;

@Auth(identify = {"ADMIN"})
@GetMapping("/hello")
public String hello() {
    return "hello";
}

此时,即完成了使用自定义注解的方式进行用户鉴权的操作。

附录

JwtUtils

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

import java.util.Calendar;
import java.util.Map;


/**
 * 本类中包括JWT相关操作,如生成、解析及鉴权等。
 *
 * @author MingYi
 */
@Slf4j
@Component
@PropertySource("classpath:prop.properties")
public class JwtUtils {

    /**
     * 生成JWT时所用到的私钥。
     * <p>请在resource/prop.properties中配置"jwt.signature",否则可能将无法使用。</p>
     */
    private static String SIGNATURE;
    
    public static final int ONE_DAY = 60 * 60 * 24;

    @Value("${jwt.signature}")
    private void setSignature(String signature) {
        JwtUtils.SIGNATURE = signature;
    }

    /**
     * 生成token,放在payload中的数据可以通过map方式传输,Utils的通用性会比较高,调用的时候需要多写一点
     * SignatureVerificationException 无效签名Exception
     * TokenExpiredException token过期Exception
     * AlgorithmMismatchException token算法不一致Exception
     *
     * @param map 参数列表
     * @return token
     */
    public static String getToken(Map<String, String> map) {
        JWTCreator.Builder builder = JWT.create();
        if (map != null && map.size() != 0){
            map.forEach((k, v) -> {
                builder.withClaim(k, v);
            });
        }

        Calendar instance = Calendar.getInstance();
        //设定1天的token有效期
        instance.add(Calendar.SECOND, ONE_DAY);
        String token = builder.withExpiresAt(instance.getTime())
                .sign(Algorithm.HMAC256(SIGNATURE));
        log.info("Generate token successfully.");
        return token;
    }

    /**
     * 验证token的合法性
     * 这里面会抛出各种异常,需要外部,如拦截器等进行捕获、处理
     *
     * @param token token
     * @return 解析过的token信息
     */
    public static DecodedJWT verify(String token) {
        return JWT.require(Algorithm.HMAC256(SIGNATURE)).build().verify(token);
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值