SpringBoot整合JWT实现登录验证(2)

1 JWT请求流程

SpringBoot整合JWT实现登录验证(1)

通过上一章节,介绍了JWT的相关信息,以及JWT字符串的生成与JWT的解析,接下来将用代码实现登录验证。实现流程如下图:

JWT流程

  1. 发送登录请求,服务器根据私钥key创建JWT字符串返回给用户;
  2. 浏览器将该jwt串在请求头向服务器发送请求;
  3. 服务器验证该jwt 【通过拦截器实现,校验JWT中有效荷载playload,具体的校验逻辑与实际要求有关】;
  4. 校验通过后返回响应的资源给浏览器,否则抛出登录异常

2 相关细节

在实际开发中,处在不同的开发环境时,如测试环境时,请求资源时,将不需要进行token验证。所以在此需求下,配置application.yml文件 jwt:enabled属性,当该值为true时,开启JWT token验证服务,反之当为false时,关闭该服务。
在这里插入图片描述
于此同时,并不是所有的资源都需要进行token验证,如登录获得token信息的接口,则不需要token验证。因此自定义注解@PassToken ,标注该请求的方法将不进行token验证。自定义注解信息如下:

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {

    boolean canPass() default  false;
}

3 相关依赖

  <dependencies>
  <!--web starter-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
             <version>2.2.6.RELEASE</version>
        </dependency>


<!--swagger2-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>


        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

<!--jwt-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

    </dependencies>

4 拦截器实现token校验逻辑

使用拦截器,通过反射,可以将请求缩小到方法上。实现@PassToken注解标注的方法可以不需要进行token登录验证。

4.1 编写JwtInterceptor
/**
 * @author lyf
 * @projectName correcting-message
 * @date 2022/3/7 上午 08:32
 * @description 通过拦截器实现请求是否需要进行token验证
 */
@Configuration
public class JwtInterceptor extends HandlerInterceptorAdapter {

    /**
    * 读取jwt.enable配置信息
    */
    @Value("${jwt.enabled}")
    private String jwt_enabled;

    /**
     * 在业务处理器处理请求之前执行
     * @param request 请求
     * @param response 响应
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        if(jwt_enabled.equals("false")){
            //jwt token验证服务未开启时,返回true
            return true;
        }else{
            // 请求资源没有映射到方法上时直接通过
            if (!(handler instanceof HandlerMethod)) {
                return true;
            }
            //判断方法上是否有存在@PassToken注解
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            PassToken annotation = method.getAnnotation(PassToken.class);
            if(annotation!=null&&annotation.canPass()){
                return true;
            }else{
                //所有的请求头信息
                Enumeration<String> headerNames = request.getHeaderNames();

                while ( headerNames.hasMoreElements()){
                    System.out.println(headerNames.nextElement());
                }
                //解析请求中的token信息
                String token=request.getHeader("Authorization");
                //解析请求中的
                String empno=request.getHeader("empno");
                if(StringUtils.isEmpty(token)){
                    throw new LoginException("请求头中的token为空,token验证失败,请重新登录");
                }else{
                    JwtUtil jwtUtil=new JwtUtil(); //JwtUti工具类见上一章节介绍
                    DefaultClaims object = (DefaultClaims)jwtUtil.decodeJWTrHS256(token, "123456");
                    //自定义token playload载体的信息
                    String emp=object.get("emp",String.class);
                    if(!emp.equals(empno)){
                        //自定义LoginException,当校验token失败后,抛出该异常,交由全局异常处理
                        throw new LoginException("token验证用户工号失败,无法请求服务");
                    }else {
                        //判断token是否过期
                        Date expireDate =object.getExpiration();
                        if(expireDate.before(new Date())){
                            throw new LoginException("token已过期,请重新登录获得token");
                        }
                    }
                }
            }
        }

        return true;
    }


    @Override
    public void postHandle(HttpServletRequest httpServletRequest,
                           HttpServletResponse httpServletResponse,
                           Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest,
                                HttpServletResponse httpServletResponse,
                                Object o, Exception e) throws Exception {
    }

}

4.2 注册JwtInterceptor
/**
 * @author lyf
 * @projectName correcting-message
 * @date 2022/3/8 上午 11:22
 * @description
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor())
                .addPathPatterns("/controller/**");
    }

    @Bean
    public JwtInterceptor authenticationInterceptor() {
        return new JwtInterceptor();
    }
}

5 Swagger2配置

/**
 * @author lyf
 * @projectName correcting-message
 * @date 2022/2/21 下午 03:42
 * @description swagger2配置
 **/
@Configuration
@EnableSwagger2
@EnableConfigurationProperties(Swagger2Propertity.class)
public class Swagger2Config {


    @ConditionalOnProperty(name = "swagger2.enabled",havingValue = "true")
    @Bean
    public Docket createDocket(){

        Docket docket=new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(getApiInfo())  //getApiInfo() 省略swagger信息方法
                //设置全局参数
                .globalOperationParameters(configGlobalParamer())
                //添加安全验证
                .securitySchemes(configSecurityScheme())
                .securityContexts(securityContexts())
                .groupName("correcting data")
                .select()
                //API有效的路径
                .apis(RequestHandlerSelectors.basePackage("com.foxconn.controller"))
                .build();
        return docket;
    }


    /**
     * 配置全局安全Scheme,用于安全访问资源
     * @return
     */
    public List<ApiKey> configSecurityScheme() {
        List<ApiKey> apiKeyList= new ArrayList<ApiKey>();
        //Authorization key值固定
        apiKeyList.add(new ApiKey("Authorization", "Authorization", "header"));
        return apiKeyList;
    }


    private List<SecurityContext> securityContexts() {
        List<SecurityContext> securityContexts = new ArrayList<>();
        securityContexts.add(SecurityContext.builder()
                .securityReferences(defaultAuth())
                .forPaths(PathSelectors.regex("^(?!app).*$")).build());
        return securityContexts;
    }

    private List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        List<SecurityReference> securityReferences = new ArrayList<>();
        securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
        return securityReferences;
    }


    /**
     * 配置全局参数
     * @return
     */
    public  List<Parameter> configGlobalParamer(){
        List<Parameter> pars = new ArrayList<>();
        ParameterBuilder headerPar = new ParameterBuilder();
        Parameter p = headerPar
                //header请求头类型
                .parameterType("header")
                .modelRef(new ModelRef("string"))
                //参数名字
                .name("empno")
                //参数描述
                .description("empno请求头中用户工号,用于校验token中的用户信息")
                //该参数是否必须,false表示非必须
                .required(false)
                .build();

        pars.add(p);
        return pars;
    }

    /**
     * 重定向
     * @return
     */
    @RequestMapping("/")//重定向url
    public ModelAndView forwardSwagger() {
        ModelAndView mvc=new ModelAndView();
        mvc.setViewName("redirect:/swagger-ui.html");
        return mvc;
    }



}


4 项目结构

项目结构

5 测试

5.1 获得token

在这里插入图片描述

5.2 测试非登录请求——不带token验证

在这里插入图片描述

5.3 测试非登录请求——带token验证

在这里插入图片描述
在这里插入图片描述


版权声明:本文为contact97原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。