SpringBoot集成Swagger实现JWT验证token

前言

最近有个项目涉及用户权限管理,然后之前也有看到的一个小技术JWT,简单学习了解以后结合SpringBoot Swagger实现了一个简单的demo,这个demo主要是实现用户通过用户名和密码登录系统,登录成功以后系统给一个token,之后的用户操作需要验证token,只有token符合要求才能进行其他系统资源访问操作,否则提示权限不够或者token过期有误等提示信息,现将实现过程进行简单记录。

参考链接

1. Swagger了解:https://www.jianshu.com/p/349e130e40d5

2. SpringBoot集成Swagger:https://www.jianshu.com/p/be1e772b089a

3. JWT了解:https://www.cnblogs.com/wangshouchang/p/9551748.html

4. SpringBoot实现JWT:https://blog.csdn.net/cf535261933/article/details/102603490

5. SpringBoot拦截器实现:https://blog.csdn.net/qq_30745307/article/details/80974407

实现过程

1. SpringBoot集成Swagger

1.1 导入依赖

 
        
            io.springfox
            springfox-swagger2
            2.6.1
        

        
        
            io.springfox
            springfox-swagger-ui
            2.6.1
        

1.2 配置Swagger(SwaggerConfiguration)

@Configuration //SpringBoot中配置类不可少此注释
@EnableSwagger2
public class SwaggerConfiguration {
    /**
     * 注册Bean实体
     * @return
     */
    @Bean
    public Docket createRestApi(){

        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //当前包名
                .apis(RequestHandlerSelectors.basePackage("com.practice.tokendemo"))
                .paths(PathSelectors.any())
                .build()
                //Lists工具类的newArrayList方法将对象转为ArrayList
                .securitySchemes(Lists.newArrayList(apiKey()));//结果是Swagger-ui上出现Authorize,可以手动点击输入token
    }

    /**
     * 构建Authorization验证key
     * @return
     */
    private ApiKey apiKey() {
        return new ApiKey(Constant.TOKEN_HEADER_STRING,Constant.TOKEN_HEADER_STRING,"header");//配置输入token的备注 TOKEN_HEADER_STRING = "Authorization"
    }

    /**
     * 构建API文档的详细信息方法
     * @return
     */
    public ApiInfo apiInfo(){
        return new ApiInfoBuilder()
                //API页面标题
                .title("Spring Boot继承Swagger2实现JWT")
                //创建者
                .contact(new Contact("ciery","https://mp.csdn.net/console/article",""))
                //版本
                .version("1.0")
                //描述
                .description("API描述")
                .build();
    }
}

SpringBoot集成Swagger实现JWT验证token_第1张图片

1.3 Swagger简单介绍

Swagger UI提供了一个可视化的UI页面展示描述文件。接口的调用方、测试、项目经理等都可以在该页面中对相关接口进行查阅和做一些简单的接口请求。该项目支持在线导入描述文件和本地部署UI项目。

Springfox-swagger可以通过扫描代码去生成swagger定义的描述文件(通过维护这个描述文件可以去更新接口文档),所有的信息都在代码中。代码即接口文档,接口文档即代码。

2. 基本功能的实现(用户登录)

写法和普通的controller层一致,只不过多了API的注释完成与swagger的集成。

@Api("用户操作接口") //配置swagger页面api名称
//@Controller("user") //如果controller中有多个方法ResponseMapping,那么直接使用@RestController注释,底下的所有方法都不需要再加@ReponseMapping注释,直接使用@XXXMapping表明获取参数的方式(PUT/GET/POST)即可
@RestController("user")
@RequestMapping(Constant.BASE_PATH_PERFIX + "/user") //配置网址前缀 BASE_PATH_PERFIX = "/api/practice/v1"
public class UserController{
    @Autowired
    UserService userService;

    @ApiOperation(value = "登录", notes = "通过用户名和密码登录系统") //配置方法操作在swagger中的名和曾 notes为备注
    @PostMapping(value = "login")
    public String login(@RequestBody User user){
        if(user==null){
            return "用户名或密码为空!";
        }
        User loginUser = userService.login(user);
        if(loginUser==null){
            return "用户名或密码错误!";
        }
        //生成token(用户拿着这个token+请求返回给服务端,服务端匹配token,如果匹配上了则处理用户发来的请求,如果匹配不上则用户验证失败不处理请求)
        String jwt = TokenUtils.createToken(user.getUserName());
        HashMap map = new HashMap<>();
        map.put("token",jwt);
        return ResponseEntity.ok(loginUser).toString() + map;
    }

把项目跑起来后浏览器进入localhost:8080/swagger-ui.html进入swagger页面。

SpringBoot集成Swagger实现JWT验证token_第2张图片

3. JWT实现

3.1 导入依赖

        
            com.auth0
            java-jwt
            3.10.0
        
        
            io.jsonwebtoken
            jjwt
            0.9.0
        
        
            commons-codec
            commons-codec
            1.9
        

3.2 JWT生成和解析

@Component //SpringBoot中组件类需要使用@Component进行组件注册才能使用
public class TokenUtils {

