Skip to content

Commit

Permalink
Merge pull request #20 from Throyer/development
Browse files Browse the repository at this point in the history
swagger authentication improve
  • Loading branch information
Throyer authored Jun 1, 2022
2 parents 9ca7821 + 2ced82e commit 25d6f41
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 36 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ docker-compose -p common-api-development -f docker-compose.dev.yml up -d
Building image for production
```bash
cd docker
DOCKER_BUILDKIT=1 docker build -f Dockerfile.prod -t common-api:4.1.1 ../
DOCKER_BUILDKIT=1 docker build -f Dockerfile.prod -t common-api:4.1.2 ../
```

docker compose for production
Expand Down Expand Up @@ -155,13 +155,15 @@ docker-compose -p common-api -f docker-compose.prod.yml up -d
| SMTP server password | `SMTP_PASSWORD` | secret |
| time for recovery email to expire | `MINUTES_TO_EXPIRE_RECOVERY_CODE` | 20 |
| max requests per minute | `MAX_REQUESTS_PER_MINUTE` | 10 |
| swagger username | `SWAGGER_USERNAME` | `null` |
| swagger password | `SWAGGER_PASSWORD` | `null` |

> these variables are defined in: [**application.properties**](./src/main/resources/application.properties)
>
> ```shell
> # to change the value of some environment variable at runtime
> # on execution, just pass it as a parameter. (like --SERVER_PORT=80).
> $ java -jar api-4.1.1.RELEASE.jar --SERVER_PORT=80
> $ java -jar api-4.1.2.RELEASE.jar --SERVER_PORT=80
> ```


Expand Down
5 changes: 3 additions & 2 deletions docker/docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ services:
- ./.volumes/database:/var/lib/postgresql/data

api:
image: common-api:4.1.1
image: common-api:4.1.2
restart: unless-stopped
container_name: common-api
links:
Expand All @@ -30,4 +30,5 @@ services:
DB_PASSWORD: ${DB_PASSWORD}
TOKEN_SECRET: ${TOKEN_SECRET}
DB_SHOW_SQL: "false"
PRIVATE_SWAGGER: "true"
SWAGGER_USERNAME: "example"
SWAGGER_PASSWORD: "example"
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</parent>
<groupId>com.github.throyer.common.spring-boot</groupId>
<artifactId>api</artifactId>
<version>4.1.1</version>
<version>4.1.2</version>
<name>CRUD API</name>

<description>Exemplo de api simples com Spring Boot</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,30 @@

import static com.github.throyer.common.springboot.constants.SECURITY.ACESSO_NEGADO_URL;
import static com.github.throyer.common.springboot.constants.SECURITY.DAY_MILLISECONDS;
import static com.github.throyer.common.springboot.constants.SECURITY.ENCODER;
import static com.github.throyer.common.springboot.constants.SECURITY.HOME_URL;
import static com.github.throyer.common.springboot.constants.SECURITY.LOGIN_ERROR_URL;
import static com.github.throyer.common.springboot.constants.SECURITY.LOGIN_URL;
import static com.github.throyer.common.springboot.constants.SECURITY.LOGOUT_URL;
import static com.github.throyer.common.springboot.constants.SECURITY.PASSWORD_ENCODER;
import static com.github.throyer.common.springboot.constants.SECURITY.PASSWORD_PARAMETER;
import static com.github.throyer.common.springboot.constants.SECURITY.PRIVATE_SWAGGER;
import static com.github.throyer.common.springboot.constants.SECURITY.PUBLIC_API_ROUTES;
import static com.github.throyer.common.springboot.constants.SECURITY.PUBLICS;
import static com.github.throyer.common.springboot.constants.SECURITY.SESSION_COOKIE_NAME;
import static com.github.throyer.common.springboot.constants.SECURITY.TOKEN_SECRET;
import static com.github.throyer.common.springboot.constants.SECURITY.USERNAME_PARAMETER;
import static com.github.throyer.common.springboot.utils.Responses.forbidden;
import static java.util.Optional.ofNullable;
import static org.springframework.http.HttpMethod.GET;
import static org.springframework.http.HttpMethod.POST;
import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;

import java.util.Optional;
import java.util.stream.Stream;

