基于SpringBoot2开发WebApi(六)JWT(json web token)集成

1.前言

1.1.JWT说明

JWT全程JSON Web Token,是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名。

1.2.什么情况下需要用JWT

  • Authorization (授权) : 这是使用JWT的最常见场景。一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。
  • Information Exchange (信息交换) : 对于安全的在各方之间传输信息而言,JSON Web Token无疑是一种很好的方式。因为JWT可以被签名,例如,用公钥/私钥对,你可以确定发送人就是它们所说的那个人。另外,由于签名是使用头和有效负载计算的,您还可以验证内容没有被篡改。

1.3.JWT结构说明

JSON Web Token由三部分组成,它们之间用圆点(.)连接。这三部分分别是:

  • Header:JWT头
  • Payload:有效载荷
  • Signature:签名

结构是这样的:xxxxxx.yyyyyy.zzzzzz

例如:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsiMSIsImFkbWluIiwiMSJdLCJleHAiOjE2MjMwNzUyMzcsImlhdCI6MTYyMzA0NjQzN30.pT5NgZCkfN2WwQS8rp1Scifa07KbuvZbXO8y8596ACg

基于SpringBoot2开发WebApi(六)JWT(json web token)集成

2.集成说明

2.1引入依赖

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>

2.2.定义Token工具类

TokenUtil
package com.xxxxxx.sim.utils;

import com.auth0.jwt.JWT;
import com.xxxxxx.sim.model.AdminUser;
import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.math.BigInteger;
import java.util.List;

/**
 * Token方法封装
 * @author xxxx
 * @date 20200428
 */
public class TokenUtil {
    /**
     * 获取Token里面的UserId
     * @return
     */
    public static String getTokenUserId() {
        // 从 http 请求头中取出 token
        String token = getRequest().getHeader("token");
        String userId = JWT.decode(token).getAudience().get(0);
        return userId;
    }

    /**
     * 获取Token里面的UserName
     * @return
     */
    public static String getTokenUseName(){
        // 从 http 请求头中取出 token
        String token = getRequest().getHeader("token");
        String userName = JWT.decode(token).getAudience().get(1);
        return userName;
    }
    /**
     * 获取request
     *
     * @return
     */
    public static HttpServletRequest getRequest() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        return requestAttributes == null ? null : requestAttributes.getRequest();
    }

}

2.3.定义注解

@CheckToken需要验证Token注解
package com.xxxxxx.sim.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 需要验证Token
 * @author xxxxxx
 * @date 20200428
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckToken {
    boolean required() default true;
}
@PassToken跳过Token验证注解
package com.xxxxxx.sim.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


/**
 * 跳过验证
 * @author xxxxxx
 * @date 20200428
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
    boolean required() default true;
}

需要验证Token注解

2.4.定义Token拦截器

AuthenticationInterceptor
package com.xxxxxx.sim.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.xxxxxx.sim.annotation.CheckToken;
import com.xxxxxx.sim.annotation.PassToken;
import com.xxxxxx.sim.model.AdminUser;
import com.xxxxxx.sim.service.AdminUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.math.BigInteger;


/**
 * Token拦截器
 * @author xxxxxx
 * @date 20200428
 */
public class AuthenticationInterceptor implements HandlerInterceptor {
    @Autowired
    private AdminUserService adminUserService;
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
        // 从 http 请求头中取出 token
        String token = httpServletRequest.getHeader("token");
        // 如果不是映射到方法直接通过
        if(!(object instanceof HandlerMethod)){
            return true;
        }
        HandlerMethod handlerMethod=(HandlerMethod)object;
        Method method=handlerMethod.getMethod();
        //检查是否有PassToken注释,有则跳过认证
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                return true;
            }
        }
        //检查有没有需要用户权限的注解
        if (method.isAnnotationPresent(CheckToken.class)) {
            CheckToken checkToken = method.getAnnotation(CheckToken.class);
            if (checkToken.required()) {
                // 执行认证
                if (token == null) {
                    return false;
                }
                // 获取 token 中的 user id
                String userId;
                try {
                    userId = JWT.decode(token).getAudience().get(0);
                } catch (JWTDecodeException j) {
                    return false;
                }
                AdminUser user = adminUserService.queryAdminUserById(new BigInteger(userId));
                if (user == null) {
                    return false;
                }
                // 验证 token
                JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassWord())).build();
                try {
                    jwtVerifier.verify(token);
                } catch (JWTVerificationException e) {
                    return false;
                }
                return true;
            }
        }
        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 {
    }
}

