feature a check
This commit is contained in:
parent
a8750313da
commit
fe1e7b138e
@ -11,6 +11,8 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
|
||||
|
||||
@ -33,4 +35,11 @@ public class AuthenticationRestController {
|
||||
@RequestBody @Valid RegisterRequest request) {
|
||||
return ResponseEntity.ok(service.register(request));
|
||||
}
|
||||
|
||||
// Optional: simple endpoint to start OAuth2 authorization if frontend needs a redirect URL
|
||||
@GetMapping("/oauth2/authorization")
|
||||
public ResponseEntity<Void> oauth2Authorization(@RequestParam("provider") String provider) {
|
||||
// Frontend should redirect to /oauth2/authorization/{provider}
|
||||
return ResponseEntity.status(302).header("Location", "/oauth2/authorization/" + provider).build();
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
package com.dh7789dev.xpeditis.configuration;
|
||||
|
||||
import com.dh7789dev.xpeditis.dao.UserDao;
|
||||
import com.dh7789dev.xpeditis.entity.UserEntity;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
|
||||
|
||||
private final UserDao userDao;
|
||||
|
||||
@Override
|
||||
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
|
||||
OAuth2User oAuth2User = super.loadUser(userRequest);
|
||||
|
||||
Map<String, Object> attributes = oAuth2User.getAttributes();
|
||||
String registrationId = userRequest.getClientRegistration().getRegistrationId();
|
||||
|
||||
String email;
|
||||
if ("google".equals(registrationId)) {
|
||||
email = (String) attributes.get("email");
|
||||
} else if ("linkedin".equals(registrationId)) {
|
||||
email = (String) attributes.getOrDefault("email", attributes.get("emailAddress"));
|
||||
} else {
|
||||
throw new OAuth2AuthenticationException("Unsupported provider: " + registrationId);
|
||||
}
|
||||
|
||||
if (email == null) {
|
||||
throw new OAuth2AuthenticationException("Email not provided by OAuth2 provider.");
|
||||
}
|
||||
|
||||
UserEntity user = userDao.findByEmail(email)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("No local user bound to email: " + email));
|
||||
|
||||
return new DefaultOAuth2User(user.getAuthorities(), attributes, "email");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -88,8 +88,8 @@ public class GlobalConfiguration {
|
||||
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService() {
|
||||
return username -> userDao.findByUsername(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException(String.format(USER_NOT_FOUND_MSG, username)));
|
||||
return identifier -> userDao.findByUsernameOrEmail(identifier)
|
||||
.orElseThrow(() -> new UsernameNotFoundException(String.format(USER_NOT_FOUND_MSG, identifier)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -45,7 +45,9 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
final String jwt;
|
||||
final String username;
|
||||
|
||||
if (request.getServletPath().contains("/api/v1/auth")) {
|
||||
if (request.getServletPath().contains("/api/v1/auth") ||
|
||||
request.getServletPath().startsWith("/oauth2/") ||
|
||||
request.getServletPath().startsWith("/login/oauth2/")) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
package com.dh7789dev.xpeditis.configuration;
|
||||
|
||||
import com.dh7789dev.xpeditis.dao.TokenDao;
|
||||
import com.dh7789dev.xpeditis.dao.UserDao;
|
||||
import com.dh7789dev.xpeditis.entity.TokenEntity;
|
||||
import com.dh7789dev.xpeditis.entity.UserEntity;
|
||||
import com.dh7789dev.xpeditis.util.JwtUtil;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class OAuth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler {
|
||||
|
||||
private final UserDao userDao;
|
||||
private final TokenDao tokenDao;
|
||||
private final JwtUtil jwtUtil;
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
|
||||
String email = authentication.getName();
|
||||
UserEntity user = userDao.findByEmail(email)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found for email: " + email));
|
||||
|
||||
String accessToken = jwtUtil.generateToken(user);
|
||||
String refreshToken = jwtUtil.generateRefreshToken(user);
|
||||
|
||||
// Save token (like classic login)
|
||||
TokenEntity tokenEntity = new TokenEntity()
|
||||
.setUser(user)
|
||||
.setToken(accessToken)
|
||||
.setTokenType(TokenEntity.Type.BEARER)
|
||||
.setExpired(false)
|
||||
.setRevoked(false);
|
||||
tokenDao.save(tokenEntity);
|
||||
|
||||
response.setContentType("application/json");
|
||||
response.getWriter().write("{\"access_token\":\"" + accessToken + "\", \"refresh_token\":\"" + refreshToken + "\"}");
|
||||
response.getWriter().flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -34,13 +34,15 @@ public class SecurityConfiguration {
|
||||
boolean csrfEnabled;
|
||||
|
||||
private static final String ADMIN_ROLE = "ADMIN";
|
||||
private static final String ADMIN_PLATFORM_ROLE = "ADMIN_PLATFORM";
|
||||
// private static final String ADMIN_PLATFORM_ROLE = "ADMIN_PLATFORM";
|
||||
|
||||
private static final String API_V1_URI = "/api/v1/**";
|
||||
|
||||
private static final String[] WHITE_LIST_URL = {
|
||||
"/api/v1/auth/**",
|
||||
"/actuator/health/**"};
|
||||
"/actuator/health/**",
|
||||
"/oauth2/**",
|
||||
"/login/oauth2/**"};
|
||||
|
||||
private static final String[] INTERNAL_WHITE_LIST_URL = {
|
||||
"/v2/api-docs",
|
||||
@ -61,11 +63,17 @@ public class SecurityConfiguration {
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
private final LogoutHandler logoutHandler;
|
||||
private final CustomOAuth2UserService customOAuth2UserService;
|
||||
private final OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler;
|
||||
|
||||
@Autowired
|
||||
public SecurityConfiguration(UserDetailsService userDetailsService, LogoutHandler logoutHandler) {
|
||||
public SecurityConfiguration(UserDetailsService userDetailsService, LogoutHandler logoutHandler,
|
||||
CustomOAuth2UserService customOAuth2UserService,
|
||||
OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler) {
|
||||
this.userDetailsService = userDetailsService;
|
||||
this.logoutHandler = logoutHandler;
|
||||
this.customOAuth2UserService = customOAuth2UserService;
|
||||
this.oAuth2AuthenticationSuccessHandler = oAuth2AuthenticationSuccessHandler;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ -111,12 +119,14 @@ public class SecurityConfiguration {
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
|
||||
.authenticationProvider(authenticationProvider())
|
||||
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
.oauth2Login(oauth -> oauth
|
||||
.userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService))
|
||||
.successHandler(oAuth2AuthenticationSuccessHandler)
|
||||
)
|
||||
.logout(logout -> logout
|
||||
.logoutUrl("/api/v1/auth/logout")
|
||||
.addLogoutHandler(logoutHandler)
|
||||
.logoutSuccessHandler((request,
|
||||
response,
|
||||
authentication) -> SecurityContextHolder.clearContext()));
|
||||
.logoutSuccessHandler((req, res, auth) -> SecurityContextHolder.clearContext()));
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@ -47,6 +47,28 @@ spring:
|
||||
timeout: 3000
|
||||
writetimeout: 5000
|
||||
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration:
|
||||
google:
|
||||
client-id: ${GOOGLE_CLIENT_ID:}
|
||||
client-secret: ${GOOGLE_CLIENT_SECRET:}
|
||||
scope: openid,profile,email
|
||||
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
|
||||
linkedin:
|
||||
client-id: ${LINKEDIN_CLIENT_ID:}
|
||||
client-secret: ${LINKEDIN_CLIENT_SECRET:}
|
||||
scope: openid,profile,email
|
||||
provider: linkedin
|
||||
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
|
||||
provider:
|
||||
linkedin:
|
||||
authorization-uri: https://www.linkedin.com/oauth/v2/authorization
|
||||
token-uri: https://www.linkedin.com/oauth/v2/accessToken
|
||||
user-info-uri: https://api.linkedin.com/v2/userinfo
|
||||
user-name-attribute: sub
|
||||
|
||||
application:
|
||||
email:
|
||||
from: randommailjf@gmail.com
|
||||
|
||||
@ -51,6 +51,28 @@ spring:
|
||||
timeout: 3000
|
||||
writetimeout: 5000
|
||||
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration:
|
||||
google:
|
||||
client-id: ${GOOGLE_CLIENT_ID:}
|
||||
client-secret: ${GOOGLE_CLIENT_SECRET:}
|
||||
scope: openid,profile,email
|
||||
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
|
||||
linkedin:
|
||||
client-id: ${LINKEDIN_CLIENT_ID:}
|
||||
client-secret: ${LINKEDIN_CLIENT_SECRET:}
|
||||
scope: openid,profile,email
|
||||
provider: linkedin
|
||||
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
|
||||
provider:
|
||||
linkedin:
|
||||
authorization-uri: https://www.linkedin.com/oauth/v2/authorization
|
||||
token-uri: https://www.linkedin.com/oauth/v2/accessToken
|
||||
user-info-uri: https://api.linkedin.com/v2/userinfo
|
||||
user-name-attribute: sub
|
||||
|
||||
application:
|
||||
email:
|
||||
from: contact@leblr.fr
|
||||
|
||||
@ -42,6 +42,10 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-oauth2-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
|
||||
@ -6,12 +6,16 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
public interface UserDao extends JpaRepository<UserEntity, Long> {
|
||||
|
||||
@Query("SELECT u FROM UserEntity u WHERE u.username = :username")
|
||||
Optional<UserEntity> findByUsername(String username);
|
||||
boolean existsByUsername(String username);
|
||||
Optional<UserEntity> findByEmail(String email);
|
||||
boolean existsByEmail(String email);
|
||||
|
||||
@Query("SELECT u FROM UserEntity u WHERE u.username = :identifier OR u.email = :identifier")
|
||||
Optional<UserEntity> findByUsernameOrEmail(String identifier);
|
||||
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ public class AuthenticationJwtRepository implements AuthenticationRepository {
|
||||
@Override
|
||||
public AuthenticationResponse authenticate(AuthenticationRequest request) {
|
||||
|
||||
log.info("username: {}, password: {}", request.getUsername(), request.getPassword());
|
||||
log.info("identifier: {}", request.getUsername());
|
||||
UsernamePasswordAuthenticationToken authToken = UsernamePasswordAuthenticationToken
|
||||
.unauthenticated(request.getUsername(), request.getPassword());
|
||||
Authentication authentication = authenticationManager.authenticate(authToken);
|
||||
@ -42,7 +42,7 @@ public class AuthenticationJwtRepository implements AuthenticationRepository {
|
||||
throw new UsernameNotFoundException("Failed to authenticate");
|
||||
}
|
||||
|
||||
var userEntity = userDao.findByUsername(request.getUsername()).orElseThrow();
|
||||
var userEntity = userDao.findByUsernameOrEmail(request.getUsername()).orElseThrow();
|
||||
|
||||
var jwtToken = jwtUtil.generateToken(userEntity);
|
||||
var refreshToken = jwtUtil.generateRefreshToken(userEntity);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user