Swagger interface document knife4j based on system authentication

preface

Six months ago, a set of Swagger interface document knife4j based on business system authentication was built, which works well, so the record is built.

Pain point

If the Swagger like interface document framework is not used, the general company will also build a set of independent interface document projects. For example, the company previously used CrapApi, a completely open source interface document project. Cry about the pain points of these independent interface document projects.

① Writing is time-consuming and laborious

The pain before using the interface document framework is self-evident. The simpler curd is to write documents, the more painful it is. You will find that you can write the interface leisurely in 3 hours in the morning and add the interface on the test line in 4 hours in the afternoon.

② Even if it is written, it is difficult to guarantee the specification

Generally, the request parameters are written, and a lot of messy return values are ignored. If the front end can't understand them, ask again.

③ Iterative difficulty

Documents are usually written for newly added interfaces, but it often doesn't matter if the interfaces are modified. It takes time and effort to change the documents for small changes. There is no mandatory specification at all, resulting in no change at all. The contents of the documents are very old and have low reference value.

④ Difficult debugging

This is a disgusting back-end and front-end thing. Disgusting to the back end, the natural quality of the documents is not high, and the front end is naturally uncomfortable to view the documents. In addition, the debugging function of the general interface document project is weak and cannot be as powerful as postman, so the front end is even more reluctant to use, and even copies the interface, and reopens a document test in postman. It cannot achieve convenient functions such as postman unified domain name prefix, cache history form submission, unified request header and token.

⑤ Query difficulty

This problem may have been solved in the higher version of CrapApi document project, or it does not exist in other document projects. The document cannot find the interface content of the multi-level directory, which is true. It is equivalent to finding the detailed information of the interface document according to the url and interface comments.
In conclusion, independent interface documentation is a thankless task. Unless the specification is strictly reviewed, it will be inefficient and inefficient.

Swagger's advantages

■ easy to write

Do you usually write notes? The answer is yes, so now just add a few comments, and then move the comments to the comments. Including the request mode and so on, the framework will identify the interface to help you generate, which shows how easy it is.

■ Swagger is not invasive

Whether swagger is invasive, the answer is No. Many reasons against using swagger are that it is too intrusive to the source code, resulting in bloated code, but they think that javadoc (in other words, generating documents according to standard comments) is very reasonable, which is obviously biased. Swagger's comments can actually be regarded as javadoc comments. After understanding the comments, the comments are actually classification, The value in the annotation is a very standard annotation. People are advocating to write good annotations, and after wrapping a layer of specifications for annotations, do you want to dislike its bloated?

■ easy iteration and version available

Annotations are in the java project. Whenever an interface is modified, it is easy to modify the annotation (annotation content). If the back end of such a simple operation is unwilling to modify, it is probably fishing and ready to run. As for the historical version, the submitted code is naturally stored in the svn or git repository, so it is very simple and convenient to view the history.

■ normative

There is no doubt about the standardization of documents. Based on OpenAPI, the standard can no longer be standardized.

■ convenient commissioning

After connecting with Knife4j, I don't even like postman, which shows the excellence of this framework. All the auxiliary functions are available, and the cache can be turned on, which is very convenient.

■ convenient query

According to the level generated by the control class, natural excellent classification, keyword search can also achieve the purpose of filtering through level search.

Swagger collection past and present

Note that it's Swagger, not swag of bay.

		=> springdoc
Swagger1 => Swagger2 => Swagger3
		=> swagger-bootstrap-ui => Knife4j
	=> Other series ui frame

Swagger1(swagger-ui) =>
A RESTFUL interface document online automatic generation + function test function framework, low version introduces trouble.

Swagger2(springfox-swagger) =>
The default access path is different from swagger1, and the domestic mainstream version is used
swagger-ui/index.html

Swagger3(springfox-boot-starter) =>
In the springboot version, only one dependency needs to be referenced. Compared with Swagger2, swagger3 redefines a set of annotations. Swagger3 normalizes the annotation name, releases a version, and then stops the work. It's unfinished
swagger-ui.html

springdoc(springdoc-openapi-ui) =>
Based on swagger, the ui of swagger is very low. The ui here is greatly changed and is still being maintained. It can be used as a swagger for ui reconstruction