    /**
     * 由字符串生成加密key
     * @return
     */
    public static SecretKey generalKey() {
        String stringKey = "thisisasecretkey"; //随机写的
        // 本地的密码解码
        byte[] encodedKey = Base64.decodeBase64(stringKey);
        // 根据给定的字节数组使用AES加密算法构造一个密钥
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }

    /**
     * 生成JWT
     * @return
     */
    public static String createToken(String userName){
        //设置JWT的header
        HashMap map = new HashMap();
        //Head
        map.put("alg","HS256");
        map.put("typ","jwt");
        //Payload
        map.put("username",userName); //根据userName生成jwt
        //设置JWT的过期时间
        Calendar now = Calendar.getInstance();
        now.add(Calendar.MINUTE,20);//当前时间+20mins
        Date expireDate = now.getTime();//Calendar转Date
        //设置JWT生效时间
        Date nowDate = new Date();//系统当前时间
        SecretKey key = generalKey(); //密钥(服务端专有,面向客户端隐藏)
        JwtBuilder jwtBuilder = Jwts.builder()
                .setClaims(map)
                .setExpiration(expireDate)
                .setNotBefore(nowDate)
                //Signature
                .signWith(SignatureAlgorithm.HS256,key);//设置签发算法和密钥
        return Constant.TOKEN_PERFIX + jwtBuilder.compact();//jwt前面一般会加上Bearer
    }

    /**
     * 解析token
     * @param token
     * @return
     */
    public static Claims parseToken(String token){
        SecretKey key = generalKey();
        try{
            Claims claims = Jwts.parser()
                    .setSigningKey(key)
                    .parseClaimsJws(token.replace(Constant.TOKEN_PERFIX,"")).getBody();//TOKEN_PERFIX = "Bearer"
            return claims;
        }catch (Exception e){
            throw new IllegalStateException("Invalid token." + e.getMessage());
        }
    }
}

3.3 JWT知识总结

SpringBoot集成Swagger实现JWT验证token_第3张图片

JWT由Head、Payload和Signature组成。

Head:头部。包括alg和typ,其中alg为加密类型,一般选择HS256,typ指定jwt

Payload:声明。JWT中claims包含想要签署的信息(contact、expireDate等)

SpringBoot集成Swagger实现JWT验证token_第4张图片

Signature:签名。对Head和Payload的签名,即对Head和Payload进行加密转换处理

以该demo为例生成一个token结果如下:

token=BearereyJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE1OTEyNjcyNDIsInR5cCI6Imp3dCIsImV4cCI6MTU5MTI2ODQ0MiwiYWxnIjoiSFMyNTYiLCJ1c2VybmFtZSI6ImNpZXJ5In0.OwKU0Wj7FV22vmgwgKLFCzA_sgj0fOGg42ogDWzyrDI

4. 拦截器实现

