首页 > 编程语言 > 详细

SpringBoot利用自定义注解实现通用的JWT校验方案

时间:2021-04-01 18:15:02      阅读:42      评论:0      收藏:0      [点我收藏+]

利用注解开发一个通用的JWT前置校验功能

设计的预期:
系统中并不是所有的应用都需要JWT前置校验,这就需要额外设计一个注解Annotation来标识这个方法需要JWT前置校验.例如:

@GetMapping("/dosth")
//加入自定义注解JwtToken,该Controller方法在运行前就需要进行JWT校验
//JWT校验通过执行Controller方法
//JWT校验未通过则直接返回校验失败
@JwtToken
public ResponseObject doSth(){
    return new ResponseObject("...");
}

开发步骤

pom.xml引入JJWT

<!--JJWT-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.2</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>

application.yml增加appkey秘钥

#JWT秘钥与Auth-Service保持一致
app:
  secretKey: 1234567890-1234567890-1234567890

创建自定义注解@JwtToken

//这个注解只能用在方法上

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME) //运行时注解生效
public @interface JwtToken {
    //当required=true说明请求必须包含token,未包含则报错
    //当required=false说明token不是必须的,不会中断请求的传递,交由后面的业务代码处理
    boolean required() default true;
} 

在ArticleController增加doSth测试方法

  @JwtToken //dosth进行jwt校验
  @GetMapping("/dosth")
  public ResponseObject dosth() {
    return new ResponseObject("业务处理成功");
  }

创建TokenInterceptor实现代码拦截JWTToken业务验证逻辑

/**
 * 执行目标URI方法前,对存放在请求头中的Jwt进行校验,校验通过执行目标方法,校验失败则提示错误
 */
public class TokenInteceptor implements HandlerInterceptor {
    @Value("${app.secretKey}")
    private String appKey = null;

    /**
     * 在目标方法执行前先执行preHandle进行前置处理
     * @param request 原生请求对象
     * @param response 原生响应对象
     * @param handler 处理器对象
     * @return true-请求向后送达到Controller, false-中断请求立即产生响应
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("TokenInterceptor.preHandle()");
        // 如果不是映射到方法直接通过
        if(!(handler instanceof HandlerMethod)){
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        //获取目标方法的Method对象
        Method method = handlerMethod.getMethod();

        response.setContentType("text/json;charset=utf-8");

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

        //判断目标方法是否包含JwtToken注解
        if(method.isAnnotationPresent(JwtToken.class)){
            String token = request.getHeader("token");
            //判断请求头是否包含token属性,在拥有@JwtToken的方法处理时,未包含token则抛出安全异常
            if(token == null){
                JwtToken jwtToken = method.getAnnotation(JwtToken.class);
                if(jwtToken.required() == true) {
                    response.setStatus(401);//未认证
                    ResponseObject<Object> responseObject = new ResponseObject<>("SecurityException", "Token不存在,请检查请求头是否包含Token");
                    String json = objectMapper.writeValueAsString(responseObject);
                    response.getWriter().println(json);
                    return false;
                }

            }else{//token存在时验证JWT的有效性
                String base64Key = new BASE64Encoder().encode(appKey.getBytes());
                SecretKey key = Keys.hmacShaKeyFor(base64Key.getBytes());
                try {
                    Jws<Claims> claimsJws = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
                    String userJson = claimsJws.getBody().getSubject();
                    System.out.println(userJson);
                    User user = objectMapper.readValue(userJson, User.class);
                    request.setAttribute("$user",user); //将当前登录用户对象保存到请求属性
                    return true;
                }catch (JsonProcessingException e){
                    e.printStackTrace();
                    response.setStatus(500);
                    ResponseObject<Object> responseObject = new ResponseObject<>(e.getClass().getSimpleName(), e.getMessage());
                    String json = objectMapper.writeValueAsString(responseObject);
                    response.getWriter().println(json);
                    return false;
                }catch (JwtException e){//Jwt校验失败是抛出异常
                    e.printStackTrace();
                    response.setStatus(401);
                    ResponseObject<Object> responseObject = new ResponseObject<>(e.getClass().getSimpleName(), e.getMessage());
                    String json = objectMapper.writeValueAsString(responseObject);
                    response.getWriter().println(json);
                    return false;
                }

            }
        }
        return true;
    }
}

启动应用,

http://localhost:8100/dosth

将Token中包含的用户数据与业务逻辑绑定,如果Token验证成功,TokenInterceptor会把用户信息转为User对象放入当前请求$user属性中

之后在list方法中,使用@RequestArrtibute()进行提取即可.

/**
 * 如果是VIP会员,可以查看所有普通文章与精选文章
 * 如果是普通会员,只能查看所有普通文章
 *
 * @return
 */
@GetMapping("/list")
@JwtToken(required = false)
public ResponseObject list(@RequestAttribute(value = "$user", required = false) User user) {
    //request.getAttribute()
    System.out.println(user);
    return new ResponseObject("0", "success", articleService.list(user));
}

ArticleService增加业务处理代码,根据用户级别查看不同的结果

/**
 * 如果是VIP会员,可以查看所有普通文章与精选文章
 * 如果是普通会员,只能查看所有普通文章
 * @return
 */
public List<Article> list(User user){
    int level = 0;
    if(user == null || user.getGrade().equals("normal")){
        level = 1;
    }else if(user.getGrade().equals("vip")){
        level = 2;
    }

    List<Article> list = articleMapper.list(level);
    for(Article article:list){
        ResponseObject<Video> videoResponseObject = videoFeignClient.findByArticleId(article.getArticleId());
        article.setVideo(videoResponseObject.getData());
    }
    return list;
}

ArticleMapper进行修改,增加level参数

@Mapper
public interface ArticleMapper {
    @Select("select * from article where article_type <= #{value} order by create_time desc")
    public List<Article> list(int level);


至此业务代码改造完毕
用户未登录或普通用户只能看到ArticleType=1的普通文章
而VIP身份用户则可以看到所有文章
}

SpringBoot利用自定义注解实现通用的JWT校验方案

原文:https://www.cnblogs.com/Fzeng/p/14606587.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!