جاوا و تکنولوژی های آن

java programming language

در این وبلاگ به بررسی نکات موجود در جاوا و تکنولوژی های آن می پردازیم

طبقه بندی موضوعی


امروزه front-end و back-end اجزای یک web application را تشکیل میدهند ولی در مورد سرویس ها API بخش Back-end و کلاینت ها و اجزای دیگری که از API استفاده میکنند بخش front را شامل میشوند.

در این شرایط داشتن داکیومنت و توضیحات فنی دقیق کامل و روان برای API ها یک شرط الزام آور برای استفاده کلاینت ها میباشد تا بتوانند از API آنگونه که باید، استفاده کنند

همچنین توسعه API طی زمان دستخوش تغییراتی میشود که این تغییرات نیاز به داکیومنت و اطلاع رسانی به کلاینت ها نیز دارد و همین امر به دشواری های توسعه API می افزاید

در این مبحث با استفاده از Springfox که استاندارد و تعاریف swagger 2 را پیاده سازی کرده است Spring REST Service را توسعه خواهیم داد و با ویژگی هایی که به سرویس ما اضافه میکند آشنا خواهیم شد


قبل از هر چیز اگر با swagger آشنایی قبلی ندارید بهتر است نگاهی به وب سایت https://swagger.io و توضیحات آن بیاندازید


اضافه کردن کتابخانه Springfox به پروژه :


<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>



ادغام پروژه و swagger :


تنظیمات Swagger همراه با Spring Boot :


ابتدا باید swagger2 را enable کنیم و برای ست کردن تنظیمات از Docket Bean استفاده کنیم :

@Configuration
@EnableSwagger2
public class SpringFoxConfig {                                    
    @Bean
    public Docket api() { 
        return new Docket(DocumentationType.SWAGGER_2)  
          .select()                                  
          .apis(RequestHandlerSelectors.any())              
          .paths(PathSelectors.any())                          
          .build();                                           
    }
}


متد Select روی ابجکت Docket یک ابجکت از نوع ApiSelectorBuilder میدهد که میتوانیم endpoint هایمان را روی آن مشخص کنیم 

همین تنظیمات کافی است که پروژه Spring Boot ما با Swagger ادغام شود اگر از Spring Boot استفاده نکنیم نیاز است برخی تنظیمات دیگری را لحاظ کنیم 


تنظیمات Swagger بدون Spring Boot :


بدون Spring Boot ویژگی تنظیمات اتوماتیک Spring Boot را نخواهیم داشت و لازم است یکسری تنظیمات بصورت دستی ست شوند 

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("swagger-ui.html")
      .addResourceLocations("classpath:/META-INF/resources/");
 
    registry.addResourceHandler("/webjars/**")
      .addResourceLocations("classpath:/META-INF/resources/webjars/");
}


swagger-ui در بر گیرنده مجموعه ای از resource هایی است که باید در تنظیمات کلاسی که از WebMvcConfigurerAdapter ارث بری کرده است ست شوند آن کلاس باید EnableWebMvc@ نیز باشد


تست اجرای Springfox :


بعد از راه اندازی پروزه کافی است به آدرس :

http://localhost:8080/spring-security-rest/api/v2/api-docs

برویم در پاسخ یک JSON که خیلی محتوای آن قابل فهم نیست دریافت میشود که برای خواندن محتوا میتوان از Swagger UI استفاده کرد 



Swagger UI : 


ابزاری است برای تعامل کاربر با swagger تا دسترسی به داکیومنت های API راحتتر صورت پذیرد 


ابتدا باید کتابخانه آنرا اضافه کنیم :

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>

و بعد میتوان صفحه مورد نظر را در آدرسی مشابه زیر مشاهده کرد :

http://localhost:8080/your-app-root/swagger-ui.html

مشاهده داکیومنت های تولیدی توسط Swagger :


در خروجی swagger میتوان لیست کنترلر هایی که در برنامه تعریف کردیم را ببینیم همینطور با انتخاب کنترلرهای لیست شده میتوان جزییات بیشتری از آنرا دید مانند Http Method های معتبر، کد وضعیت response ارسالی، content-type و لیست پارامترها دریافتی.