 写的这个小demo中是想对用户登录后的其他操作进行身份验证,当用户登录后给用户一个token,之后用户的操作都需要拿着这个token发送请求,服务端匹配token。如果匹配上了那么正常处理用户请求;如果匹配不上则用户身份认证失败不处理用户请求。(处理过程同session)

那么具体来讲就是对除了login的操作其他需要用户身份认证的操作(对应controller中的XXXMapping链接)都需要进行验证,可以考虑使用拦截器拦截这些链接,然后进行token匹配,如果匹配上了那么进行其他操作,如果匹配不上那么对用户进行提示(请先登录!)

4.1 拦截器实现(AuthInterceptor)

/**
 * 拦截器Interceptor:对过往的连接/对象进行判断,
 * 如果符合条件那么放行/做其他处理,如果不符合条件那么提示并false拦截
 */
@Component  //拦截器也是一个组件,需要加@Component注解进行组件注册
public class AuthInterceptor implements HandlerInterceptor {
    //声明一个static final的Logger对象
    private static final Logger logger = LoggerFactory.getLogger(AuthInterceptor.class);
    /**
     * 预处理回调方法,实现处理器的预处理
     * 返回值:true表示继续流程;false表示流程中断,不会继续调用其他的拦截器或处理器
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("开始拦截.........");
        //设置response的编码格式
        response.setContentType("text/html;charset=utf-8");
        //获取请求的url
        String url = request.getServletPath().toString();
        System.out.println("url:" + url);
        //判断放行的url
        if(url.contains("/user/login")){
            return true;
        }
        if(url.contains("/swagger-resource")){
            return true;
        }
        if(url.contains("/v2/api-docs")){
            return true;
        }
        //获取request中的参数token
        String token = request.getHeader(Constant.TOKEN_HEADER_STRING);
        //如果token为空或不存在
        if(token==null || "".equals(token) || !token.startsWith(Constant.TOKEN_PERFIX)){
            logger.info("{} : Unknown token", request.getServletPath());
            //将结果打印返回到前端
            response.getWriter().print("The resource requires authentication, which was not supplied with the request");
            return false;//拦截成功
        }
        //解析token
        Claims claims = TokenUtils.parseToken(token);
        String userName = (String)claims.get("username");
        Date expireTime = claims.getExpiration();
        //如果token的username不存在
        if(!"ciery".equals(userName)){
            logger.info("{} : token user not found", request.getServletPath());
            response.getWriter().print("ERROR Permission denied");
            return false;
        }
        //如果token过期
        if(expireTime.before(new Date())){
            logger.info("{} : token expired", request.getServletPath());
            response.getWriter().print("The token expired, please apply for a new one");
            return false;
        }
        //token匹配成功,放行
        request.setAttribute(Constant.CURRENT_USER, userName);
        System.out.println("放行...........");
        return true;
    }
    /**
     * 后处理回调方法,实现处理器(controller)的后处理,但在渲染视图之前
     * 此时我们可以通过modelAndView对模型数据进行处理或对视图进行处理
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }
  /**
   * 整个请求处理完毕回调方法,即在视图渲染完毕时回调,
   * 如性能监控中我们可以在此记录结束时间并输出消耗时间,
   * 还可以进行一些资源清理,类似于try-catch-finally中的finally,
   * 但仅调用处理器执行链中
   */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

4.2 拦截器注册(WebMvcConfig) SpringBoot中必须对拦截器进行注册之后才能起效

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    AuthInterceptor authInterceptor;

    /**
     * 添加拦截器
     * addPathPatterns 用于添加拦截规则,/**表示拦截所有请求
     * excludePathPatterns 排除拦截
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册AuthInterceptor拦截器
        registry.addInterceptor(authInterceptor).addPathPatterns(Constant.BASE_PATH_PERFIX +"/user/test") //拦截/api/practice/v1/user/test
                .excludePathPatterns(Constant.BASE_PATH_PERFIX + "/user/login"); //放行/api/practice/v1/user/login
    }

    // 这个方法是用来配置静态资源的,比如html,js,css,等等
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    }

}

4.3 测试

4.3.1 在原有的UserController基础上加了个test,测试JWT,当token没上传时提示token 为空。

 @ApiOperation(value = "测试", notes = "通过用户名和密码测试token")
    @PostMapping(value = "test")
    public String testToken(@RequestBody User user){
        return user.toString();
    }

4.3.2 先login然后上传token再测试test

SpringBoot集成Swagger实现JWT验证token_第5张图片

SpringBoot集成Swagger实现JWT验证token_第6张图片

4.3.3 如果长时间不操作token过期,则照样拦截成功提示用户

The token expired, please apply for a new one

如果用户名不为ciery,则拦截成功提示用户

ERROR Permission denied

4.4 Logger使用说明

多用{}而非+,对于占位符的形式而言,只有在我们需要的时候才会提取字符串,这样就会避免创建string对象的时候消耗大量的资源。因为string对象是不可变的,所以会消耗大量的堆内存,一旦我们用了字符串拼接,就有大量的字符串占用机器的内存,但是当我们用占位符的时候,只有在用到的时候才会动态的创建

logger.info("{} : Unknown token", request.getServletPath());参数 request.getServletPath()直接复制给前面的占位符{}(动态分配空间)

总结

项目中遇到比较有意思觉得需要学习的技术点,需要利用空闲时间进行学习并尽量整合多个技术点知识点进行实战,好好学习天天向上^o^

你可能感兴趣的:(后端开发,Java学习,Spring,Boot学习)