swagger-bootstrap-ui(com.github.xiaoymin) =>
Domestic conscience is open source. Swagger's enhanced ui also quotes springdoc and other source codes. Later, it also supports more functions, not just a ui framework

Knife4j =>
Renamed, formerly swagger bootstrap UI, named kni4j in the hope that she can be as small, lightweight and powerful as a dagger! The UI continues to be beautified and more suitable for micro services.

model selection

■ process

Naturally, the introduction of the framework is to choose the new rather than the old. It is directly based on Swagger3, springfox boot starter. After some tossing, it is found that the internal fields of the generic return value cannot be automatically recognized, and the framework is not maintained.

Then, if you go back to the lower version of Swagger2, you can recognize the generic return value, but the interface is too ugly, and the function is really rudimentary.

Start selecting the ui enhanced version of swagger's underlying framework, use the popular and still maintained springdoc, and set up the springdoc. The interface is really powerful. However, it is based on Swagger3, but it still can't recognize generics and discard it. This is difficult. Although it is feasible to select some versions of Swagger2, the documents are simple, the debugging is difficult, and whoever uses it will suffer. Although springdoc is powerful, the underlying version is too new to recognize generics and cannot fit the actual business system.

After a search, we found the framework of Knife4j. Its interface is even better. The higher version still does not recognize generics. After some adjustment, we finally found a suitable version that can be applied to the current business system.

Finally, visit the Knife4j community and finally understand the reason why generics cannot be recognized. Then upgrade the Knife4j version to experience the latest features.

■ why Swagger3 was not used in the first place

Swagger3 is not used to identify generics, so why is it always emphasized to identify generics?

We usually wrap a layer of general response data class outside the interface return value, including status code, error code and error type. If the business request is successful, we will bring an Object and put it in the data field of the response data Object. The class of this data Object is generally set to Object class.

The following interfaces and ResultDTO classes

   @ResponseBody
   @RequestMapping(value = "api/ai", method = RequestMethod.POST)
   public ResultDTO ai(@RequestBody Ai ai) {
    try {
    		AiResponse aiResponse = aiServer.deal(ai);
           return new ResultDTO(ResultDTO.SUCCESS_CODE, "Successful return", aiResponse);
       } catch (Exception e) {
           logger.error("ai Alarm error", e);
           return new ResultDTO(ResultDTO.ERROR_CODE, "Parameter error", null);
       }
   }

ResultDTO

public class ResultDTO implements Serializable {

    private static final long serialVersionUID = 1L;
    public static final String ERROR_CODE = "-2";
    public static final String SUCCESS_CODE = "0";

    private String errcode;
    private String errmsg;
    private Object data;

    public ResultDTO(String errcode, String errmsg, Object data) {
        super();
        this.errcode = errcode;
        this.errmsg = errmsg;
        this.data = data;
    }

    public static ResultDTO buildResult(String code, String msg) {
        return new ResultDTO(code, msg, null);
    }

    public static ResultDTO buildResult(String code, String msg, Object data) {
        return new ResultDTO(code, msg, data);
    }
    
    public String getErrcode() {
        return errcode;
    }
    public void setErrcode(String errcode) {
        this.errcode = errcode;
    }
    public String getErrmsg() {
        return errmsg;
    }
    public void setErrmsg(String errmsg) {
        this.errmsg = errmsg;
    }
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
}


Then, the Object cannot be parsed by the swagger framework. Therefore, it needs to be transformed into a generic type, and the generic type should be specified in the interface. Only a few modified codes can support the generic type.

    @ResponseBody
    @RequestMapping(value = "api/ai", method = RequestMethod.POST)
    @ApiOperation(value = "ai call the police")
    public ResultDTO<AiResponse> ai(@RequestBody Ai ai) {
     try {
     		AiResponse aiResponse = aiServer.deal(ai);
            return new ResultDTO(ResultDTO.SUCCESS_CODE, "Successful return", aiResponse);
        } catch (Exception e) {
            logger.error("ai Alarm error", e);
            return new ResultDTO(ResultDTO.ERROR_CODE, "Parameter error", null);
        }
    }

Look at the new ResultDTO. Data becomes T. the lower version of swagger2 can automatically parse the content of T, but the higher versions of swagger3 and swagger2 do not support it, which will cause the data content in the interface document response to be empty.

ResultDTO