import com.github.throyer.common.springboot.domain.session.service.SessionService;
import com.github.throyer.common.springboot.middlewares.AuthorizationMiddleware;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
Expand All @@ -46,6 +50,9 @@ public class SpringSecurityConfiguration {
private final SessionService sessionService;
private final AuthorizationMiddleware filter;

public static String SWAGGER_USERNAME;
public static String SWAGGER_PASSWORD;

@Autowired
public SpringSecurityConfiguration(
SessionService sessionService,
Expand All @@ -57,11 +64,29 @@ public SpringSecurityConfiguration(

@Autowired
protected void globalConfiguration(
AuthenticationManagerBuilder authentication
AuthenticationManagerBuilder authentication,
@Value("${swagger.username}") String username,
@Value("${swagger.password}") String password
) throws Exception {
SpringSecurityConfiguration.SWAGGER_USERNAME = username;
SpringSecurityConfiguration.SWAGGER_PASSWORD = password;

if (Stream
.of(ofNullable(SWAGGER_PASSWORD), ofNullable(SWAGGER_USERNAME))
.allMatch(Optional::isPresent)) {

authentication
.inMemoryAuthentication()
.passwordEncoder(ENCODER)
.withUser(username)
.password(ENCODER.encode(password))
.authorities("SWAGGER");
}


authentication
.userDetailsService(sessionService)
.passwordEncoder(PASSWORD_ENCODER);
.passwordEncoder(ENCODER);
}

@Bean
Expand All @@ -74,7 +99,7 @@ public AuthenticationManager authenticationManager(
@Bean
@Order(1)
public SecurityFilterChain api(HttpSecurity http) throws Exception {
PUBLIC_API_ROUTES.injectOn(http);
PUBLICS.injectOn(http);

http
.antMatcher("/api/**")
Expand Down Expand Up @@ -137,19 +162,20 @@ public SecurityFilterChain app(HttpSecurity http) throws Exception {
@Bean
@Order(4)
public SecurityFilterChain swagger(HttpSecurity http) throws Exception {
if (Stream
.of(ofNullable(SWAGGER_PASSWORD), ofNullable(SWAGGER_USERNAME))
.allMatch(Optional::isPresent)) {

if (PRIVATE_SWAGGER) {
http
.authorizeRequests()
.antMatchers("/swagger-ui/**", "/swagger-ui.html", "/**.html", "/documentation/**")
.antMatcher("/swagger-ui/**")
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(STATELESS)
.and()
.httpBasic();
} else {
http
.authorizeRequests()
.antMatchers("/swagger-ui/**", "/swagger-ui.html", "/**.html", "/documentation/**")
.permitAll();
}

return http.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,23 @@ public SECURITY(
@Value("${token.secret}") String tokenSecret,
@Value("${token.expiration-in-hours}") Integer tokenExpirationInHours,
@Value("${token.refresh.expiration-in-days}") Integer refreshTokenExpirationInDays,
@Value("${server.servlet.session.cookie.name}") String sessionCookieName,
@Value("${swagger.is-private}") Boolean privateSwagger
@Value("${server.servlet.session.cookie.name}") String sessionCookieName
) {
SECURITY.TOKEN_SECRET = tokenSecret;
SECURITY.TOKEN_EXPIRATION_IN_HOURS = tokenExpirationInHours;
SECURITY.REFRESH_TOKEN_EXPIRATION_IN_DAYS = refreshTokenExpirationInDays;
SECURITY.SESSION_COOKIE_NAME = sessionCookieName;
SECURITY.PRIVATE_SWAGGER = privateSwagger;
}

public static final PublicRoutes PUBLIC_API_ROUTES = create()
public static final PublicRoutes PUBLICS = create()
.add(GET, "/api")
.add(POST, "/api/users", "/api/sessions/**", "/api/recoveries/**");

public static final Integer DAY_MILLISECONDS = 86400;
public static final JsonWebToken JWT = new JsonWebToken();

public static final Integer PASSWORD_STRENGTH = 10;
public static final BCryptPasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder(PASSWORD_STRENGTH);
public static final BCryptPasswordEncoder ENCODER = new BCryptPasswordEncoder(PASSWORD_STRENGTH);

public static final String ROLES_KEY_ON_JWT = "roles";

Expand All @@ -46,7 +44,6 @@ public SECURITY(
public static Integer REFRESH_TOKEN_EXPIRATION_IN_DAYS;

public static String SESSION_COOKIE_NAME;
public static Boolean PRIVATE_SWAGGER;

public static final String USERNAME_PARAMETER = "email";
public static final String PASSWORD_PARAMETER = "password";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public static void authorize(
HttpServletRequest request,
HttpServletResponse response
) {
if (PUBLIC_API_ROUTES.anyMatch(request)) {
if (PUBLICS.anyMatch(request)) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import java.util.Objects;

import static com.fasterxml.jackson.annotation.JsonProperty.Access.WRITE_ONLY;
import static com.github.throyer.common.springboot.constants.SECURITY.PASSWORD_ENCODER;
import static com.github.throyer.common.springboot.constants.SECURITY.ENCODER;
import static com.github.throyer.common.springboot.domain.management.repository.Queries.NON_DELETED_CLAUSE;
import static com.github.throyer.common.springboot.utils.JSON.stringify;
import static java.util.Objects.hash;
Expand Down Expand Up @@ -120,17 +120,16 @@ public void merge(UpdateUserProps props) {
}

public void updatePassword(String newPassword) {
this.password = PASSWORD_ENCODER
.encode(newPassword);
this.password = ENCODER.encode(newPassword);
}

public Boolean validatePassword(String password) {
return PASSWORD_ENCODER.matches(password, this.password);
return ENCODER.matches(password, this.password);
}

@PrePersist
private void created() {
this.password = PASSWORD_ENCODER.encode(password);
this.password = ENCODER.encode(password);
}

@Override
Expand Down
6 changes: 3 additions & 3 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ spring.h2.console.enabled=false
spring.jpa.open-in-view=false

# swagger
springdoc.swagger-ui.path=/documentation
springdoc.api-docs.path=/documentation/schemas
springdoc.default-produces-media-type=application/json
springdoc.default-consumes-media-type=application/json

Expand All @@ -33,7 +31,9 @@ token.expiration-in-hours=${TOKEN_EXPIRATION_IN_HOURS:24}
token.refresh.expiration-in-days=${REFRESH_TOKEN_EXPIRATION_IN_DAYS:7}
token.secret=${TOKEN_SECRET:secret}
server.servlet.session.cookie.name=API_EXAMPLE_SESSION_ID
swagger.is-private=${PRIVATE_SWAGGER:true}
server.servlet.session.cookie.path=/app
swagger.username=${SWAGGER_USERNAME:#{null}}
swagger.password=${SWAGGER_PASSWORD:#{null}}

# smtp configurations
spring.mail.host=${SMTP_HOST:smtp.gmail.com}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.github.throyer.common.springboot.swagger;

import static org.springframework.http.HttpHeaders.AUTHORIZATION;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@AutoConfigureMockMvc
@TestPropertySource(properties = {
"SWAGGER_USERNAME=test",
"SWAGGER_PASSWORD=test"})
public class SwaggerAuthTests {

@Autowired
private MockMvc api;

@Test
@DisplayName("Deve exibir a documentação com basic auth valido")
public void should_display_the_swagger_docs_ui_with_valid_credentials() throws Exception {

var request = get("/swagger-ui/index.html?configUrl=/documentation/schemas/swagger-config")
.header(AUTHORIZATION, "Basic dGVzdDp0ZXN0");

api.perform(request)
.andExpect(status().isOk());
}
@Test
@DisplayName("Não deve exibir a documentação com basic auth invalido")
public void must_not_display_the_swagger_docs_ui_with_invalid_credentials() throws Exception {
var request = get("/swagger-ui/index.html?configUrl=/documentation/schemas/swagger-config")
.header(AUTHORIZATION, "Basic anViaWxldTppcmluZXU=");

api.perform(request)
.andExpect(status().isUnauthorized());
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.github.throyer.common.springboot;
package com.github.throyer.common.springboot.swagger;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
Expand Down
3 changes: 2 additions & 1 deletion src/test/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ token.expiration-in-hours=24
token.refresh.expiration-in-days=7
token.secret=secret
server.servlet.session.cookie.name=JSESSIONID
swagger.is-private=false
swagger.username=${SWAGGER_USERNAME:#{null}}
swagger.password=${SWAGGER_PASSWORD:#{null}}

# recovery email
recovery.minutes-to-expire=20
Expand Down

0 comments on commit 25d6f41

Please sign in to comment.