این امکان هم فراهم است که تک تک متد ها را در صفحه UI ایجاد شده امتحان کنیم 


یکی از توانایی های مهم synchronized شدن swagger با کد اصلی برنامه است بدین ترتیب که اگر کنترلر جدیدی را به برنامه اضافه کنیم تنها با refresh کردن صفحه swagger document میتوان آن کنترلر جدید را نیز مشاهده کرد




Springfox و Spring Data :


Springfox با استفاده از کتابخانه springfox-data-rest میتواند از Spring Data حمایت میکند. در حال حاضر ورژن 2.9.2 قابلیت ترکیب شدن با Spring Boot و Spring Data Rest API را ندارد از این رو از ورژن 3 snapshot استفاده میکنیم :

ابتدا repository مربوط به snapshot را اضافه میکنیم :

<repositories>
    <repository>
        <id>jcenter-snapshots</id>
        <name>jcenter</name>
        <url>http://oss.jfrog.org/artifactory/oss-snapshot-local/</url>
    </repository>
</repositories>

و بعد کتابخانه را به pom اضافه مکنیم :

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>3.0.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-data-rest</artifactId>
    <version>3.0.0-SNAPSHOT</version>
</dependency>


برای کار با Spring Data یک Entity با نام User میسازیم :

@Entity
public class User {
    @Id
    private Long id;
    private String firstName;
    private int age;
    private String email;
 
    // getters and setters
}


و یک CrudRepository اضافه میکنیم :

@Repository
public interface UserRepository extends CrudRepository<User, Long> {
}


حالا نیاز است که تنظیمات SpringDataRestConfiguration را وارد کلاس تنظیمات اضلی کنیم :

@EnableSwagger2WebMvc
@Import(SpringDataRestConfiguration.class)
public class SpringFoxConfig {
    //...
}

* دقت کنید که در ورژن 3 EnableSwagger2WebMvc@ جایگزین EnableSwagger2@ شده است 


حال اگر application را restart کنیم خواهیم دید که بخش تعاریف مربوط به Spring Data Rest API نیز قابل مشاهده است :


همانطور که در تصویر بالا قابل مشاهد است Swagger خصوصیات و مشخصات User Entity را همراه با Http Method ها تولید کرده است 



Bean Validation : 


با استفاده از springfox-bean-validators میتوان از bean validation هم در swagger حمایت کرد 

ابتدا کتابخانه آنرا به pom اضافه میکنیم :

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-bean-validators</artifactId>
    <version>2.9.2</version>
</dependency>


کلاس User entity را تصور کنید که یکسری Validation annotation را استفاده کرده است :

@Entity
public class User {
    //...
     
    @NotNull(message = "First Name cannot be null")
    private String firstName;
     
    @Min(value = 15, message = "Age should not be less than 15")
    @Max(value = 65, message = "Age should not be greater than 65")
    private int age;
}


و برای تنظیمات اتوماتیک کافی است کلاس تنظیمات BeanValidatorPluginsConfiguration را به کلاس تنظیمات اصلی پروژه اضافه کنیم :

@EnableSwagger2
@Import(BeanValidatorPluginsConfiguration.class)
public class SpringFoxConfig {
    //...
}


حالا با restart کردن application میتوانیم داکیومنت تولید شده را مشاهده کنیم :




Plugin برای Springfox:


برای اضافه کردن ویژگی های بیشتر میتوانیم با استفاده از ایجاد plugin در Springfox مدل ها و خصوصیاتی غنی تری را بسازیم. در Springfox با استفاده از ماژول SPI اینترفیس هایی مانند  ModelBuilderPlugin, ModelPropertyBuilderPlugin و ApiListingBuilderPlugin خواهیم داشت که کمک میکند plugin مورد نظر را بسازیم

برای نمونه میخواهیم پلاگینی بسازیم که فیلد email از کلاس User را غنی تر کنیم برای ساخت plugin از اینترفیس  ModelPropertyBuilderPlugin  استفاده میکنیم و  pattern و example این فیلد را تعریف میکنیم