@ApiModel(description = "Response data encapsulation")
public class ResultDTO<T> implements Serializable {

    private static final long serialVersionUID = 1L;
    
    public static final String ERROR_CODE = "-2";
    public static final String SUCCESS_CODE = "0";
    @ApiModelProperty(value = "Response status code", position = 1)
    private String errcode;
    @ApiModelProperty(value = "Response information", position = 2)
    private String errmsg;
    @ApiModelProperty(value = "Response data", position = 3)
    private T data;
    
    public ResultDTO(String errcode, String errmsg, T data) {
        super();
        this.errcode = errcode;
        this.errmsg = errmsg;
        this.data = data;
    }

    public static<T> ResultDTO<T> buildResult(String code, String msg) {
        return new ResultDTO(code, msg, null);
    }

    public static<T>  ResultDTO<T>  buildResult(String code, String msg, T data) {
        return new ResultDTO(code, msg, data);
    }
	
	public String getErrcode() {
        return errcode;
    }
    public void setErrcode(String errcode) {
        this.errcode = errcode;
    }
    public String getErrmsg() {
        return errmsg;
    }
    public void setErrmsg(String errmsg) {
        this.errmsg = errmsg;
    }
    public T getData() {
        return data;
    }
    public void setData(T data) {
        this.data = data;
    }
}

Therefore, this kind of framework is not as new as possible. Its application is the king. There is no need to follow suit. When swagger2 is too low, we follow swagger3. As a result, we find all kinds of bug s and have to go back and wait for the emergence and solution of the community answer.

■ unrecognized causes of generics

https://xiaoym.gitee.io/knife4j/faq/swagger-des-not-found.html

In later versions such as 2.0.6, due to the upgrade of the basic components of springfox, if developers use hot loaded plug-ins such as JRebel, there will be no class fields. At present, there is no way to solve the conflict between springfox project and JRebel plug-in. It is recommended not to use JRebel

Then I realized that the failure to recognize generic bugs was caused by running JRebel locally and did not affect the deployment. The solution is to use the version below 2.0.6 for local debugging, and deploy the new version to experience the latest features. Or use my scheme. The back-end uses version 2.0.5, and the front-end can use a higher version.

build

■ pom

Constantly stepping on the pit to understand the characteristics of each version. 3.X series is based on OpenAPI3. However, due to the shutdown of springfox, there are many unknown pits, which knife4j the author does not recommend. The large version takes a wait-and-see attitude, and the small version is as up-to-date as possible. At present, it is 2.0.9. 2.0.6 is a turning point. There are many new features that have to be used, but there are also bug s that are incompatible with JRebel.

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>2.0.6</version>
</dependency>

■ SwaggerConfig

There are several explanations in SwaggerConfig
① @ EnableSwagger2 is the old annotation @ EnableSwagger2WebMvc is the annotation after 2.0.6
② AlternateTypeRule defines several system classes, which are nested circularly. For example, class A has class B objects, and class B has class a objects, which will cause an endless loop of automatic resolution response. This rule is created to exclude the resolution of these special classes. In fact, there are only a few.
③ The interface that only parses the ApiOperation annotation is the most reliable.
④ I finally choose 2.0.5 for the back end, which is similar. Change the annotation, comment out the construction method, and comment out the last extension.

import com.fasterxml.classmate.TypeResolver;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
import persistence.Page;
import utils.StringUtils;
import sys.entity.Area;
import sys.entity.Office;
import sys.entity.User;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.AlternateTypeRule;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
//import springfox.documentation.swagger2.annotations.EnableSwagger2;

//@EnableSwagger2
@EnableSwagger2WebMvc
@EnableKnife4j
@Configuration
public class SwaggerConfig {

    private final OpenApiExtensionResolver openApiExtensionResolver;

    @Autowired
    public SwaggerConfig(OpenApiExtensionResolver openApiExtensionResolver) {
        this.openApiExtensionResolver = openApiExtensionResolver;
    }

