View Javadoc
1   package cn.home1.oss.lib.security.swagger;
2   
3   import static cn.home1.oss.boot.autoconfigure.AppType.RESOURCE;
4   import static com.google.common.base.Predicates.or;
5   import static com.google.common.collect.Lists.newArrayList;
6   import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
7   import static org.springframework.http.MediaType.TEXT_HTML_VALUE;
8   import static org.springframework.web.bind.annotation.RequestMethod.GET;
9   import static org.springframework.web.bind.annotation.RequestMethod.POST;
10  import static springfox.documentation.builders.PathSelectors.regex;
11  
12  import com.google.common.base.Optional;
13  import com.google.common.base.Predicate;
14  import com.google.common.collect.Sets;
15  
16  import cn.home1.oss.boot.autoconfigure.AppProperties;
17  import cn.home1.oss.boot.autoconfigure.AppSecurityProperties;
18  import cn.home1.oss.lib.common.crypto.KeyExpression;
19  import cn.home1.oss.lib.errorhandle.api.ResolvedError;
20  import cn.home1.oss.lib.security.api.GenericUser;
21  import cn.home1.oss.lib.security.internal.rest.RestfulLoginPublicKeyFilter;
22  import cn.home1.oss.lib.swagger.ManualRequestHandler;
23  import cn.home1.oss.lib.swagger.SwaggerUtils;
24  import cn.home1.oss.lib.swagger.model.ApiOperationInfo;
25  import cn.home1.oss.lib.swagger.model.ApiRequest;
26  
27  import com.fasterxml.classmate.TypeResolver;
28  
29  import org.springframework.beans.factory.annotation.Autowired;
30  import org.springframework.core.annotation.Order;
31  import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
32  import org.springframework.security.web.authentication.logout.LogoutFilter;
33  import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
34  import org.springframework.web.bind.annotation.RequestMethod;
35  import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
36  import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition;
37  import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
38  import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
39  import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
40  import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
41  
42  import springfox.documentation.RequestHandler;
43  import springfox.documentation.builders.ResponseMessageBuilder;
44  import springfox.documentation.schema.DefaultGenericTypeNamingStrategy;
45  import springfox.documentation.schema.ModelRef;
46  import springfox.documentation.service.ResponseMessage;
47  import springfox.documentation.spi.DocumentationType;
48  import springfox.documentation.spi.service.DocumentationPlugin;
49  import springfox.documentation.spi.service.contexts.DocumentationContext;
50  import springfox.documentation.spi.service.contexts.DocumentationContextBuilder;
51  import springfox.documentation.swagger.common.SwaggerPluginSupport;
52  
53  import java.util.EnumMap;
54  import java.util.List;
55  import java.util.Map;
56  
57  /**
58   * equivalent to @Bean public Docket securityDocket() {... }.
59   */
60  @Order(value = SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER)
61  public class SecurityApiDocumentationPlugin implements DocumentationPlugin {
62  
63    @Autowired
64    private TypeResolver typeResolver;
65    @Autowired
66    private AppProperties appProperties;
67  
68    @Override
69    public DocumentationContext configure(final DocumentationContextBuilder documentationContextBuilder) {
70      documentationContextBuilder
71        .apiInfo(SwaggerUtils.apiInfo("oss-lib-security", "oss-lib-security's security endpoints"))
72        .groupName("security")
73        .pathMapping(Optional.absent())
74        .genericsNaming(new DefaultGenericTypeNamingStrategy())
75        .requestHandlers(this.requestHandlers())
76        .additionalModels(Sets.newHashSet(this.typeResolver.resolve(ResolvedError.class)));
77      return documentationContextBuilder.build();
78    }
79  
80    private List<RequestHandler> requestHandlers() {
81      final AppSecurityProperties appSecurityProperties = this.appProperties.getSecurity();
82      // TODO add oauth endpoints
83      // TODO conditional filter
84      // finish how to add description
85      final List<RequestHandler> result = newArrayList();
86      if (appSecurityProperties.getEnabled() && this.appProperties.getType() != RESOURCE) {
87        result.add( // formAuth loginPage
88          ManualRequestHandler.requestHandlerBuilder()
89            .consumes(new ConsumesRequestCondition())
90            .declaringClass(DefaultLoginPageGeneratingFilter.class)
91            .groupName(DefaultLoginPageGeneratingFilter.class.getSimpleName())
92            .headers(new HeadersRequestCondition())
93            .parameters(newArrayList())
94            .params(new ParamsRequestCondition())
95            .patternsCondition(new PatternsRequestCondition(appSecurityProperties.getLoginPage()))
96            .produces(new ProducesRequestCondition(TEXT_HTML_VALUE))
97            .returnType(this.typeResolver.resolve(String.class))
98            .supportedMethods(new RequestMethodsRequestCondition(GET))
99            .build()
100       );
101       result.add( // formAuth loginProcessingUrl
102         ManualRequestHandler.requestHandlerBuilder()
103           .consumes(new ConsumesRequestCondition())
104           .declaringClass(UsernamePasswordAuthenticationFilter.class)
105           .groupName(UsernamePasswordAuthenticationFilter.class.getSimpleName())
106           .headers(new HeadersRequestCondition())
107           .parameters(newArrayList( //
108           //  new ResolvedMethodParameter("username", null, TYPE_RESOLVER.resolve(String.class))
109           ))
110           .apiOperationInfo(ApiOperationInfo.builder()
111             .notes("用户登录请求处理")
112             .apiRequest(ApiRequest.builder()
113               .parameters(newArrayList())
114               .build()
115               .addParameter("username", "enter your user name", true)
116               .addParameter("password", "enter your password", true)
117               .addParameter("_csrf", "Cross-site request forgery", false)
118               .addParameter("remember-me", "So remember me?", false))
119             .build()
120           ).patternsCondition(new PatternsRequestCondition(appSecurityProperties.getLoginProcessingUrl()))
121           .produces(new ProducesRequestCondition(APPLICATION_JSON_VALUE))
122           .returnType(this.typeResolver.resolve(GenericUser.class))
123           .supportedMethods(new RequestMethodsRequestCondition(POST))
124           .build()
125 
126       );
127       result.add( // formAuth loginPublicKeyUrl
128         ManualRequestHandler.requestHandlerBuilder()
129           .consumes(new ConsumesRequestCondition())
130           .declaringClass(RestfulLoginPublicKeyFilter.class)
131           .groupName(RestfulLoginPublicKeyFilter.class.getSimpleName())
132           .headers(new HeadersRequestCondition())
133           .parameters(newArrayList())
134           .params(new ParamsRequestCondition())
135           .patternsCondition(new PatternsRequestCondition(appSecurityProperties.getLoginPublicKeyUrl()))
136           .produces(new ProducesRequestCondition(APPLICATION_JSON_VALUE))
137           .returnType(this.typeResolver.resolve(KeyExpression.class))
138           .supportedMethods(new RequestMethodsRequestCondition(GET))
139           .build()
140       );
141       result.add( // formAuth logoutUrl
142         ManualRequestHandler.requestHandlerBuilder()
143           .consumes(new ConsumesRequestCondition())
144           .declaringClass(LogoutFilter.class)
145           .groupName(LogoutFilter.class.getSimpleName())
146           .headers(new HeadersRequestCondition())
147           .parameters(newArrayList())
148           .params(new ParamsRequestCondition())
149           .patternsCondition(new PatternsRequestCondition(appSecurityProperties.getLogoutUrl()))
150           .produces(new ProducesRequestCondition(APPLICATION_JSON_VALUE))
151           .returnType(this.typeResolver.resolve(Void.class))
152           .supportedMethods(new RequestMethodsRequestCondition(POST))
153           .build()
154       );
155     }
156     return result;
157   }
158 
159   @Override
160   public DocumentationType getDocumentationType() {
161     return DocumentationType.SWAGGER_2;
162   }
163 
164   @Override
165   public String getGroupName() {
166     return "security";
167   }
168 
169   @Override
170   public boolean isEnabled() {
171     return true;
172   }
173 
174   @Override
175   public boolean supports(final DocumentationType delimiter) {
176     return DocumentationType.SWAGGER_2.equals(delimiter);
177   }
178 
179   /**
180    * @return securityPaths
181    * @deprecated remove this later.
182    */
183   @Deprecated
184   public Predicate<String> securityPaths() {
185     final String securityBasePath = this.appProperties.getSecurity().getBasePath();
186     return or(regex(securityBasePath + "/.*"), regex("/oauth/.*"));
187   }
188 
189   private static List<ResponseMessage> responseMessages() {
190     final List<ResponseMessage> responseMessages = newArrayList();
191     responseMessages.add(new ResponseMessageBuilder()
192       .code(400).message("400diy")
193       .responseModel(new ModelRef("cn.home1.oss.lib.errorhandle.api.ResolvedError"))
194       .build());
195     return responseMessages;
196   }
197 
198   /**
199    * @return responseMessages
200    * @deprecated remove this later.
201    */
202   @Deprecated
203   static Map<RequestMethod, List<ResponseMessage>> additionalResponseMessages() {
204     final Map<RequestMethod, List<ResponseMessage>> additionalResponseMessages = new EnumMap<>(RequestMethod.class);
205     final List<ResponseMessage> responseMessages = responseMessages();
206     for (final RequestMethod requestMethod : RequestMethod.values()) {
207       additionalResponseMessages.put(requestMethod, responseMessages);
208     }
209     return additionalResponseMessages;
210   }
211 }