برای استفاده از  ModelPropertyBuilderPlugin  یک کلاس بنام EmailAnnotationPlugin ایجاد و متد support ارث بری شده را override میکنیم 


@Component
@Order(Validators.BEAN_VALIDATOR_PLUGIN_ORDER)
public class EmailAnnotationPlugin implements ModelPropertyBuilderPlugin {
    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }
}


سپس متد apply را override میکنیم :

@Override
public void apply(ModelPropertyContext context) {
    Optional<Email> email = annotationFromBean(context, Email.class);
    if (email.isPresent()) {
        context.getBuilder().pattern(email.get().regexp());
        context.getBuilder().example("email@email.com");
    }
}

همانطور که میبینید ابتدا از annotation مربوط به Email@ استفاده کردیم و بعد pattern درستی عبارت ایمیل را از آن گرفتیم و نمونه درست آن را از ایجاد کردیم تا در داکیومنت نمایش داده شود


حال کافی است Email@ را در فیلد مورد نظر برای Validation استفاده کنیم :

@Entity
public class User {
    //...
 
    @Email(regexp=".@.\\..*", message = "Email should be valid")
    private String email;
}


اما قبل از تست باید کلاس EmailAnnotationPlugin بصورت یک Bean تعریف شود :

@Import({BeanValidatorPluginsConfiguration.class})
public class SpringFoxConfig {
    //...
 
    @Bean
    public EmailAnnotationPlugin emailPlugin() {
        return new EmailAnnotationPlugin();
    }
}

حال میتوانیم در داکیومنت تولید شده Swagger مشاهده کنیم که مدل User فیلد email دارای داکیومنت pattern و example است :




تنظیمات پیشرفته تر :


Docket Bean تنظیماتی دارد که میتواند کنترل بیشتری روی فرآیند تولید داکیومنت API  به ما بدهد



فیلتر کردن بخش هایی از داکیومنت API :


همیشه معقول نیست که کلیه داکیومنت های بخش های مختلف API را افشا کنیم از اینرو میتوان با کمک متدهای apis و paths در کلاس Docket بخش های مورد نظر را فیلتر کنیم 


همانطور که در ابتدای این بخش دیدم با استفاده از RequestHandlerSelectors میتوانستیم بدون انتخاب بصورت any بگذاریم و یا بر اساس base package name یا  class annotation یا method annotation فیلتر کنیم 

و PathSelectors امکان اضافه تری برای فیلتر کردن بر اساس مسیر در application میباشد که میتوان از سه متد any و regex و ant استفاده کنیم 

@Bean
public Docket api() {                
    return new Docket(DocumentationType.SWAGGER_2)          
      .select()                                       
      .apis(RequestHandlerSelectors.basePackage("org.baeldung.web.controller"))
      .paths(PathSelectors.ant("/foos/*"))                     
      .build();
}



اضافه کردن اطلاعات جانبی و اضافی در داکیومنت تولید شده :


گاهی لازم است اطلاعات اضافی برای نمایش ارائه دهیم مانند لایسنس استفاده ، ایمیل برای تماس  و ... که میتوانیم از طریق متد apiInfo که آرگومانی از نوع کلاس ApiInfo میگیرد

@Bean
public Docket api() {                
    return new Docket(DocumentationType.SWAGGER_2)          
      .select()
      .apis(RequestHandlerSelectors.basePackage("com.example.controller"))
      .paths(PathSelectors.ant("/foos/*"))
      .build()
      .apiInfo(apiInfo());
}
 
private ApiInfo apiInfo() {
    return new ApiInfo(
      "My REST API", 
      "Some custom description of API.", 
      "API TOS", 
      "Terms of service", 
      new Contact("John Doe", "www.example.com", "myeaddress@company.com"), 
      "License of API", "API license URL", Collections.emptyList());
}




امکان گذاشتن پیام های دلخواه برای کد response های مختلف :


یکی دیگر از امکانات swagger عوض کردن پیام های Http Response در کلاس Docket بوسیله متد globalResponseMessage .

ابتدا باید پیام های پیش فرض را غیر فعال کنیم 

فرض کنید میخواهیم پیام های مربوط به کد وضعیت 403 و 500 را در حالت GET بصورت سراسری تغییر و باز تعریف کنیم 

