Skip to content

Commit 25d6f41

Browse files
authored
Merge pull request #20 from Throyer/development
swagger authentication improve
2 parents 9ca7821 + 2ced82e commit 25d6f41

File tree

11 files changed

+107
-36
lines changed

11 files changed

+107
-36
lines changed

README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ docker-compose -p common-api-development -f docker-compose.dev.yml up -d
125125
Building image for production
126126
```bash
127127
cd docker
128-
DOCKER_BUILDKIT=1 docker build -f Dockerfile.prod -t common-api:4.1.1 ../
128+
DOCKER_BUILDKIT=1 docker build -f Dockerfile.prod -t common-api:4.1.2 ../
129129
```
130130

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

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

docker/docker-compose.prod.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ services:
1717
- ./.volumes/database:/var/lib/postgresql/data
1818

1919
api:
20-
image: common-api:4.1.1
20+
image: common-api:4.1.2
2121
restart: unless-stopped
2222
container_name: common-api
2323
links:
@@ -30,4 +30,5 @@ services:
3030
DB_PASSWORD: ${DB_PASSWORD}
3131
TOKEN_SECRET: ${TOKEN_SECRET}
3232
DB_SHOW_SQL: "false"
33-
PRIVATE_SWAGGER: "true"
33+
SWAGGER_USERNAME: "example"
34+
SWAGGER_PASSWORD: "example"

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
</parent>
1010
<groupId>com.github.throyer.common.spring-boot</groupId>
1111
<artifactId>api</artifactId>
12-
<version>4.1.1</version>
12+
<version>4.1.2</version>
1313
<name>CRUD API</name>
1414

1515
<description>Exemplo de api simples com Spring Boot</description>

src/main/java/com/github/throyer/common/springboot/configurations/SpringSecurityConfiguration.java

+40-14
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,30 @@
22

33
import static com.github.throyer.common.springboot.constants.SECURITY.ACESSO_NEGADO_URL;
44
import static com.github.throyer.common.springboot.constants.SECURITY.DAY_MILLISECONDS;
5+
import static com.github.throyer.common.springboot.constants.SECURITY.ENCODER;
56
import static com.github.throyer.common.springboot.constants.SECURITY.HOME_URL;
67
import static com.github.throyer.common.springboot.constants.SECURITY.LOGIN_ERROR_URL;
78
import static com.github.throyer.common.springboot.constants.SECURITY.LOGIN_URL;
89
import static com.github.throyer.common.springboot.constants.SECURITY.LOGOUT_URL;
9-
import static com.github.throyer.common.springboot.constants.SECURITY.PASSWORD_ENCODER;
1010
import static com.github.throyer.common.springboot.constants.SECURITY.PASSWORD_PARAMETER;
11-
import static com.github.throyer.common.springboot.constants.SECURITY.PRIVATE_SWAGGER;
12-
import static com.github.throyer.common.springboot.constants.SECURITY.PUBLIC_API_ROUTES;
11+
import static com.github.throyer.common.springboot.constants.SECURITY.PUBLICS;
1312
import static com.github.throyer.common.springboot.constants.SECURITY.SESSION_COOKIE_NAME;
1413
import static com.github.throyer.common.springboot.constants.SECURITY.TOKEN_SECRET;
1514
import static com.github.throyer.common.springboot.constants.SECURITY.USERNAME_PARAMETER;
1615
import static com.github.throyer.common.springboot.utils.Responses.forbidden;
16+
import static java.util.Optional.ofNullable;
1717
import static org.springframework.http.HttpMethod.GET;
1818
import static org.springframework.http.HttpMethod.POST;
1919
import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
2020

21+
import java.util.Optional;
22+
import java.util.stream.Stream;
23+
2124
import com.github.throyer.common.springboot.domain.session.service.SessionService;
2225
import com.github.throyer.common.springboot.middlewares.AuthorizationMiddleware;
2326