    @Bean
    public Docket createRestApi() {
        TypeResolver typeResolver = new TypeResolver();
        AlternateTypeRule typeRule1 = new AlternateTypeRule(typeResolver.resolve(User.class),typeResolver.resolve(Object.class));
        AlternateTypeRule typeRule2 = new AlternateTypeRule(typeResolver.resolve(Page.class),typeResolver.resolve(Object.class));
        AlternateTypeRule typeRule3 = new AlternateTypeRule(typeResolver.resolve(Office.class),typeResolver.resolve(Object.class));
        AlternateTypeRule typeRule4 = new AlternateTypeRule(typeResolver.resolve(Area.class),typeResolver.resolve(Object.class));
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .groupName("test")
                .alternateTypeRules(typeRule1, typeRule2, typeRule3, typeRule4)
                .select()
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
//                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
//                .apis(RequestHandlerSelectors.basePackage("com.xxx.modules"))
                .paths((s) -> {
                    if(StringUtils.contains(s, "api") || StringUtils.contains(s, "bbq")) {
                        return true;
                    }
                    return false;
                })
                .build()
                .extensions(openApiExtensionResolver.buildExtensions("test"));
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Service interface document")
                .description("===========")
                .contact(new Contact("bbq", "https://blog.csdn.net/qq_24054301?type=blog", "894944272@qq.com"))
                .version("1.0")
                .build();
    }
}

■ interface example