روی همان ابجکت Docket این تغییرات را میتوانیم اعمال کنیم :

.useDefaultResponseMessages(false)                                   
.globalResponseMessage(RequestMethod.GET,                     
  newArrayList(new ResponseMessageBuilder()   
    .code(500)
    .message("500 message")
    .responseModel(new ModelRef("Error"))
    .build(),
    new ResponseMessageBuilder() 
      .code(403)
      .message("Forbidden!")
      .build()));


و در خروجی داکیومنت میتوانیم آنرا مشاهده کنیم :





گذاشتن شرایط امنیتی احراز هویت برای صفحه نمایش داکیومنت با OAuth API :


میخواهیم دسترسی به داکیومنت را با استفاده از تنظیمات SecurityScheme و SecurityContext روی ابجکت Docket ست کنیم


@Bean
public Docket api() {
    return new Docket(DocumentationType.SWAGGER_2).select()
        .apis(RequestHandlerSelectors.any())
        .paths(PathSelectors.any())
        .build()
        .securitySchemes(Arrays.asList(securityScheme()))
        .securityContexts(Arrays.asList(securityContext()));
}




بعد یک SecurityConfiguration Bean میسازیم :


@Bean
public SecurityConfiguration security() {
    return SecurityConfigurationBuilder.builder()
        .clientId(CLIENT_ID)
        .clientSecret(CLIENT_SECRET)
        .scopeSeparator(" ")
        .useBasicAuthenticationWithAccessCodeGrant(true)
        .build();
}


با SecurityScheme روش های مورد استفاده را تعریف میکنیم که در اینجا از OAuth2 استفاده شده است :

private SecurityScheme securityScheme() {
    GrantType grantType = new AuthorizationCodeGrantBuilder()
        .tokenEndpoint(new TokenEndpoint(AUTH_SERVER + "/token", "oauthtoken"))
        .tokenRequestEndpoint(
          new TokenRequestEndpoint(AUTH_SERVER + "/authorize", CLIENT_ID, CLIENT_SECRET))
        .build();
 
    SecurityScheme oauth = new OAuthBuilder().name("spring_oauth")
        .grantTypes(Arrays.asList(grantType))
        .scopes(Arrays.asList(scopes()))
        .build();
    return oauth;
}


حالا نیاز است که scope هایی را تعریف کنیم :

private AuthorizationScope[] scopes() {
    AuthorizationScope[] scopes = { 
      new AuthorizationScope("read", "for read operations"), 
      new AuthorizationScope("write", "for write operations"), 
      new AuthorizationScope("foo", "Access foo API") };
    return scopes;
}



تعریف security Context :


private SecurityContext securityContext() {
    return SecurityContext.builder()
      .securityReferences(
        Arrays.asList(new SecurityReference("spring_oauth", scopes())))
      .forPaths(PathSelectors.regex("/foos.*"))
      .build();
}



حالا تست میکنیم : 


ادرس دسترسی :

http://localhost:8082/spring-security-oauth-resource/swagger-ui.html


اولین چیزی که در این صفحه خواهیم دید درخواست احراز هویت قبل از ورود به بخش داکیومنت ها است :




وقتی وارد بخش احراز هویت میشویم از ما اطلاعات مربوط به scope  و id و عبارت محرمانه را درخواست میکند :




بعد از احراز هویت وقتی وارد بخش داکیومنت ها شدیم میتوانیم ببینیم که محتوا علامت محافظت شده (عکس یک قفل) را داراست و بدین صورت تنظیمات امنیتی دسترسی به داکیومنت ست شده است :









نظرات  (۰)

هیچ نظری هنوز ثبت نشده است

ارسال نظر

ارسال نظر آزاد است، اما اگر قبلا در بیان ثبت نام کرده اید می توانید ابتدا وارد شوید.
شما میتوانید از این تگهای html استفاده کنید:
<b> یا <strong>، <em> یا <i>، <u>، <strike> یا <s>، <sup>، <sub>، <blockquote>، <code>، <pre>، <hr>، <br>، <p>، <a href="" title="">، <span style="">، <div align="">
تجدید کد امنیتی