2.5.初始化拦截器

package com.xxxxxx.sim.configuration;

import com.xxxxxx.sim.utils.AuthenticationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;

import java.util.List;

/**
 * 新建Token拦截器
 * @author xxxxxxx
 * @date 20200429
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor())
                .addPathPatterns("/**");
    }
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> arg0) {
        // TODO Auto-generated method stub

    }
    @Override
    public void addCorsMappings(CorsRegistry arg0) {
        // TODO Auto-generated method stub

    }
    @Override
    public void addFormatters(FormatterRegistry arg0) {
        // TODO Auto-generated method stub

    }
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry arg0) {
        // TODO Auto-generated method stub

    }
    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> arg0) {
        // TODO Auto-generated method stub

    }
    @Override
    public void addViewControllers(ViewControllerRegistry arg0) {
        // TODO Auto-generated method stub

    }
    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer arg0) {
        // TODO Auto-generated method stub

    }
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer arg0) {
        // TODO Auto-generated method stub

    }
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer arg0) {
        // TODO Auto-generated method stub

    }
    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> arg0) {
        // TODO Auto-generated method stub

    }
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> arg0) {
        // TODO Auto-generated method stub

    }
    @Override
    public void configurePathMatch(PathMatchConfigurer arg0) {
        // TODO Auto-generated method stub

    }
    @Override
    public void configureViewResolvers(ViewResolverRegistry arg0) {
        // TODO Auto-generated method stub

    }
    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> arg0) {
        // TODO Auto-generated method stub

    }
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> arg0) {
        // TODO Auto-generated method stub

    }
    @Override
    public MessageCodesResolver getMessageCodesResolver() {
        // TODO Auto-generated method stub
        return null;
    }
    @Override
    public Validator getValidator() {
        // TODO Auto-generated method stub
        return null;
    }
}

3.Token使用

3.1.定义token使用Service

package com.xxxxxx.sim.service;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.xxxxxx.sim.model.AdminUser;
import org.springframework.stereotype.Service;

import java.util.Date;

/**
 * 获取Token Service
 * @author xxxxxx
 */
@Service
public class TokenService {

    /**
     * 获取Token
     * @param user
     * @return
     */
    public String getToken(AdminUser user) {
        Date start = new Date();
        //8小时有效时间
        long currentTime = System.currentTimeMillis() + 8*60* 60 * 1000;
        Date end = new Date(currentTime);
        String token = "";

        token = JWT.create().withAudience(user.getId().toString()).withIssuedAt(start).withExpiresAt(end)
                .sign(Algorithm.HMAC256(user.getPassWord()));
        return token;
    }
}

3.2.登录Controller生成Token

package com.xxxxxx.sim.controller;

import com.xxxxxx.sim.base.Result;
import com.xxxxxx.sim.base.ResultEnum;
import com.xxxxxx.sim.base.ResultGenerator;
import com.xxxxxx.sim.model.AdminUser;
import com.xxxxxx.sim.service.AdminUserService;
import com.xxxxxx.sim.service.TokenService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

/**
 * @author xxxxxx
 */
@Api(tags = "登录")
@RestController
public class LoginController {
    @Autowired
    private AdminUserService adminUserService;
    @Autowired
    private TokenService tokenService;

    @ApiOperation(value = "登陆", notes = "登陆")
    @ApiResponses({
            @ApiResponse(code = 200,message = "成功",response = AdminUser.class),
    })
    @RequestMapping(value = "/login" ,method = RequestMethod.GET)
    public Result<Object> login(@RequestParam String userName, @RequestParam String password, HttpServletResponse response){
        AdminUser adminUser=adminUserService.queryAdminUserListByUserName(userName);
        if(adminUser!=null)
        {
            if(adminUser.getPassWord().equals(password))
            {
                String token = tokenService.getToken(adminUser);
                Cookie cookie = new Cookie("token", token);
                cookie.setPath("/");
                response.addCookie(cookie);
                //将token返回前端
                adminUser.setUserToken(token);
                //将密码置空,不返回给前端
                adminUser.setPassWord("");
                return ResultGenerator.success(adminUser);
            }else
            {
                return ResultGenerator.failure(ResultEnum.PASSWORD_ERROR);
            }
        }else
        {
            return ResultGenerator.failure(ResultEnum.USER_NOT_EXISTS);
        }
    }
}