2427
import org.springframework.beans.factory.annotation.Autowired;
28+
import org.springframework.beans.factory.annotation.Value;
2529
import org.springframework.context.annotation.Bean;
2630
import org.springframework.context.annotation.Configuration;
2731
import org.springframework.core.annotation.Order;
@@ -46,6 +50,9 @@ public class SpringSecurityConfiguration {
4650
private final SessionService sessionService;
4751
private final AuthorizationMiddleware filter;
4852

53+
public static String SWAGGER_USERNAME;
54+
public static String SWAGGER_PASSWORD;
55+
4956
@Autowired
5057
public SpringSecurityConfiguration(
5158
SessionService sessionService,
@@ -57,11 +64,29 @@ public SpringSecurityConfiguration(
5764

5865
@Autowired
5966
protected void globalConfiguration(
60-
AuthenticationManagerBuilder authentication
67+
AuthenticationManagerBuilder authentication,
68+
@Value("${swagger.username}") String username,
69+
@Value("${swagger.password}") String password
6170
) throws Exception {
71+
SpringSecurityConfiguration.SWAGGER_USERNAME = username;
72+
SpringSecurityConfiguration.SWAGGER_PASSWORD = password;
73+
74+
if (Stream
75+
.of(ofNullable(SWAGGER_PASSWORD), ofNullable(SWAGGER_USERNAME))
76+
.allMatch(Optional::isPresent)) {
77+
78+
authentication
79+
.inMemoryAuthentication()
80+
.passwordEncoder(ENCODER)
81+
.withUser(username)
82+
.password(ENCODER.encode(password))
83+
.authorities("SWAGGER");
84+
}
85+
86+
6287
authentication
6388
.userDetailsService(sessionService)
64-
.passwordEncoder(PASSWORD_ENCODER);
89+
.passwordEncoder(ENCODER);
6590
}
6691

6792
@Bean
@@ -74,7 +99,7 @@ public AuthenticationManager authenticationManager(
7499
@Bean
75100
@Order(1)
76101
public SecurityFilterChain api(HttpSecurity http) throws Exception {
77-
PUBLIC_API_ROUTES.injectOn(http);
102+
PUBLICS.injectOn(http);
78103

79104
http
80105
.antMatcher("/api/**")
@@ -137,19 +162,20 @@ public SecurityFilterChain app(HttpSecurity http) throws Exception {
137162
@Bean
138163
@Order(4)
139164
public SecurityFilterChain swagger(HttpSecurity http) throws Exception {
165+
if (Stream
166+
.of(ofNullable(SWAGGER_PASSWORD), ofNullable(SWAGGER_USERNAME))
167+
.allMatch(Optional::isPresent)) {
140168

141-
if (PRIVATE_SWAGGER) {
142169
http
143-
.authorizeRequests()
144-
.antMatchers("/swagger-ui/**", "/swagger-ui.html", "/**.html", "/documentation/**")
170+
.antMatcher("/swagger-ui/**")
171+
.authorizeRequests()
172+
.anyRequest()
145173
.authenticated()
174+
.and()
175+
.sessionManagement()
176+
.sessionCreationPolicy(STATELESS)
146177
.and()
147178
.httpBasic();
148-
} else {
149-
http
150-
.authorizeRequests()
151-
.antMatchers("/swagger-ui/**", "/swagger-ui.html", "/**.html", "/documentation/**")
152-
.permitAll();
153179
}
154180

155181
return http.build();

src/main/java/com/github/throyer/common/springboot/constants/SECURITY.java

+3-6
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,23 @@ public SECURITY(
1919
@Value("${token.secret}") String tokenSecret,
2020
@Value("${token.expiration-in-hours}") Integer tokenExpirationInHours,
2121
@Value("${token.refresh.expiration-in-days}") Integer refreshTokenExpirationInDays,
22-
@Value("${server.servlet.session.cookie.name}") String sessionCookieName,
23-
@Value("${swagger.is-private}") Boolean privateSwagger
22+
@Value("${server.servlet.session.cookie.name}") String sessionCookieName
2423
) {
2524
SECURITY.TOKEN_SECRET = tokenSecret;
2625
SECURITY.TOKEN_EXPIRATION_IN_HOURS = tokenExpirationInHours;
2726
SECURITY.REFRESH_TOKEN_EXPIRATION_IN_DAYS = refreshTokenExpirationInDays;
2827
SECURITY.SESSION_COOKIE_NAME = sessionCookieName;
29-
SECURITY.PRIVATE_SWAGGER = privateSwagger;
3028
}
3129

32-
public static final PublicRoutes PUBLIC_API_ROUTES = create()
30+
public static final PublicRoutes PUBLICS = create()
3331
.add(GET, "/api")
3432
.add(POST, "/api/users", "/api/sessions/**", "/api/recoveries/**");
3533

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

3937
public static final Integer PASSWORD_STRENGTH = 10;
40-
public static final BCryptPasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder(PASSWORD_STRENGTH);
38+
public static final BCryptPasswordEncoder ENCODER = new BCryptPasswordEncoder(PASSWORD_STRENGTH);
4139

4240
public static final String ROLES_KEY_ON_JWT = "roles";
4341

@@ -46,7 +44,6 @@ public SECURITY(
4644
public static Integer REFRESH_TOKEN_EXPIRATION_IN_DAYS;
4745

4846
public static String SESSION_COOKIE_NAME;
49-
public static Boolean PRIVATE_SWAGGER;
5047

5148
public static final String USERNAME_PARAMETER = "email";
5249
public static final String PASSWORD_PARAMETER = "password";

src/main/java/com/github/throyer/common/springboot/domain/session/service/SessionService.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public static void authorize(
4343
HttpServletRequest request,
4444
HttpServletResponse response
4545
) {
46-
if (PUBLIC_API_ROUTES.anyMatch(request)) {
46+
if (PUBLICS.anyMatch(request)) {
4747
return;
4848
}
4949

src/main/java/com/github/throyer/common/springboot/domain/user/entity/User.java

+4-5
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import java.util.Objects;
2222

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

122122
public void updatePassword(String newPassword) {
123-
this.password = PASSWORD_ENCODER
124-
.encode(newPassword);
123+
this.password = ENCODER.encode(newPassword);
125124
}
126125

127126
public Boolean validatePassword(String password) {
128-
return PASSWORD_ENCODER.matches(password, this.password);
127+
return ENCODER.matches(password, this.password);
129128
}
130129

131130
@PrePersist
132131
private void created() {
133-
this.password = PASSWORD_ENCODER.encode(password);
132+
this.password = ENCODER.encode(password);
134133
}
135134

136135
@Override

src/main/resources/application.properties

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ spring.h2.console.enabled=false
2323
spring.jpa.open-in-view=false
2424

2525
# swagger
26-
springdoc.swagger-ui.path=/documentation
27-
springdoc.api-docs.path=/documentation/schemas
2826
springdoc.default-produces-media-type=application/json
2927
springdoc.default-consumes-media-type=application/json
3028

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

3838
# smtp configurations
3939
spring.mail.host=${SMTP_HOST:smtp.gmail.com}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.github.throyer.common.springboot.swagger;
2+
3+
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
4+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
5+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
6+
7+
import org.junit.jupiter.api.DisplayName;
8+
import org.junit.jupiter.api.Test;
9+
import org.springframework.beans.factory.annotation.Autowired;
10+
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
11+
import org.springframework.boot.test.context.SpringBootTest;
12+
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
13+
import org.springframework.test.context.TestPropertySource;
14+
import org.springframework.test.web.servlet.MockMvc;
15+
16+
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
17+
@AutoConfigureMockMvc
18+
@TestPropertySource(properties = {
19+
"SWAGGER_USERNAME=test",
20+
"SWAGGER_PASSWORD=test"})
21+
public class SwaggerAuthTests {
22+
23+
@Autowired
24+
private MockMvc api;
25+
26+
@Test
27+
@DisplayName("Deve exibir a documentação com basic auth valido")
28+
public void should_display_the_swagger_docs_ui_with_valid_credentials() throws Exception {
29+
30+
var request = get("/swagger-ui/index.html?configUrl=/documentation/schemas/swagger-config")
31+
.header(AUTHORIZATION, "Basic dGVzdDp0ZXN0");
32+
33+
api.perform(request)
34+
.andExpect(status().isOk());
35+
}
36+
@Test
37+
@DisplayName("Não deve exibir a documentação com basic auth invalido")
38+
public void must_not_display_the_swagger_docs_ui_with_invalid_credentials() throws Exception {
39+
var request = get("/swagger-ui/index.html?configUrl=/documentation/schemas/swagger-config")
40+
.header(AUTHORIZATION, "Basic anViaWxldTppcmluZXU=");
41+
42+
api.perform(request)
43+
.andExpect(status().isUnauthorized());
44+
}
45+
}

src/test/java/com/github/throyer/common/springboot/SwaggerTests.java renamed to src/test/java/com/github/throyer/common/springboot/swagger/SwaggerTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.github.throyer.common.springboot;
1+
package com.github.throyer.common.springboot.swagger;
22

33
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
44
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

src/test/resources/application.properties

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ token.expiration-in-hours=24
2626
token.refresh.expiration-in-days=7
2727
token.secret=secret
2828
server.servlet.session.cookie.name=JSESSIONID
29-
swagger.is-private=false
29+
swagger.username=${SWAGGER_USERNAME:#{null}}
30+
swagger.password=${SWAGGER_PASSWORD:#{null}}
3031

3132
# recovery email
3233
recovery.minutes-to-expire=20

0 commit comments

Comments
 (0)