【springboot进阶】jwt在前后端分离的最佳实践方式(二)

本文介绍了在支付宝小程序的用户认证登录场景下,如何使用JWT进行用户鉴权,并将JWT信息传递到Controller控制器。首先,展示了创建JWT并设置过期时间的过程,接着讲解了如何创建用户鉴权拦截器,注册拦截器以及参数解析器,确保JWT的有效性和将用户信息注入到Controller的方法参数中。最后,给出了Controller的示例,展示如何直接使用解析后的JWT用户信息。
摘要由CSDN通过智能技术生成

目录

一、实战场景

二、实战处理

创建用户鉴权拦截器

注册拦截器

参数解析器

添加解析器

控制器


第二小节,我们结合实战,看看在拦截器中如何校验jwt,又如何将jwt的载荷(payload)传递到controller控制器。

以下内容,我们用支付宝小程序场景来进行实战说明。

一、实战场景

支付宝小程序用户认证登录时序图

在支付宝小程序内,得到用户授权的authCode,经过支付宝授权平台认证通过后,由后端服务器生成对应用户的jwt,并返回给前端小程序,最终将其缓存起来,用于后续业务接口的调用。

对于能解密的密文,我们尽量的少暴露用户的信息,所以我们只会在jwt中保存用户的user_id和小程序的app_id。

//密钥secret
String JWT_USER_AUTH_SECRET = "qwerasdf123";
 
JWTCreator.Builder builder = JWT.create();
//添加claim
builder.withClaim("app_id", "20220606162783612");
builder.withClaim("open_id", "123456789");
//可根据实际情况,添加字段信息,数据以key-value方式保存

//设置JWT令牌的过期时间为一天
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND, 1 * 86400);
builder.withExpiresAt(instance.getTime());
//生成JWT
String token = builder.sign(Algorithm.HMAC256(JWT_USER_AUTH_SECRET));
System.out.println(token);

二、实战处理

一般用户鉴权可以写在过滤器或者是拦截器,从执行顺序来看,过滤器要比拦截器更靠前。

这两者的区别在于: 

  • Filter是依赖于Servlet容器,属于Servlet规范的一部分,而拦截器则是独立存在的,可以在任何情况下使用。
  • Filter的执行由Servlet容器回调完成,而拦截器通常通过动态代理的方式来执行。
  • Filter的生命周期由Servlet容器管理,而拦截器则可以通过IoC容器来管理,因此可以通过注入等方式来获取其他Bean的实例,因此使用会更方便。

创建用户鉴权拦截器

自定义的拦截器实现HandlerInterceptor,并重写preHandle方法,对请求进行拦截,校验请求附带的jwt是否合法。

public class AuthenticationInterceptor implements HandlerInterceptor {

    
    /***
     * 在请求处理之前进行调用(Controller方法调用之前)
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        
        
        //如果不是映射到方法直接通过
        if(!(handler instanceof HandlerMethod)) {
            return true;
        }

        //判断是否为通行注解,这里通过注解的方式,能灵活放行一些不需要鉴权的方法
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        //这个注解是加在方法上,所以从方法上获取这个注解
        Method method = handlerMethod.getMethod();
        if(method.isAnnotationPresent(PassAuthToken.class)) {
            PassAuthToken passAuthToken = method.getAnnotation(PassAuthToken.class);
            if (passAuthToken.required()) {
                return true;
            }
        }

        //从头部获取授权信息
        String authorization = request.getHeader("Authorization");
        if (StrUtil.isEmpty(authorization)) {//如果授权信息为空,则抛出异常,中断请求链
            //抛出自定义异常后,由统一异常处理类返回对应错误信息
            throw new AuthException("授权信息为空");
        }

        //此时的授权信息为'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMTIzNDU2IiwidXNlcl9uYW1lIjoi5byg5LiJIiwiZXhwIjoxNjU0NTI2MDU5fQ.MCuHhUOoRkMeabCk2SlfubDAL7ZgOopn7nti5QDaEhY'
        String[] tokens = authorization.split(" ");
        if (null == tokens || tokens.length != 2 || StrUtil.isBlank(tokens[1])) {
            throw new AuthException("未找到授权信息");
        }
        
        //后面的部分才是我们生成的jwt
        String token = tokens[1];
        //获取jwt里面的用户信息
        JwtUser jwtUser = this.getJwtUser(token);

        //将用户信息通过request传递到下一层,然后通过解析器将用户信息加到方法参数上
        request.setAttribute("CurrentUser", jwtUser);
        
        //返回true,则代表放行
        return true;
    }    

    /**
     * 获取jwt的用户信息
     *
     * @param token
     * @return
     */
    private JwtUser getJwtUser(String token) throws AuthException {

        DecodedJWT jwt = this.getDecodedJWT(PublicConstants.JWT_USER_AUTH_SECRET, token);
        if (ObjectUtil.isNotEmpty(jwt)) {
            JSONObject userJson = this.convertClaimToJson(jwt);
            if (ObjectUtil.isNotEmpty(userJson)) {
                //jwt的用户信息bean
                JwtUser jwtUser = new JwtUser();
                //封装平台app_id
                String appId = userJson.getString("app_id");
                jwtUser.setAppId(appId);
                //封装用户open_id
                String openId = userJson.getString("open_id");
                jwtUser.setOpenId(openId);
                //这里可以通过openId查询数据库,封装一些其他的用户信息,让它带到后面的控制器参数上

                return jwtUser;
            }
        }

        return null;
    }


