authentication

This commit is contained in:
2024-11-18 16:26:53 +01:00
parent 576c5155d9
commit a59d438dbb
8 changed files with 327 additions and 40 deletions
@@ -3,15 +3,25 @@ package com.sasiedzi.event.config;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.oauth2.core.oidc.StandardClaimNames.PREFERRED_USERNAME;
import com.sasiedzi.event.domain.CurrentUserHolder;
import com.sasiedzi.event.repository.UserRepository;
import com.sasiedzi.event.security.*;
import com.sasiedzi.event.security.SecurityUtils;
import com.sasiedzi.event.security.oauth2.AudienceValidator;
import com.sasiedzi.event.security.oauth2.CustomClaimConverter;
import com.sasiedzi.event.service.EventService;
import com.sasiedzi.event.service.UserService;
import com.sasiedzi.event.service.dto.AdminUserDTO;
import com.sasiedzi.event.web.filter.SpaWebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
@@ -21,8 +31,15 @@ import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
@@ -35,11 +52,14 @@ import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
import org.springframework.security.oauth2.jwt.*;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.*;
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.context.annotation.RequestScope;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import tech.jhipster.config.JHipsterProperties;
import tech.jhipster.web.filter.CookieCsrfFilter;
@@ -82,27 +102,140 @@ public class SecurityConfiguration {
.authorizeHttpRequests(authz ->
// prettier-ignore
authz
.requestMatchers(mvc.pattern("/index.html"), mvc.pattern("/*.js"), mvc.pattern("/*.txt"), mvc.pattern("/*.json"), mvc.pattern("/*.map"), mvc.pattern("/*.css")).permitAll()
.requestMatchers(mvc.pattern("/*.ico"), mvc.pattern("/*.png"), mvc.pattern("/*.svg"), mvc.pattern("/*.webapp")).permitAll()
.requestMatchers(mvc.pattern("/assets/**")).permitAll()
.requestMatchers(mvc.pattern("/swagger-ui/**")).permitAll()
.requestMatchers(mvc.pattern("/api/authenticate")).permitAll()
.requestMatchers(mvc.pattern("/api/auth-info")).permitAll()
// .requestMatchers(mvc.pattern("/index.html"), mvc.pattern("/*.js"), mvc.pattern("/*.txt"), mvc.pattern("/*.json"), mvc.pattern("/*.map"), mvc.pattern("/*.css")).permitAll()
// .requestMatchers(mvc.pattern("/*.ico"), mvc.pattern("/*.png"), mvc.pattern("/*.svg"), mvc.pattern("/*.webapp")).permitAll()
// .requestMatchers(mvc.pattern("/assets/**")).permitAll()
// .requestMatchers(mvc.pattern("/swagger-ui/**")).permitAll()
// .requestMatchers(mvc.pattern("/api/authenticate")).permitAll()
// .requestMatchers(mvc.pattern("/api/auth-info")).permitAll()
.requestMatchers(mvc.pattern("/logout")).permitAll()
.requestMatchers(mvc.pattern("/api/admin/**")).hasAuthority(AuthoritiesConstants.ADMIN)
.requestMatchers(mvc.pattern("/api/**")).authenticated()
// .requestMatchers(mvc.pattern("/api/**")).authenticated()
.requestMatchers(mvc.pattern("/**")).authenticated()
.requestMatchers(mvc.pattern("/v3/api-docs/**")).hasAuthority(AuthoritiesConstants.ADMIN)
.requestMatchers(mvc.pattern("/management/health")).permitAll()
.requestMatchers(mvc.pattern("/management/health/**")).permitAll()
.requestMatchers(mvc.pattern("/management/info")).permitAll()
.requestMatchers(mvc.pattern("/management/prometheus")).permitAll()
// .requestMatchers(mvc.pattern("/management/health")).permitAll()
// .requestMatchers(mvc.pattern("/management/health/**")).permitAll()
// .requestMatchers(mvc.pattern("/management/info")).permitAll()
// .requestMatchers(mvc.pattern("/management/prometheus")).permitAll()
.requestMatchers(mvc.pattern("/management/**")).hasAuthority(AuthoritiesConstants.ADMIN)
)
.oauth2Login(oauth2 -> oauth2.loginPage("/").userInfoEndpoint(userInfo -> userInfo.oidcUserService(this.oidcUserService())))
.rememberMe(
rememberMe -> rememberMe.rememberMeServices(rememberMeServices())
// .key("remember-me-cookie-key32342") // Klucz do szyfrowania tokenu
// .tokenValiditySeconds(60 * 60 * 24 * 7) // 7 dni
)
.oauth2Login(oauth2 ->
oauth2
.defaultSuccessUrl("/", true)
/*.loginPage("/")*/.userInfoEndpoint(userInfo -> userInfo.oidcUserService(this.oidcUserService()))
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(authenticationConverter())))
.oauth2Client(withDefaults());
return http.build();
}
@Bean
public RememberMeServices rememberMeServices() {
TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices("remember-me-key", userDetailsService());
rememberMeServices.setTokenValiditySeconds(60 * 60 * 24 * 7);
rememberMeServices.setAlwaysRemember(true);
// Ustawienie czasu ważności tokenu na 7 dni
return rememberMeServices;
}
@Autowired
UserRepository userRepository;
@Bean
public UserDetailsService userDetailsService() {
// Jeśli korzystasz z in-memory użytkowników lub innego źródła, skonfiguruj tutaj.
return new UserDetailsService() {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String login = extractUsername(username);
// String[] authorities = extractGrantedAuthorities(username);
Map<String, Object> attributes = extractUserAttributes(username);
Optional<com.sasiedzi.event.domain.User> one = userRepository.findOneByLogin(login);
if (one.isPresent()) {
// return new User(one.get().getLogin(), "somepassword", one.get().getAuthorities().stream().map(authority-> new SimpleGrantedAuthority(authority.getName())).collect(Collectors.toSet()));
// return new User(login, "somepassword", Arrays.stream(authorities).map(authority-> new SimpleGrantedAuthority(authority)).collect(Collectors.toSet()));
return new User(username, "somepassword", extractRoles(username));
} else {
throw new UsernameNotFoundException("User not found");
}
}
private static String extractUsername(String input) {
Pattern pattern = Pattern.compile("Name:\\s+\\[([^]]+)]");
Matcher matcher = pattern.matcher(input);
if (matcher.find()) {
return matcher.group(1);
}
return null;
}
private static String[] extractGrantedAuthorities(String input) {
Pattern pattern = Pattern.compile("Granted Authorities:\\s+\\[([^]]+)]");
Matcher matcher = pattern.matcher(input);
if (matcher.find()) {
return matcher.group(1).split(", ");
}
return new String[0];
}
private static Set<GrantedAuthority> extractRoles(String input) {
Set<GrantedAuthority> roles = new HashSet<>();
Pattern pattern = Pattern.compile("https://www\\.jhipster\\.tech/roles=\\[([^]]+)]");
Matcher matcher = pattern.matcher(input);
if (matcher.find()) {
String rolesString = matcher.group(1);
String[] rolesArray = rolesString.split(",\\s*");
for (String role : rolesArray) {
roles.add(new SimpleGrantedAuthority(role));
}
}
return roles;
}
private static Map<String, Object> extractUserAttributes(String input) {
Map<String, Object> attributes = new HashMap<>();
Pattern pattern = Pattern.compile("User Attributes:\\s+\\[\\{([^}]+)\\}\\]");
Matcher matcher = pattern.matcher(input);
if (matcher.find()) {
String attributesString = matcher.group(1);
String[] attributePairs = attributesString.split(",\\s*");
for (String pair : attributePairs) {
String[] keyValue = pair.split("=");
if (keyValue.length == 2) {
String key = keyValue[0];
String value = keyValue[1];
// Tutaj wartość jest zawsze String, ale można by to rozszerzyć by obsługiwało różne typy
attributes.put(key, value);
}
}
}
return attributes;
}
};
}
@Bean
@RequestScope
public CurrentUserHolder currentUser(UserService userService, EventService eventService) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication instanceof AbstractAuthenticationToken) {
AbstractAuthenticationToken authToken = (AbstractAuthenticationToken) authentication;
AdminUserDTO userFromAuthentication = userService.getUserFromAuthentication(authToken);
return new CurrentUserHolder(
userFromAuthentication,
eventService.getOrCreateUserAccountForLogin(userFromAuthentication.getLogin()),
authentication
);
}
return new CurrentUserHolder();
}
@Bean
MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) {
return new MvcRequestMatcher.Builder(introspector);
@@ -0,0 +1,41 @@
package com.sasiedzi.event.domain;
import com.sasiedzi.event.service.dto.AdminUserDTO;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;
@Component
@RequestScope
public class CurrentUserHolder {
AdminUserDTO adminUser;
UserAccount userAccount;
Authentication authentication;
public CurrentUserHolder() {}
public AdminUserDTO getAdminUser() {
return adminUser;
}
public UserAccount getUserAccount() {
return userAccount;
}
public Authentication getAuthentication() {
return authentication;
}
public String getLogin() {
if (adminUser == null) return null;
return adminUser.getLogin();
}
public CurrentUserHolder(AdminUserDTO adminUser, UserAccount userAccount, Authentication authentication) {
this.adminUser = adminUser;
this.userAccount = userAccount;
this.authentication = authentication;
}
}
@@ -3,6 +3,7 @@ package com.sasiedzi.event.service;
import com.sasiedzi.event.domain.*;
import com.sasiedzi.event.domain.enumeration.TransactionType;
import com.sasiedzi.event.repository.*;
import com.sasiedzi.event.service.dto.AdminUserDTO;
import com.sasiedzi.event.web.rest.AccountResource;
import jakarta.validation.Valid;
import java.math.BigDecimal;
@@ -10,12 +11,16 @@ import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.RememberMeAuthenticationToken;
import org.springframework.security.core.AuthenticatedPrincipal;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -235,21 +240,36 @@ public class EventService {
return eventRepository.findById(event.getId());
}
public String getCurrentAuthenticatedUserAsDTO() {
if (SecurityContextHolder.getContext().getAuthentication() == null) {
@Autowired
AdminUserDTO currentUser;
// public String getCurrentAuthenticatedUserAsDTO() {
// if (SecurityContextHolder.getContext().getAuthentication() == null) {
// return null;
// }
// if (SecurityContextHolder.getContext() == null) {
// return null;
// }
// Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
// if (principal instanceof AuthenticatedPrincipal) {
// return extractUsername((AuthenticatedPrincipal) principal).getName();
// // return userService.getUserFromAuthentication((AbstractAuthenticationToken) principal);
// } else if (principal instanceof UserDetails) {
// return asdf((UserDetails) principal).getUsername();
// // return userService.getUserFromAuthentication((AbstractAuthenticationToken) principal);
// } else {
// throw new RuntimeException("User could not be found");
// }
// }
private static String extractUsername(String input) {
Pattern pattern = Pattern.compile("Name:\\s+\\[([^]]+)]");
Matcher matcher = pattern.matcher(input);
if (matcher.find()) {
return matcher.group(1);
}
return null;
}
if (SecurityContextHolder.getContext() == null) {
return null;
}
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof AuthenticatedPrincipal) {
return ((AuthenticatedPrincipal) principal).getName();
// return userService.getUserFromAuthentication((AbstractAuthenticationToken) principal);
} else {
throw new RuntimeException("User could not be found");
}
}
public UserAccount createNewAccountForLogin(String login) {
UserAccount entity = new UserAccount();
@@ -264,9 +284,7 @@ public class EventService {
}
}
public UserAccount getOrCreateForCurrentUser() {
// if (true) return new UserAccount();
String username = getCurrentAuthenticatedUserAsDTO();
public UserAccount getOrCreateUserAccountForLogin(String username) {
if (username != null) {
// List<UserAccount> userAccounts = userAccountRepository.findByUserLogin(currentAuthenticatedUserAsDTO.getLogin());
List<UserAccount> userAccounts = userAccountRepository
@@ -8,14 +8,20 @@ import com.sasiedzi.event.repository.UserRepository;
import com.sasiedzi.event.security.SecurityUtils;
import com.sasiedzi.event.service.dto.AdminUserDTO;
import com.sasiedzi.event.service.dto.UserDTO;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.RememberMeAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
@@ -129,6 +135,48 @@ public class UserService {
return user;
}
private static Map<String, Object> extractUserAttributes(String input) {
Map<String, Object> attributes = new HashMap<>();
Pattern pattern = Pattern.compile("User Attributes:\\s+\\[\\{([^}]+)\\}\\]");
Matcher matcher = pattern.matcher(input);
if (matcher.find()) {
String attributesString = matcher.group(1);
String[] attributePairs = attributesString.split(",\\s*");
for (String pair : attributePairs) {
String[] keyValue = pair.split("=");
if (keyValue.length == 2) {
String key = keyValue[0];
String value = keyValue[1];
// Konwersja wartości "true" i "false" na typ Boolean
if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) {
attributes.put(key, Boolean.parseBoolean(value));
} else {
// Próba sparsowania jako Instant
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX");
Instant instant = Instant.from(formatter.parse(value));
attributes.put(key, instant);
} catch (Exception e) {
// Jeśli nie jest datą, zostawiamy jako String
attributes.put(key, value);
}
}
}
}
}
return attributes;
}
// private static String extractUsername(String input) {
// Pattern pattern = Pattern.compile("Name:\\s+\\[([^]]+)]");
// Matcher matcher = pattern.matcher(input);
// if (matcher.find()) {
// return matcher.group(1);
// }
// return null;
// }
/**
* Returns the user from an OAuth 2.0 login or resource server with JWT.
* Synchronizes the user in the local repository.
@@ -143,6 +191,10 @@ public class UserService {
attributes = ((OAuth2AuthenticationToken) authToken).getPrincipal().getAttributes();
} else if (authToken instanceof JwtAuthenticationToken) {
attributes = ((JwtAuthenticationToken) authToken).getTokenAttributes();
} else if (authToken instanceof RememberMeAuthenticationToken) {
attributes = extractUserAttributes(authToken.getName());
// attributes = new HashMap<>();
// attributes = ((RememberMeAuthenticationToken) authToken).getTokenAttributes();
} else {
throw new IllegalArgumentException("AuthenticationToken is not OAuth2 or JWT!");
}
@@ -8,10 +8,14 @@ import java.io.Serializable;
import java.time.Instant;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;
/**
* A DTO representing a user, with his authorities.
*/
@Component
@RequestScope
public class AdminUserDTO implements Serializable {
private static final long serialVersionUID = 1L;
@@ -1,10 +1,12 @@
package com.sasiedzi.event.web.rest;
import com.sasiedzi.event.domain.CurrentUserHolder;
import com.sasiedzi.event.service.UserService;
import com.sasiedzi.event.service.dto.AdminUserDTO;
import java.security.Principal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.web.bind.annotation.GetMapping;
@@ -44,13 +46,16 @@ public class AccountResource {
*/
@GetMapping("/account")
public AdminUserDTO getAccount(Principal principal) {
if (principal instanceof AbstractAuthenticationToken) {
return userService.getUserFromAuthentication((AbstractAuthenticationToken) principal);
if (currentUser != null) {
return currentUser.getAdminUser();
} else {
throw new AccountResourceException("User could not be found");
}
}
@Autowired
CurrentUserHolder currentUser;
/**
* {@code GET /authenticate} : check if the user is authenticated, and return its login.
*
@@ -1,13 +1,21 @@
package com.sasiedzi.event.web.rest;
import com.sasiedzi.event.domain.CurrentUserHolder;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -23,24 +31,47 @@ public class LogoutResource {
this.registration = registrations.findByRegistrationId("oidc");
}
@Autowired
CurrentUserHolder currentUser;
/**
* {@code POST /api/logout} : logout the current user.
*
* @param request the {@link HttpServletRequest}.
* @param idToken the ID token.
* @param principal principal with the ID token.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and a body with a global logout URL.
*/
@PostMapping("/api/logout")
public ResponseEntity<?> logout(HttpServletRequest request, @AuthenticationPrincipal(expression = "idToken") OidcIdToken idToken) {
public ResponseEntity<?> logout(HttpServletRequest request, HttpServletResponse response) {
OidcIdToken idToken = null;
Authentication authentication = currentUser.getAuthentication();
StringBuilder logoutUrl = new StringBuilder();
String originUrl = request.getHeader(HttpHeaders.ORIGIN);
if (authentication != null && authentication.getPrincipal() instanceof DefaultOAuth2User) {
idToken = ((DefaultOidcUser) authentication.getPrincipal()).getIdToken();
logoutUrl.append(this.registration.getProviderDetails().getConfigurationMetadata().get("end_session_endpoint").toString());
String originUrl = request.getHeader(HttpHeaders.ORIGIN);
if (idToken != null) {
logoutUrl.append("?id_token_hint=").append(idToken.getTokenValue()).append("&post_logout_redirect_uri=").append(originUrl);
}
} else {
logoutUrl.append(originUrl);
}
new SecurityContextLogoutHandler().logout(request, response, null);
// Ręczne usuwanie ciasteczka 'remember-me'
Cookie cookie = new Cookie("remember-me", null);
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie);
HttpSession existingSession = request.getSession();
if (existingSession != null) {
existingSession.invalidate();
}
request.getSession().invalidate();
return ResponseEntity.ok().body(Map.of("logoutUrl", logoutUrl.toString()));
}
}
@@ -1,5 +1,6 @@
package com.sasiedzi.event.web.rest;
import com.sasiedzi.event.domain.CurrentUserHolder;
import com.sasiedzi.event.domain.User;
import com.sasiedzi.event.domain.UserAccount;
import com.sasiedzi.event.repository.UserAccountRepository;
@@ -174,10 +175,12 @@ public class UserAccountResource {
@GetMapping("/currentUser")
public UserAccount getCurrentUserAccount() {
UserAccount userAccount = eventService.getOrCreateForCurrentUser();
return userAccount;
return currentUser.getUserAccount();
}
@Autowired
CurrentUserHolder currentUser;
/**
* {@code DELETE /user-accounts/:id} : delete the "id" userAccount.
*