一.Swagger2
(一)概述
在团队开发中,一个好的 API
文档不但可以减少大量的沟通成本,还可以帮助一位新人快速上手业务。传统的做法是由开发人员创建一份 RESTful API
文档来记录所有的接口细节,并在程序员之间代代相传。
这种做法存在以下几个问题:
-
API
接口众多,细节复杂,需要考虑不同的HTTP
请求类型、HTTP
头部信息、HTTP
请求内容等,想要高质量的完成这份文档需要耗费大量的精力。 - 难以维护。随着需求的变更和项目的优化、推进,接口的细节在不断地演变,接口描述文档也需要同步修订,可是文档和代码处于两个不同的媒介,除非有严格的管理机制,否则很容易出现文档、接口不一致的情况。
Swagger2
的出现就是为了从根本上解决上述问题。它作为一个规范和完整的框架,可以用于生成、描述、调用和可视化 RESTful
风格的 Web
服务:接口文档在线自动生成,文档随接口变动实时更新,节省维护成本,支持在线接口测试,不依赖第三方工具。
(二)Swagger 优点
- 代码变,文档变。只需要少量的注解,
Swagger
就可以根据代码自动生成API
文档,很好的保证了文档的时效性。 - 跨语言性,支持 40 多种语言。
-
Swagger UI
呈现出来的是一份可交互式的API
文档,我们可以直接在文档页面尝试API
的调用,省去了准备复杂的调用参数的过程。 - 还可以将文档规范导入相关的工具(例如
Postman
、SoapUI
), 这些工具将会为我们自动地创建自动化测试。
二.Swagger2配置
(一)Security放行端口
package com.cxy.server.config.security;
import com.cxy.server.config.security.component.JwtAuthenticationFilter;
import com.cxy.server.config.security.component.RestAuthorizationEntryPoint;
import com.cxy.server.config.security.component.RestfulAccessDeniedHandler;
import com.cxy.server.pojo.Admin;
import com.cxy.server.service.IAdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @author 陈鑫元
* @description Security 配置类
* @date 2021-05-23 10:38
* @since 1.0.0
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private IAdminService adminService;
@Autowired
private RestAuthorizationEntryPoint restAuthorizationEntryPoint; // 未登录 token 失效时自定义处理结果
@Autowired
private RestfulAccessDeniedHandler restfulAccessDeniedHandler; // 无权访问时自定义处理结果
// 1、重写 UserDetailsService,用我们自己写的业务逻辑
@Override
@Bean
public UserDetailsService userDetailsService() {
return username -> {
Admin admin = adminService.getAdminByUserName(username);
// 如果admin不为空,返回admin
if (null != admin) {
return admin;
}
throw new UsernameNotFoundException("用户名或密码不正确");
};
}
// 2、让 Security 走我们重写的 UserDetailsService ,通过 getAdminByUserName 获取用户名
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
}
// 3、密码加解密对象
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 4、SpringSecurity 配置
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 使用 JWT , 不需要 csrf
http.csrf()
.disable()
// 基于 token,不需要 session
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 所有请求都要求认证
.anyRequest()
.authenticated()
.and()
// 禁用缓存
.headers()
.cacheControl();
// 添加 jwt 登录授权过滤器
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
// 添加自定义未授权和未登录结果返回
http.exceptionHandling()
.authenticationEntryPoint(restAuthorizationEntryPoint)
.accessDeniedHandler(restfulAccessDeniedHandler);
}
// 5、JWT 登录授权过滤器
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
// 6、放行路径(不走拦截链)
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/websocket/**",
"/login/**",
"/register/**",
"/logout/**",
"/css/**",
"/js/**",
"/img/**",
"/fonts/**",
"favicon.ico",
"/doc.html", // 放行 swagger 资源
"/webjars/**", // 放行 swagger 资源
"/swagger-resources/**", // 放行 swagger 资源
"/v2/api-docs/**", // 放行 swagger 资源
"/captcha", // 验证码接口
"/ws/**"
);
}
}
(二)放行端口
修改:yeb/yeb-server/src/main/java/com/cxy/server/config/security/SecurityConfig.java
文件:
package com.cxy.server.config.security;
import com.cxy.server.config.security.component.JwtAuthenticationFilter;
import com.cxy.server.config.security.component.RestAuthorizationEntryPoint;
import com.cxy.server.config.security.component.RestfulAccessDeniedHandler;
import com.cxy.server.pojo.Admin;
import com.cxy.server.service.IAdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @author 陈鑫元
* @description Security 配置类
* @date 2021-05-23 10:38
* @since 1.0.0
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private IAdminService adminService;
@Autowired
private RestAuthorizationEntryPoint restAuthorizationEntryPoint; // 未登录 token 失效时自定义处理结果
@Autowired
private RestfulAccessDeniedHandler restfulAccessDeniedHandler; // 无权访问时自定义处理结果
// 1、重写 UserDetailsService,用我们自己写的业务逻辑
@Override
@Bean
public UserDetailsService userDetailsService() {
return username -> {
Admin admin = adminService.getAdminByUserName(username);
// 如果admin不为空,返回admin
if (null != admin) {
return admin;
}
throw new UsernameNotFoundException("用户名或密码不正确");
};
}
// 2、让 Security 走我们重写的 UserDetailsService ,通过 getAdminByUserName 获取用户名
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
}
// 3、密码加解密对象
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 4、SpringSecurity 配置
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 使用 JWT , 不需要 csrf
http.csrf()
.disable()
// 基于 token,不需要 session
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 所有请求都要求认证
.anyRequest()
.authenticated()
.and()
// 禁用缓存
.headers()
.cacheControl();
// 添加 jwt 登录授权过滤器
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
// 添加自定义未授权和未登录结果返回
http.exceptionHandling()
.authenticationEntryPoint(restAuthorizationEntryPoint)
.accessDeniedHandler(restfulAccessDeniedHandler);
}
// 5、JWT 登录授权过滤器
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
// 6、放行路径(不走拦截链)
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/websocket/**",
"/login/**",
"/register/**",
"/logout/**",
"/css/**",
"/js/**",
"/img/**",
"/fonts/**",
"favicon.ico",
"/doc.html", // 放行 swagger 资源
"/webjars/**", // 放行 swagger 资源
"/swagger-resources/**", // 放行 swagger 资源
"/v2/api-docs/**", // 放行 swagger 资源
"/captcha", // 验证码接口
"/ws/**"
);
}
}
(三)配置Swagger
新建:yeb/yeb-server/src/main/java/com/cxy/server/config/SwaggerConfig.java
文件:
package com.cxy.server.config;
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.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.ArrayList;
import java.util.List;
/**
* @author 陈鑫元
* @description Swagger2 配置类
* @date 2021-05-23 17:16
* @since 1.0.0
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
/**
* 创建Swagger2文档
*
* @return
*/
@Bean
public Docket createAPI() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
//指定哪个包下面生成接口文档
.apis(RequestHandlerSelectors.basePackage("com.cxy.server.controller"))
.paths(PathSelectors.any())
.build()
.securityContexts(securityContexts()) // 设置需要登录认证的路径
.securitySchemes(securitySchemes()); // 配置请求头信息
}
/**
* 文档基本信息
*
* @return
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.version("1.0")
.title("云E办接口文档")
.description("云E办接口文档")
.contact(new Contact("陈鑫元", "localhost:8081/doc.html", "[email protected]"))
.build();
}
// 1. 设置请求头信息,解决访问接口登录问题
private List securitySchemes() {
// 设置请求头信息
List result = new ArrayList<>();
// 参数:api key 名字 { 准备的 key 名字,value 请求头 }
result.add(new ApiKey("Authorization", "Authorization", "header"));
return result;
}
// 2. 设置需要登录认证的路径,解决访问接口登录问题
private List securityContexts() {
// 设置需要登录认证的路径
List result = new ArrayList<>();
result.add(getContextByPath());
return result;
}
// 3. 安全上下文,解决访问接口登录问题,
private SecurityContext getContextByPath() {
return SecurityContext.builder()
.securityReferences(defaultAuth())
// 在Swagger2的securityContexts中通过正则表达式,设置需要使用参数的接口
.forPaths(PathSelectors.regex("/register/.*"))
.build();
}
// 4. 设置默认授权 - 解决访问接口登录问题
private List defaultAuth() {
List result = new ArrayList<>();
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] scopes = new AuthorizationScope[1];
scopes[0] = authorizationScope;
result.add(new SecurityReference("Authorization", scopes));
return result;
}
}