@ApiImplicitParams({
            @ApiImplicitParam(name = "classification", value = "Statistical unit", dataTypeClass = String.class, required = true),
            @ApiImplicitParam(name = "type", value = "Time type", dataTypeClass = String.class, required = true),
            @ApiImplicitParam(name = "beginDate", value = "Start time", dataTypeClass = String.class, required = false),
            @ApiImplicitParam(name = "endDate", value = "End time", dataTypeClass = String.class, required = false),
            @ApiImplicitParam(name = "isExportExcel", value = "report form excel Export 1: export", dataTypeClass = String.class, required = false),
    })
	public ResultDTO<List<HandleStatisticLog>> statHandleAlarm(@ApiIgnore StatisticLog statisticLog,
                                     @RequestParam(value = "isExportExcel", required = false, defaultValue = "") String isExportExcel,
                                     HttpServletRequest request,
                                     HttpServletResponse response)  {
              			...........
	return ResultDTO.buildResult(ResultDTO.SUCCESS_CODE, "", list);         
}
@ApiModel(description = "Alarm statistics log")
public class StatisticLog extends DataEntity<StatisticLog> {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty("date")
    private Date date;//date
    @ApiModelProperty("total")
    private Integer totalNum=0;//total
    @ApiModelProperty("Number of unprocessed")
    private Integer unHandleNum=0;//Number of unprocessed
    @ApiModelProperty("Unaudited number")
    private Integer unCheckNum=0;//Unaudited number
    @ApiModelProperty("Expired number")
   ....................

■ Properties

# Implementation of knife4j shiro authentication switch
knife4j.authEnable=true
# knife4j switch
knife4j.enable=true
# knife4j personalized configuration domain name prefix switch 2.0.6 and above can be supported
knife4j.setting.enableHost=true
knife4j.setting.enableHostText=http://doman:8080/proxyApi

Effect list

homepage

Classified according to control class

Request parameters

response

Here you can see that the generic content is recognized

Request parameters

A json example will be generated automatically, with comments at the end.

Front end garbled error reporting

Under the two conditions of win and js in the jar package, the request will be garbled and forcibly converted to anni.

If you use Java jar (without - dfile.encoding=utf-8) under win
ui2. Version 0.5 of js will be garbled (utf-8 content is converted to gbk), including postman requests. In version 3.0.2, this seems to be handled. Other methods request utf-8 normally, but the newer Google browser requests are still garbled (utf-8 content is converted to gbk)
Questions like this https://www.oschina.net/question/3639571_2322823
I finally used nginx to run static files. Otherwise, I need to force the code utf-8 to make a record here.
https://www.oschina.net/question/3639571_2322823
https://gitee.com/xiaoym/knife4j/issues/I3Y37F

Therefore, for local debugging, you can use the front-end provided in maven. It is best to use nginx to run static files when deploying online.

Uniform default request prefix

At version 2.0.6 and above, it can be set in the back-end configuration field. Due to the conflict between 2.0.6 and JRebel, the back-end uses version 2.0.5, so this function is not available. However, we can directly compile the front-end to achieve this goal. I choose the front-end project knife4j-vue of version 2.0.6, which has no conflict, and it is interesting to have the afterscript function( https://doc.xiaominfo.com/knife4j/documentation/afterScript.html ).
Source code found
constants.js
Line 50 has defaultSettings

defaultSettings: {
    enableSwaggerModels:true,//Whether to display the SwaggerModel function in the interface
    enableDocumentManage:true,//Display the "document management" function in the interface
    showApiUrl: false, //Interface api address is not displayed
    showTagStatus: false, //The grouping tag displays the description attribute. For the @ Api annotation, there is no tag attribute value
    enableSwaggerBootstrapUi: false, //Enable swaggerBootstrapUi enhancement
    treeExplain: true,
    enableDynamicParameter: true, //Turn on dynamic parameters
    enableFilterMultipartApis: false, //For the interface request type of RequestMapping, if the parameter type is not specified, seven types of interface address parameters will be displayed by default. If this configuration is enabled, one Post type interface address will be displayed by default
    enableFilterMultipartApiMethodType: "POST", //Default save type
    enableRequestCache: true, //Whether to enable request parameter cache
    enableCacheOpenApiTable: false, //Whether to enable caching of open api documents
    enableHost:true,//Enable Host
    enableHostText:"http://doman/proxyApi ", / / text after Host is enabled
    language: "zh-CN" //Default language version
  },

Then compile again with yarn.

Authority authentication scheme

If it is built online, it must need a set of authentication system.
You can see this by looking at the knife4j documentation,

3.5.2 access page weighting control
https://doc.xiaominfo.com/knife4j/documentation/accessControl.html#_3-5-2-%E8%AE%BF%E9%97%AE%E9%A1%B5%E9%9D%A2%E5%8A%A0%E6%9D%83%E6%8E%A7%E5%88%B6
Whether it's the official swagger UI HTML or doc HTML. At present, the interface access can access the interface document without permission. Many friends asked me if I could provide a login interface function. Developers enter the user name and password to control the access to the interface. Only those who know the user name and password can access this document
The user concept is required for login page control, so this function has not been provided for a long time
For Swagger's resource interface, Knife4j provides a simple Basic authentication function

It comes with a simple basic to prevent gentlemen from villains. I have two schemes. One is to control the ip access white list through nginx, and the other is to authenticate in combination with the token of the business system. I use the second scheme.

Scheme 1 Nginx dynamic ip whitelist

① geo module whitelist configuration

The geo module is at the same level as the server, and the ip is configured in an independent conf file

geo $remote_addr $passiplist {
	default 0;
	#100.56.2.0/24 1;
	include .../conf/companyIp.conf;
	include .../conf/whiteIp.conf;
}

② Agent plus judge interception

Add this sentence to the proxy to be intercepted in the server

if ( $passiplist = 0 ) {
	return 403;
}

③ Set the ip white list of the company's development environment

The company's development environment must ensure the white list in real time. If the company's ip cannot be fixed, an interface can be created to constantly update the ip
redis caches the ip address of the request source

/**
 * Set company ip
 * @return
 */
@ResponseBody
@RequestMapping(value = ".../setCompanyIp", method = RequestMethod.POST)
public String setCompanyIp(String auth, HttpServletRequest request) {
    try {
        if(auth != null && auth.equals(Global.getConfig("nginx.ip.auth"))) {
            return JedisUtils.set("ip:company", StringUtils.getRemoteAddr(request), 0);
        }
        return "no auth";
    } catch (Exception e) {
        logger.error("Set up company ip fail", e);
        return "Set up company ip fail";
    }
}

bat requests to set up the ip interface of the company's development environment and create scheduled task polling.

rem ****** Set up company ip ********
@echo off
	C:/.../curl.exe -X POST -d "auth=..." https://.../setCompanyIp -k
@echo on

④ Set whitelist ip

ip addresses outside the development environment can use another interface to set the whitelist.
This interface should be called safely without leakage. Here, only class basic authentication is used.

/**
 * Set whitelist ip
 * @return
 */
@ResponseBody
@RequestMapping(value = ".../addWhiteIp", method = RequestMethod.POST)
public String addWhiteIp(String auth, HttpServletRequest request) {
    Jedis jedis = null;
    try {
        if(auth != null && auth.equals(Global.getConfig("nginx.ip.auth"))) {
            String ip = StringUtils.getRemoteAddr(request);
            jedis = JedisUtils.getResource();
            jedis.hset("ip:white", ip, "" + System.currentTimeMillis());
            return "success";
        }
        return "no auth";
    } catch (Exception e) {
        logger.error("Set white list ip fail", e);
        return "Set white list ip fail";
    } finally {
        JedisUtils.returnResource(jedis);
    }
}

⑤ Get whitelist ip

The two interfaces return white list ip configuration strings. One is the fixed company ip and the other is the ip list
And update the cache data of the ip list, such as culling for more than 2 hours.

/**
 * Get company ip
 * @return
 */
@ResponseBody
@RequestMapping(value = ".../getCompanyIp", method = RequestMethod.GET)
public String getCompanyIp() {
    try {
        String ip = JedisUtils.get("ip:company");
        ip = StringUtils.isNotBlank(ip) ? ip + " 1;" : "";
        return ip;
    } catch (Exception e) {
        logger.error("Failed to get whitelist ip", e);
        return "";
    }
}

/**
 * Get whitelist ip
 * @return
 */
@ResponseBody
@RequestMapping(value = ".../getWhiteIp", method = RequestMethod.GET)
public String getWhiteIp() {
    Jedis jedis = null;
    try {
        jedis = JedisUtils.getResource();
        Map<String, String> map = jedis.hgetAll("ip:white");
        Iterator iter = map.entrySet().iterator();
        String ip = "";
        while (iter.hasNext()){
            Map.Entry e = (Map.Entry) iter.next();
            // 60000 1 minute 21600000 6 hours
            if(System.currentTimeMillis() - Long.parseLong((String) e.getValue()) > 21600000) {
                jedis.hdel("ip:white",(String) e.getKey());
            } else {
                ip += e.getKey() + " 1;\n";
            }
        }
        return ip;
    } catch (Exception e) {
        logger.error("Get whitelist ip fail", e);
        return "";
    } finally {
        JedisUtils.returnResource(jedis);
    }
}

⑥ Server write
bat as an example, get the ip configuration, write it to the temporary file, compare it with the ip configuration file, replace the file and restart nginx gracefully if there is any difference
The two bat s are similar, and then scheduled task polling can be used.

rem ****** Get company ip ********
@echo off
	...\curl.exe -X GET https://.../getCompanyIp -k > ...\companyIp_tmp.conf
	
	fc ...\companyIp.conf ...\companyIp_tmp.conf >nul
	
	if errorlevel 1 (
		echo File change, overwriting...
		echo F | xcopy /y "...\companyIp_tmp.conf" "...\companyIp.conf"	
		del ...\companyIp_tmp.conf
		rem Delay 3 s
		ping -n 3 1271	
		d:
		cd ...\nginx
		nginx -s reload
		rem pause
		exit
	)
	del ...\companyIp_tmp.conf
	echo There is no change in the document, no operation...
rem pause
@echo on

Scheme 2 combines the token of the business system

① Set access permissions for Swagger related interfaces

First, modify the shiro configuration and set the api:doc:use permission identifier to access.

@Bean
public String shiroFilterChainDefinitions() {
	StringBuffer shiroFilterChainDefinitions = new StringBuffer();
	............
	shiroFilterChainDefinitions.append(adminPath + "/**/noanth/** = anon \n");
	shiroFilterChainDefinitions.append(adminPath + "/**/api/** = apifilter \n");
	shiroFilterChainDefinitions.append(adminPath + "/**/internalauth/** = internalfilter \n");
	shiroFilterChainDefinitions.append(adminPath + "/login = authc \n");
	shiroFilterChainDefinitions.append(adminPath + "/** = user \n");
	if(knife4jEnable) {
    	shiroFilterChainDefinitions.append("/swagger-resources/** = apifilter,perms[api:doc:use] \n");
        shiroFilterChainDefinitions.append("/v2/api-docs = apifilter,perms[api:doc:use] \n");
	}
	return shiroFilterChainDefinitions.toString();
}

② Setting access permissions for Nginx static files

The two interfaces proxy to java for authentication, and the js of vue does not do static authentication, Doc HTML can do an authentication, using the internal module.
For nginx internal authentication, please refer to
The live video authentication is combined with the token or session of the service system
https://blog.csdn.net/qq_24054301/article/details/119247801

# Internal authentication
		location /internalfilter {
			internal;
			proxy_set_header Host $host;
			proxy_pass_request_body off;
			proxy_set_header Content-Length "";
			if ( $request_uri ~ ((\?)([\S\s]*))  ){
				set  $parameter $1;
			}
			proxy_pass http://doman/.../internalauth/advanced/api:doc:use$parameter;
		}
		location /doc.html {
			auth_request /internalfilter;
			error_page 401 = @error401;
			error_page 403 = @error403;
            alias  ...knife4j\\doc.html;
        }
		location /webjars{
			alias  ...knife4j\\webjars;
			#proxy_pass   http://doman/webjars/;
		}
		location /swagger-resources/{
			proxy_pass   http://doman/swagger-resources/;
		}
		location /v2/api-docs{
			proxy_pass   http://doman/v2/api-docs;
		}
		location @error401 {
			return 401 $query_string"not logged on";
		}
		location @error403 {
			return 403 "No access";
		}

③ token parameter transmission scheme

All the above authenticated are intercepted. The next step is how to make knife4j front-end bring a token when requesting.
Method 1 uses homologous cookie s
This is the first mock exam I use now, because the knife4j is always deployed in the front end of the pc and the pc front end is deployed under the same source. Any interface will automatically bring the pc terminal business system cookie, that is to say, this knife4j has become a module of the pc terminal business system.

As shown in the figure, my business system logs in to the number with permission. Click the first item in the list to pop up the interface document. Because there is a cookie, the access is successful.

Log out, switch to an account without operation permission, and refresh doc HTML, jump to no access right now.

It is also worth mentioning that when debugging the business interface in the knife4j interface document, cookies will also be brought automatically, that is, after authentication, you can debug without adding a global token. It is often convenient and safe. When you need to debug other accounts, you can overwrite them by passing a token. (the priority of the parameter token is greater than that in the request header, and greater than that in the cookie. This priority is set by shiro.)

Method 2: embed the third-party page into iframe

An html with iframe obtains the token from the cookie or url or request header, and then uniformly adds the XMLHttpRequest request header token
visit http://doman/setToken.html?url=http://doman/doc.html

setToken.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>XHR Unified increase of request header token</title>
<style>
html,body{
 height:100%;
}
</style>
<script src="//domain/jquery-1.9.1.min.js"></script>
</head>
<body >
<iframe id='iframe' width="100%" height="100%"  frameborder="0"></iframe>
</body>
<script>
var url = getQueryVariable("url")
var token = getCookie("token")
var _send = window.XMLHttpRequest.prototype.send

window.XMLHttpRequest.prototype.send = function() {
	this.setRequestHeader('token', token);
    return _send.apply(this, arguments)
}

var bbq=document.getElementById("iframe");
bbq.contentWindow.document.write("<script>var _send = window.XMLHttpRequest.prototype.send;window.XMLHttpRequest.prototype.send = function(){this.setRequestHeader('token', '" + token + "');return _send.apply(this, arguments);}<\/script>")

$.ajax({
    url: url,
    success: function (data) {
		var iframeDoc = bbq.contentWindow.document;
		iframeDoc.open('text/html', 'replace');
        iframeDoc.write(data);
        iframeDoc.close();
		document.title=iframeDoc.title;
    }
})
function getCookie(name) {
        var strCookie = document.cookie;
        var arrCookie = strCookie.split(';');
        for(var i = 0; i < arrCookie.length; i++) {
            var arr = arrCookie[i].split('=');
            if(arr[0].replace(/(^\s*)|(\s*$)/g, "") == name) {
                return arr[1];
            }
        }
        return '';
}
function getQueryVariable(variable){
       var query = window.location.search.substring(1);
       var vars = query.split("&");
       for (var i=0;i<vars.length;i++) {
               var pair = vars[i].split("=");
               if(pair[0] == variable){return pair[1];}
       }
       return(false);
}
</script>
</html>

Method 3 download the source code and modify it
Customized development of download source code is time-consuming and laborious, but it is also a scheme.

last

This system has been quite stable, and it is supported by a big man like the author of knife4j in China. It often takes time to solve issues without asking for return. It is very worth trying to use.

Keywords: Java Nginx Shiro swagger2

Added by bladx on Fri, 07 Jan 2022 21:32:04 +0200