3.3.接口需要验证Token

    @CheckToken
    @ApiOperation(value = "用户列表查询", notes = "用户列表查询")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "key",value = "查询条件"),
            @ApiImplicitParam(name = "pageNum",value = "当前页",required = true),
            @ApiImplicitParam(name = "pageSize",value = "每页显示条数",required = true),
            @ApiImplicitParam(name = "roleId",value = "角色id",required = true)
    })
    @ApiResponses({
            @ApiResponse(code = 200,message = "成功",response = AdminUser.class),
    })
    @PostMapping(value = "queryAdminUserList")
    public Result<Object> queryAdminUserList(String key, int pageNum, int pageSize,BigInteger roleId){
        try {
            //判空
            if(roleId.longValue() <= 0){
                return ResultGenerator.failure(ResultEnum.Param_Error) ;
            }
            PageResult pageUserList = adminUserService.queryAdminUserList(key,pageNum,pageSize,roleId);
            return ResultGenerator.success("成功",pageUserList);
        } catch (Exception e) {
            e.printStackTrace();
            return ResultGenerator.error("失败:" + e.getMessage());
        }
    }

在接口方法上加上@CheckToken注解即可

4.最后再Swagger2上增加token传入的说明

package com.xxxxxx.sim.configuration;


import com.xxxxxx.sim.base.ResultEnum;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMethod;
import springfox.documentation.builders.*;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Parameter;
import springfox.documentation.service.ResponseMessage;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * <p>Swagger2配置类</p>
 * @author xxxxxx
 * @date 20200429
 */
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    /**
     * 创建API应用
     * apiInfo() 增加API相关信息
     * 通过select()函数返回一个ApiSelectorBuilder实例,用来控制哪些接口暴露给Swagger来展现,
     * 本例采用指定扫描的包路径来定义指定要建立API的目录。
     *
     * @return
     */
    @Bean
    public Docket createRestApi() {
        //添加全局响应状态码
        List<ResponseMessage> responseMessageList = new ArrayList<>();
        Arrays.stream(ResultEnum.values()).forEach(errorEnums -> {
            responseMessageList.add(
                    new ResponseMessageBuilder().code(errorEnums.getCode()).message(errorEnums.getMessage()).responseModel(
                            new ModelRef(errorEnums.getMessage())).build()
            );
        });

        ParameterBuilder aParameterBuilder = new ParameterBuilder();
        aParameterBuilder.name("token").description("token验证信息").modelRef(new ModelRef("String")).parameterType("header").build();
        List<Parameter> aParameters = new ArrayList<Parameter>();
        aParameters.add(aParameterBuilder.build());

        return new Docket(DocumentationType.SWAGGER_2)
                // 添加全局响应状态码
                .globalResponseMessage(RequestMethod.GET, responseMessageList)
                .globalResponseMessage(RequestMethod.POST, responseMessageList)
                .globalResponseMessage(RequestMethod.PUT, responseMessageList)
                .globalResponseMessage(RequestMethod.DELETE, responseMessageList)

                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.jointcontrols.sim.controller"))
                .paths(PathSelectors.any())
                .build()
                .useDefaultResponseMessages(false)
                .globalOperationParameters(aParameters);



    }

    /**
     * 创建该API的基本信息(这些基本信息会展现在文档页面中)
     * 访问地址:http://项目实际地址/swagger-ui.html
     *
     * @return
     */
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("SIM Manager APIs")
                .description("SIM卡管理接口文档")
                .termsOfServiceUrl("http://www.baidu.com")
                .version("1.0.0")
                .build();
    }
}

 

上一篇:Java中Collection集合的常用方法


下一篇:python内置方法 3.any()函数