-
Notifications
You must be signed in to change notification settings - Fork 27
/
Copy pathSecurityManager.java
189 lines (171 loc) · 8.01 KB
/
SecurityManager.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package com.pizza.configuration.security;
import com.pizza.service.cache.UserBlacklistCacheService;
import com.spring5microservices.common.dto.UsernameAuthoritiesDto;
import com.spring5microservices.common.enums.ExtendedHttpStatus;
import com.spring5microservices.common.exception.UnauthorizedException;
import com.spring5microservices.common.util.HttpUtil;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.reactive.error.DefaultErrorAttributes;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ResponseStatusException;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import static java.lang.String.format;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
/**
* Manages the validation of the token related with a logged user, using the {@link Authentication}
* to get and fill the required {@link UsernamePasswordAuthenticationToken} used later to know if the
* user has the correct {@link GrantedAuthority}.
*/
@Component
@Log4j2
public class SecurityManager implements ReactiveAuthenticationManager {
private final SecurityConfiguration securityConfiguration;
private final UserBlacklistCacheService userBlacklistCacheService;
private final WebClient webClient;
@Autowired
public SecurityManager(@Lazy final SecurityConfiguration securityConfiguration,
@Lazy final UserBlacklistCacheService userBlacklistCacheService,
@Lazy final WebClient webClient) {
this.securityConfiguration = securityConfiguration;
this.userBlacklistCacheService = userBlacklistCacheService;
this.webClient = webClient;
}
@Override
public Mono<Authentication> authenticate(final Authentication authentication) {
String authToken = authentication.getCredentials().toString();
return getAuthenticationInformation(
securityConfiguration.getAuthenticationInformationWebService(),
authToken
)
.map(this::getFromUsernameAuthoritiesDto);
}
/**
* Using the given token gets the authentication information related with the logged user.
*
* @param authenticationInformationWebService
* Web service used to get authentication information
* @param token
* Token (included Http authentication scheme)
*
* @return {@link Optional} of {@link UsernameAuthoritiesDto}
*/
private Mono<UsernameAuthoritiesDto> getAuthenticationInformation(final String authenticationInformationWebService,
final String token) {
return webClient.post()
.uri(authenticationInformationWebService)
.header(
HttpHeaders.AUTHORIZATION,
buildAuthorizationHeader(
securityConfiguration.getClientId(),
securityConfiguration.getClientPassword()
)
)
.body(BodyInserters.fromValue(token))
.retrieve()
.onStatus(
httpStatus ->
List.of(BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, NOT_FOUND).contains(httpStatus),
clientResponse -> {
log.warn(
format(
"There was an error invoking authorization server using provided token: %s. The response was: %s",
token,
clientResponse.rawStatusCode()
)
);
return Mono.empty();
}
)
.onRawStatus(
httpStatus ->
ExtendedHttpStatus.TOKEN_EXPIRED.value() == httpStatus,
clientResponse -> {
log.warn(
format(
"The provided authentication token: %s has expired",
token
)
);
/**
* {@link DefaultErrorAttributes#determineHttpStatus(Throwable, MergedAnnotation)} transforms
* the information of the thrown exception into the suitable HTTP status to return.
*/
throw new ResponseStatusException(
UNAUTHORIZED,
"Provided token has expired"
);
}
)
.bodyToMono(UsernameAuthoritiesDto.class);
}
/**
* Converts a given {@link UsernameAuthoritiesDto} into an {@link UsernamePasswordAuthenticationToken}
*
* @param usernameAuthoritiesDto
* {@link UsernameAuthoritiesDto} to convert
*
* @return {@link UsernamePasswordAuthenticationToken}
*
* @throws UnauthorizedException is the given {@code username} has been included in the black list.
*/
private UsernamePasswordAuthenticationToken getFromUsernameAuthoritiesDto(final UsernameAuthoritiesDto usernameAuthoritiesDto) {
if (userBlacklistCacheService.contains(usernameAuthoritiesDto.getUsername())) {
throw new UnauthorizedException(
format("The given username: %s has been included in the blacklist",
usernameAuthoritiesDto.getUsername()
)
);
}
Collection<? extends GrantedAuthority> authorities = ofNullable(usernameAuthoritiesDto.getAuthorities())
.map(auth ->
auth.stream()
.map(SimpleGrantedAuthority::new)
.collect(toList())
)
.orElseGet(ArrayList::new);
UsernamePasswordAuthenticationToken authenticationInfo = new UsernamePasswordAuthenticationToken(
usernameAuthoritiesDto.getUsername(),
null,
authorities
);
authenticationInfo.setDetails(usernameAuthoritiesDto.getAdditionalInfo());
return authenticationInfo;
}
/**
* Build the required Basic Authentication header to send requests to the security server.
*
* @param username
* Security server client identifier
* @param password
* Security server client password
*
* @return {@link String}
*/
private String buildAuthorizationHeader(final String username,
final String password) {
return HttpUtil.encodeBasicAuthentication(
username,
password
);
}
}