Skip to content

Commit 01e0a75

Browse files
committed
feat: improve handling of successful login and logout with cookies and headers
1 parent 95102b5 commit 01e0a75

23 files changed

+629
-76
lines changed

docker-compose.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ services:
5757
build: ./user-service
5858
container_name: user-service
5959
ports:
60-
- "18080:8080" # HTTP
60+
- "18080:18080" # HTTP
6161
- "19090:19090" # gRPC
6262
restart: always
6363
environment:
@@ -71,7 +71,7 @@ services:
7171
build: ./identity-service
7272
container_name: identity-service
7373
ports:
74-
- "18081:8081"
74+
- "18081:18081"
7575
restart: always
7676
environment:
7777
- SPRING_DATASOURCE_URL
@@ -87,7 +87,7 @@ services:
8787
build: ./product-service
8888
container_name: product-service
8989
ports:
90-
- "18082:8082"
90+
- "18082:18082"
9191
restart: always
9292
environment:
9393
- SPRING_DATASOURCE_URL
@@ -100,7 +100,7 @@ services:
100100
build: ./promotion-service
101101
container_name: promotion-service
102102
ports:
103-
- "18084:8084"
103+
- "18084:18084"
104104
restart: always
105105
environment:
106106
- SPRING_DATASOURCE_URL
@@ -113,7 +113,7 @@ services:
113113
build: ./cart-service
114114
container_name: cart-service
115115
ports:
116-
- "18085:8085"
116+
- "18085:18085"
117117
restart: always
118118
environment:
119119
- SPRING_DATASOURCE_URL
@@ -126,7 +126,7 @@ services:
126126
build: ./payment-service
127127
container_name: payment-service
128128
ports:
129-
- "18086:8086"
129+
- "18086:18086"
130130
restart: always
131131
environment:
132132
- SPRING_DATASOURCE_URL
@@ -139,7 +139,7 @@ services:
139139
build: ./rating-service
140140
container_name: rating-service
141141
ports:
142-
- "18087:8087"
142+
- "18087:18087"
143143
restart: always
144144
environment:
145145
- SPRING_DATASOURCE_URL

identity-service/src/main/java/com/blubin/identityservice/config/CustomUserDetailsService.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.blubin.identityservice.model.SiteUser;
44
import com.blubin.identityservice.repository.SiteUserRepository;
5+
import com.blubin.identityservice.utils.Constants;
56
import org.springframework.beans.factory.annotation.Autowired;
67
import org.springframework.security.core.userdetails.UserDetails;
78
import org.springframework.security.core.userdetails.UserDetailsService;
@@ -16,7 +17,7 @@ public class CustomUserDetailsService implements UserDetailsService {
1617
@Override
1718
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
1819
SiteUser user = userRepository.findByEmailAddress(email)
19-
.orElseThrow(() -> new UsernameNotFoundException("User not found with email: " + email));
20+
.orElseThrow(() -> new UsernameNotFoundException(Constants.ErrorCodes.USER_NOT_FOUND_WITH_EMAIL + email));
2021

2122
return org.springframework.security.core.userdetails.User.builder()
2223
.username(user.getEmailAddress())

identity-service/src/main/java/com/blubin/identityservice/config/JwtAuthenticationFilter.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package com.blubin.identityservice.config;
22

3+
import com.blubin.identityservice.service.RefreshTokenService;
34
import com.blubin.identityservice.utils.JwtUtils;
45
import jakarta.servlet.FilterChain;
56
import jakarta.servlet.ServletException;
7+
import jakarta.servlet.http.Cookie;
68
import jakarta.servlet.http.HttpServletRequest;
79
import jakarta.servlet.http.HttpServletResponse;
10+
import org.springframework.beans.factory.annotation.Autowired;
811
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
912
import org.springframework.security.core.context.SecurityContextHolder;
1013
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
@@ -38,6 +41,9 @@ protected void doFilterInternal(HttpServletRequest request,
3841
);
3942
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
4043
SecurityContextHolder.getContext().setAuthentication(authentication);
44+
System.out.println("Authenticated user: " + email);
45+
String authHeader = request.getHeader("Authorization");
46+
System.out.println("Authorization Header: " + authHeader);
4147
}
4248
} catch(Exception e) {
4349
logger.error("Cannot set user authentication}");
@@ -46,11 +52,32 @@ protected void doFilterInternal(HttpServletRequest request,
4652
filterChain.doFilter(request, response);
4753
}
4854

55+
// get JWT from header
4956
private String parseJwt(HttpServletRequest request) {
5057
String headerAuth = request.getHeader("Authorization");
58+
5159
if(headerAuth != null && headerAuth.startsWith("Bearer ")) {
5260
return headerAuth.substring(7);
5361
}
62+
5463
return null;
5564
}
65+
66+
// get JWT from header and cookie
67+
// private String parseJwt(HttpServletRequest request) {
68+
// String headerAuth = request.getHeader("Authorization");
69+
// if (headerAuth != null && headerAuth.startsWith("Bearer ")) {
70+
// return headerAuth.substring(7);
71+
// }
72+
//
73+
// if (request.getCookies() != null) {
74+
// for (Cookie cookie : request.getCookies()) {
75+
// if ("SERVER_SESSION".equals(cookie.getName())) {
76+
// return URLDecoder.decode(cookie.getValue(), StandardCharsets.UTF_8);
77+
// }
78+
// }
79+
// }
80+
//
81+
// return null;
82+
// }
5683
}

