除暖

Spring boot +swagger+token

电脑版发表于:2021/11/3 15:23

文章借鉴于://https://www.jianshu.com/p/6e5ee9dd5a61

一、pom的相关依赖

<!--版本控制2.5.5-->
<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>2.5.5</version>  <!--版本控制2.5.5-->
   <relativePath/>
</parent>



<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!--JWT-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.9</version>
</dependency>


二、swagger 的配置
导入相关的包

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.List;
@Configuration
@EnableSwagger2
public class SwaggerConfig2 {  //https://www.jianshu.com/p/6e5ee9dd5a61
    @Bean
    public Docket api(){
        return new Docket(DocumentationType.SWAGGER_2).
             useDefaultResponseMessages(false)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.regex("^(?!auth).*$"))
                .build()
                .securitySchemes(securitySchemes())
                .securityContexts(securityContexts());//
    }

    private List<ApiKey> securitySchemes()
    {
        return newArrayList( new ApiKey("Authorization", "Authorization","header"));
    }

    private List<SecurityContext> securityContexts() {
        return newArrayList(
                SecurityContext.builder()
                        .securityReferences(defaultAuth())
                        .forPaths(PathSelectors.regex("^(?!auth).*$"))//含有auth路径的接口可以没有令牌
                        .build()
        );
    }

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

}
1、配完后会出现这个


2、下图是这个按钮的内容


三、配置toke令牌的加密、解密
1、类Constant的字段设置

public final static String TOKEN_HEADER_STRING="Authorization";
public final static String TOKEN_PERFIX="Bearer";
public final static String CURRENT_USER="name";
public final static String BASE_PATH_PERFIX="/api/practice/v1";


2、toke的配置


导入的包

import com.example.My1101.pojo.Constant;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
//SpringBoot中组件类需要使用@Component进行组件注册才能使用(不知道该类属于什么层就可以用它,如:服务处、Dao等)
@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<String, Object> map = new HashMap<String, Object>();
        //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("Bearer", "")).getBody();//TOKEN_PERFIX = "Bearer"
            return claims;
        } catch (Exception e) {
            throw new IllegalStateException("Invalid token." + e.getMessage());
        }
    }

}

四、拦截的具体逻辑
导入的包

import com.example.My1101.pojo.Constant;
import io.jsonwebtoken.Claims;
import io.swagger.models.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
@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 {

//        return HandlerInterceptor.super.preHandle(request, response, handler);
        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("/user/test2")){
            return true;
        }
        if(url.contains("/swagger-resource")){
            return true;
        }
        if(url.contains("/v2/api-docs")){
            return true;
        }
        //第一次请求放行(因为它是探路用的)
        if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
            System.out.println("OPTIONS请求,放行");
            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(userName.equals(null)){
            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 {
//        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }
    /**
     * 整个请求处理完毕回调方法,即在视图渲染完毕时回调,
     * 如性能监控中我们可以在此记录结束时间并输出消耗时间,
     * 还可以进行一些资源清理,类似于try-catch-finally中的finally,
     * 但仅调用处理器执行链中
     */


    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

五、配置mvc 实现拦截

导入的包

import com.example.My1101.pojo.Constant;
import com.example.My1101.utils.AuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@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 +"/**") //拦截/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) {
    }
}

六、controller层的应用

@Api("用户操作接口")
@RestController("user")
@RequestMapping("/api/practice/v1" + "/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<String, String> map = new HashMap<>();
        map.put("token", jwt);
        return ResponseEntity.ok("loginUser").toString() + map;
    }


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

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


关于TNBLOG
TNBLOG,技术分享。技术交流:群号677373950
ICP备案 :渝ICP备18016597号-1
App store Android
精彩评论
{{item.replyName}}
{{item.content}}
{{item.time}}
{{subpj.replyName}}
@{{subpj.beReplyName}}{{subpj.content}}
{{subpj.time}}
猜你喜欢