Skip to content

swagger authentication improve #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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