identity-service/src/main/java/com/blubin/identityservice/config/OAuth2LoginSuccessHandler.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package com.blubin.identityservice.config;
2+
23
import com.blubin.identityservice.model.SiteUser;
34
import com.blubin.identityservice.repository.SiteUserRepository;
45
import com.blubin.identityservice.service.GrpcUserService;
5-
import com.blubin.identityservice.service.SiteUserService;
6+
import com.blubin.identityservice.service.RefreshTokenService;
67
import com.blubin.identityservice.utils.JwtUtils;
7-
import com.blubin.proto.service.UserProfileRequest;
88
import jakarta.servlet.http.Cookie;
99
import jakarta.servlet.http.HttpServletRequest;
1010
import jakarta.servlet.http.HttpServletResponse;
@@ -28,6 +28,9 @@ public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler {
2828
@Autowired
2929
private SiteUserRepository siteUserRepository;
3030

31+
@Autowired
32+
private RefreshTokenService refreshTokenService;
33+
3134
public OAuth2LoginSuccessHandler(JwtUtils jwtUtils) {
3235
this.jwtUtils = jwtUtils;
3336
}
@@ -51,8 +54,15 @@ public void onAuthenticationSuccess(HttpServletRequest request,
5154
});
5255

5356
String token = jwtUtils.generateJwtToken(siteUser);
57+
String refreshToken = refreshTokenService.createRefreshToken(siteUser.getId());
58+
System.out.println(token);
59+
System.out.println(refreshToken);
60+
// Save JWT to Header
61+
response.setHeader("Access-Control-Expose-Headers", "Authorization");
62+
response.setHeader("Authorization", "Bearer " + token);
5463

55-
Cookie cookie = new Cookie("SERVER_SESSION", URLEncoder.encode(token, StandardCharsets.UTF_8));
64+
// Save JWT to Cookie
65+
Cookie cookie = new Cookie("SERVER_SESSION", URLEncoder.encode(refreshToken, StandardCharsets.UTF_8));
5666
// XSS
5767
cookie.setHttpOnly(true);
5868
// cookie.setSecure(true);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.blubin.identityservice.config;
2+
3+
import com.blubin.identityservice.service.RefreshTokenService;
4+
import jakarta.servlet.ServletException;
5+
import jakarta.servlet.http.Cookie;
6+
import jakarta.servlet.http.HttpServletRequest;
7+
import jakarta.servlet.http.HttpServletResponse;
8+
import org.springframework.beans.factory.annotation.Autowired;
9+
import org.springframework.security.core.Authentication;
10+
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
11+
import org.springframework.stereotype.Component;
12+
13+
import java.io.IOException;
14+
15+
@Component
16+
public class OAuth2LogoutSuccessHandler implements LogoutSuccessHandler {
17+
18+
@Autowired
19+
private RefreshTokenService refreshTokenService;
20+
21+
@Override
22+
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
23+
Cookie[] cookies = request.getCookies();
24+
if (cookies != null) {
25+
for (Cookie cookie : cookies) {
26+
if ("SERVER_SESSION".equals(cookie.getName())) {
27+
String refreshToken = cookie.getValue();
28+
refreshTokenService.revokeRefreshToken(refreshToken);
29+
}
30+
}
31+
}
32+
33+
response.setContentType("application/json");
34+
response.setCharacterEncoding("UTF-8");
35+
response.getWriter().write("{\"message\": \"Logged out successfully\"}");
36+
}
37+
}

identity-service/src/main/java/com/blubin/identityservice/config/SecurityConfig.java