     /**
     * 获取解密jwt信息
     *
     * @param secret
     * @param token
     * @return
     */
    private DecodedJWT getDecodedJWT(String secret, String token) throws AuthException {
        Algorithm algorithm = Algorithm.HMAC256(secret);
        JWTVerifier verifier = JWT.require(algorithm).build();

        try {
            //先使用常规接口的秘钥
            DecodedJWT jwt = verifier.verify(token);
            return jwt;
        } catch (Exception e) {
            throw new AuthException("授权信息错误");
        }
    }

}

要将jwt拿到的用户信息传递到controller控制器,这一行是重点。

request.setAttribute("CurrentUser", jwtUser);

注册拦截器

集成WebMvcConfigurationSupport,重写addInterceptors方法,加入我们自定义的拦截器。需要注意的是自定义的拦截器需要使用bean方式声明,这样才能够在拦截器中注入其他的bean。

@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {

    //这里以bean的方式声明拦截器,那么在拦截器中才可以注入其他的bean,例如注入用户service,用于查询用户表信息等
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        //这里可以定义拦截器的路径
        registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/api/**");
    }    

}

参数解析器

自定义的参数解析器,实现HandlerMethodArgumentResolver,重写supportsParameter方法,定义支持的参数;重写resolveArgument方法,分解实参。

public class JwtUserArgumentResolver implements HandlerMethodArgumentResolver {

    //定义这个解析器要处理什么,当方法返回true,才会执行下面的resolveArgument方法
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        Class<?> clazz = methodParameter.getParameterType();
        return clazz == JwtUser.class;
    }

    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) {
        //获取当前请求对象
        HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);

        //从拦截器传递的请求链中获取用户信息
        JwtUser jwtUser = (JwtUser) request.getAttribute("CurrentUser");

        return jwtUser;
    }
}

这里有个地方需要注意,就是controller控制器的JwtUser参数必须要放在第一位,否则无法注入成功。

添加解析器

@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {

    //这里以bean的方式声明拦截器,那么在拦截器中才可以注入其他的bean,例如注入用户service,用于查询用户表信息等
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }

    //添加参数解析器
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new JwtUserArgumentResolver());
    }

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        //这里可以定义拦截器的路径
        registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/api/**");
    }    

}

控制器

在控制器中,在方法上直接填入JwtUser,从参数解析器里面就可以实现自动注入jwt的信息。

@RestController
@RequestMapping("/api/user")
public class UserController {

	@Resource
	UserService userService;

	@GetMapping("/info")
    public Result userPlayTimes(JwtUser jwtUser) {

        String openId = jwtUser.getOpenId();

        UserInfo userInfo = userService.getByOpenId(openId);

        return ResultUtil.success(userInfo);
    }
	
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

reui

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

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

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

打赏作者

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

抵扣说明:

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

余额充值