Lines changed: 21 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.blubin.identityservice.model.CustomOidcUserService;
44
import com.blubin.identityservice.utils.Constants;
55
import io.github.cdimascio.dotenv.Dotenv;
6+
import jakarta.servlet.http.HttpServletResponse;
67
import org.springframework.beans.factory.annotation.Autowired;
78
import org.springframework.context.annotation.Bean;
89
import org.springframework.context.annotation.Configuration;
@@ -31,8 +32,10 @@
3132
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
3233
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
3334
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
35+
import org.springframework.web.cors.CorsConfiguration;
3436

3537
import java.util.HashSet;
38+
import java.util.List;
3639
import java.util.Map;
3740
import java.util.Set;
3841

@@ -45,12 +48,23 @@ public class SecurityConfig {
4548
@Autowired
4649
private OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler;
4750

51+
@Autowired
52+
private OAuth2LogoutSuccessHandler oAuth2LogoutSuccessHandler;
53+
4854
@Bean
4955
public SecurityFilterChain filterChain(HttpSecurity httpSecurity, CustomOidcUserService customOidcUserService) throws Exception {
5056
httpSecurity
5157
.csrf(AbstractHttpConfigurer::disable)
52-
// .cors(AbstractHttpConfigurer::disable)
53-
.cors(Customizer.withDefaults())
58+
// .cors(Customizer.withDefaults())
59+
.cors(cors -> cors.configurationSource(request -> {
60+
CorsConfiguration config = new CorsConfiguration();
61+
config.setAllowedOrigins(List.of("http://localhost:3000"));
62+
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
63+
config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
64+
config.setExposedHeaders(List.of("Authorization"));
65+
config.setAllowCredentials(true);
66+
return config;
67+
}))
5468
.headers(headers -> headers
5569
.xssProtection(xss -> xss.headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK))
5670
.contentSecurityPolicy(cps -> cps.policyDirectives("default-src 'self';base-uri 'self';"
@@ -66,33 +80,26 @@ public SecurityFilterChain filterChain(HttpSecurity httpSecurity, CustomOidcUser
6680
.requestMatchers("/api/v1/**").hasAnyAuthority("ADMIN")
6781
.anyRequest().authenticated()
6882
)
69-
// .formLogin((form) -> form
70-
// .defaultSuccessUrl("/swagger-ui/index.html", true)
71-
// .permitAll()
72-
// )
73-
7483
.oauth2Login(oauth2 -> oauth2
75-
// .loginPage("/login")
7684
.userInfoEndpoint(userInfo -> userInfo
7785
.oidcUserService(customOidcUserService)
7886
.userAuthoritiesMapper(this.userAuthoritiesMapper()))
7987
.successHandler(oAuth2LoginSuccessHandler)
8088
// .defaultSuccessUrl("/users", true)
81-
// .defaultSuccessUrl("/users")
8289
.failureUrl("/login?error")
8390
)
84-
// .oauth2Login(Customizer.withDefaults())
85-
8691
// JWT-based resource server configuration
87-
.oauth2ResourceServer(oauth2 -> oauth2
88-
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
89-
)
92+
// .oauth2ResourceServer(oauth2 -> oauth2
93+
// .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
94+
// )
9095

9196
.logout(logout -> logout
9297
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
9398
.logoutSuccessUrl("/login?logout")
99+
.logoutSuccessHandler(oAuth2LogoutSuccessHandler)
94100
.permitAll()
95101
)
102+
96103
.httpBasic(Customizer.withDefaults());
97104

98105
httpSecurity.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
@@ -123,10 +130,6 @@ private GrantedAuthoritiesMapper userAuthoritiesMapper() {
123130
OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
124131

125132
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
126-
127-
// Map the attributes found in userAttributes
128-
// to one or more GrantedAuthority's and add it to mappedAuthorities
129-
130133
}
131134
});
132135

@@ -181,22 +184,6 @@ private ClientRegistration googleClientRegistration(String google_client_id, Str
181184
.build();
182185
}
183186

184-
// @Bean
185-
// public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
186-
// UserDetails admin = User.builder()
187-
// .username("admin")
188-
// .password(passwordEncoder.encode("admin"))
189-
// .authorities("ADMIN")
190-
// .build();
191-
//
192-
// UserDetails user = User.builder()
193-
// .username("user")
194-
// .password(passwordEncoder.encode("user"))
195-
// .authorities("USER")
196-
// .build();
197-
//
198-
// return new InMemoryUserDetailsManager(admin, user);
199-
// }
200187

201188
@Bean
202189
public PasswordEncoder passwordEncoder() {

0 commit comments

Comments
 (0)