I am trying to write Spring boot authentication using JWT token and Google SSO.
But when i configured SecurityContextRepository securityContextRepository(securityContextRepository) in my security config Google SSO does not working while JWT authentication fine.
Because Google SSO does not validate in SecurityContextRepository class.
It called save function of SecurityContextRepository class.
Here is my Security Config class
#Autowired
private SecurityContextRepository securityContextRepository;
#Bean
public SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
String[] patterns = new String[] {"/auth/**","/about"};
return http
.exceptionHandling()
.accessDeniedHandler(new JsonAccessDeniedHandler())
.and()
.csrf().disable()
.authenticationManager(authenticationManager)
.securityContextRepository(securityContextRepository)
.authorizeExchange()
.pathMatchers(patterns).permitAll()
.pathMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.anyExchange().authenticated()
.and().oauth2Login()
.and()
.build();
}
Here is my SecurityContextRepository class:
#Component
public class SecurityContextRepository implements ServerSecurityContextRepository {
private static final Logger logger = LoggerFactory.getLogger(SecurityContextRepository.class);
private static final String TOKEN_PREFIX = "Bearer ";
#Autowired
private AuthenticationManager authenticationManager;
#Override
public Mono<Void> save(ServerWebExchange swe, SecurityContext sc) {
System.out.println("sc.getAuthentication().getDetails() = " + sc.getAuthentication().getDetails());
throw new UnsupportedOperationException("Not supported yet.");
// return Mono.empty();
}
#Override
public Mono load(ServerWebExchange swe) {
ServerHttpRequest request = swe.getRequest();
String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
String authToken = null;
if (authHeader != null && authHeader.startsWith(TOKEN_PREFIX)) {
authToken = authHeader.replace(TOKEN_PREFIX, "");
}else {
logger.warn("couldn't find bearer string, will ignore the header.");
}
if (authToken != null) {
Authentication auth = new UsernamePasswordAuthenticationToken(authToken, authToken);
return this.authenticationManager.authenticate(auth).map((authentication) -> new SecurityContextImpl((Authentication) authentication));
} else {
return Mono.empty();
}
}
}
How to configure oauth2Login() method with securityContextRepository.
Please, help me to find a mistake.
Related
I'm having trouble getting Spring-Security to work with Jersey within my Spring Boot project. It seems to be something to do with the way the two are integrated but I can't figure out why...
In Jersey endpoints the Spring Security enforced authentication requirement (configured in the WebSecurityConfigurerAdapter) works fine however some method security annotations are ignored. JSR-250 annotations (such as #DenyAll, #RolesAllowed, etc) seem to work if one registers the RolesAllowedDynamicFeature feature. However, #PreAuthorize, #PostAuthorize, #PreFilter, #PostFilter don't work.
Also, when logging in via the /login endpoint the XSRF-TOKEN cookie is not sent. Normally Spring security handles this and if I rewrite the endpoint with Spring MVC, I get both the XSRF-TOKEN and my own ACCESS-TOKEN (generated and sent by the endpoint itself).
(I found a workaround to this - see end of post)
Here's some code (I left out the obvious stuff like JwtUtils, general bean config, etc). Full source (not that there's much - this is a pared-down version of the real project) can be found at https://github.com/ChambreNoire/jersey-issue.
Thanks!
(I'm using Spring Boot 2.3.10.REALEASE and upgrading isn't currently an option)
application.yaml
spring:
jersey:
application-path: /resources
servlet:
load-on-startup: 1
type: filter
JerseyConfig
#Configuration
public class JerseyConfig {
#Bean
public ResourceConfig resourceConfig(ObjectMapper objectMapper) {
return new ResourceConfig()
.property(ServletProperties.FILTER_FORWARD_ON_404, true)
.register((ContextResolver<ObjectMapper>) aClass -> objectMapper)
.register(JacksonFeature.class)
.register(HttpMethodOverrideFilter.class)
.register(AuthResource.class)
.register(UserResource.class);
}
}
SecurityConfigurationAdapter
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
private static final String[] PUBLIC_PATTERNS;
private final JwtTokenAuthFilter jwtTokenAuthFilter;
private final JwtTokenAuthEntryPoint unauthorizedHandler;
#Autowired
public SecurityConfigurationAdapter(final JwtTokenAuthFilter jwtTokenAuthFilter, final JwtTokenAuthEntryPoint unauthorizedHandler) {
this.jwtTokenAuthFilter = jwtTokenAuthFilter;
this.unauthorizedHandler = unauthorizedHandler;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.configurationSource(corsConfig()).and()
.csrf()
.ignoringAntMatchers("/login", "/login2")
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler).and()
.authorizeRequests()
.antMatchers("/login", "/login2").permitAll()
.anyRequest().authenticated().and()
.addFilterBefore(jwtTokenAuthFilter, UsernamePasswordAuthenticationFilter.class)
.headers()
.xssProtection().and()
.contentSecurityPolicy("script-src 'self';require-trusted-types-for 'script';object-src 'none';");
}
private CorsConfigurationSource corsConfig() {
return request -> {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(singletonList("http://localhost:9000"));
config.setAllowedMethods(singletonList("*"));
config.setAllowCredentials(true);
config.setAllowedHeaders(singletonList("*"));
config.setExposedHeaders(singletonList("Authorization"));
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return config;
};
}
}
JwtTokenAuthFilter
#Priority(Priorities.AUTHENTICATION)
public class JwtTokenAuthFilter extends OncePerRequestFilter {
private final UserDetailsService userDetailsService;
private final JwtTokenUtils jwtTokenUtils;
public JwtTokenAuthFilter(final UserDetailsService userDetailsService, final JwtTokenUtils jwtTokenUtils) {
this.userDetailsService = userDetailsService;
this.jwtTokenUtils = jwtTokenUtils;
}
#Override
protected void doFilterInternal(final HttpServletRequest req, final HttpServletResponse resp, final FilterChain chain)
throws ServletException, IOException {
if (req.getCookies() == null) {
chain.doFilter(req, resp);
return;
}
String token = Arrays.stream(req.getCookies())
.filter(c -> "ACCESS-TOKEN".equals(c.getName()))
.findFirst()
.map(Cookie::getValue)
.orElse(null);
if (isEmpty(token) || !jwtTokenUtils.validateJwtToken(token)) {
chain.doFilter(req, resp);
return;
}
String username = jwtTokenUtils.getUserNameFromJwtToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, resp);
}
}
AuthResource
#Singleton
#Path("")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class AuthResource {
private final AuthenticationManager authenticationManager;
private final JwtTokenUtils jwtTokenUtils;
#Inject
public AuthResource(AuthenticationManager authenticationManager, JwtTokenUtils jwtTokenUtils) {
this.authenticationManager = authenticationManager;
this.jwtTokenUtils = jwtTokenUtils;
}
#POST
#Path("/login")
public Response login(#RequestBody LoginRequest request) {
try {
org.springframework.security.core.Authentication authentication = authenticationManager
.authenticate(
new UsernamePasswordAuthenticationToken(
request.getEmail(), request.getPassword()
)
);
UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal();
return Response.ok()
.cookie(new NewCookie(
"ACCESS-TOKEN",
jwtTokenUtils.generateJwtToken(user),
"/", null, null,
NewCookie.DEFAULT_MAX_AGE,
true, true
))
.build();
} catch (BadCredentialsException ex) {
return Response.status(Response.Status.UNAUTHORIZED).build();
}
}
}
If anyone could enlighten me on why this is the case that would be most appreciated, or indeed any general comments/improvements you may have !
Cheers
UPDATE
It looks like adding the JWT cookie to the javax.ws.rs.core.Response is causing the issue although I'm not certain how. If I add it directly to the HttpServletResponse, the XSRF-TOKEN cookie is present in the response.
Here's the updated Jersey authentication endpoint :
#POST
#Path("/login")
public Response authenticateUser(#Context HttpServletResponse response, #RequestBody LoginRequest request) {
try {
Authentication authentication = authenticationManager
.authenticate(
new UsernamePasswordAuthenticationToken(
request.getEmail(), request.getPassword()
)
);
UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal();
Cookie cookie = new Cookie("ACCESS-TOKEN", jwtTokenUtils.generateJwtToken(user));
cookie.setPath("/");
cookie.setSecure(true);
cookie.setHttpOnly(true);
cookie.setMaxAge(-1);
response.addCookie(cookie);
return Response.ok().build();
} catch (BadCredentialsException ex) {
return Response.status(Response.Status.UNAUTHORIZED).build();
}
}
I am trying to add access control to a set of api endpoints and the problem I am running into is that the service is redirecting to / regardless of whether the original request was /api/apple or /api/orange. I currently have a filter set up to read a custom http header to do the authentication and the filter I am using is extended from AbstractAuthenticationProcessingFilter. The documentation is saying that it is intended for the AbstractAuthenticationProcessingFilter to redirect to a specific url upon successful authentication, but this is not the behavior I want for an api. I think I may be using the wrong Filter, but I don't know which one I should be using. Can I get some help on what I may be doing wrong and what I should be doing?
Filter Chain Configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfig {
#Bean
AuthenticationManager customAuthenticationManager(PreAuthProvider preAuthProvider) {
return new ProviderManager(List.of(preAuthProvider));
}
#Bean
SessionAuthFilter customAuthFilter(AuthenticationManager authManager, CustomUserDetails userDetails) {
return new SessionAuthFilter(
new OrRequestMatcher(
new AntPathRequestMatcher("/apple/**"),
new AntPathRequestMatcher("/orange/**")
),
authManager,
userDetails);
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http, SessionAuthFilter authFilter) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new Http403ForbiddenEntryPoint())
.accessDeniedHandler(new AccessDeniedHandlerImpl())
.and()
.formLogin().disable()
.httpBasic().disable()
.authorizeRequests()
.antMatchers(
"/",
"/error",
"/v3/api-docs/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/actuator/**"
).permitAll()
.antMatchers(GET, "/apple").hasAuthority("getApples")
.antMatchers(GET, "/orange").hasAuthority("getOranges")
.anyRequest().authenticated()
.and()
.addFilterBefore(authFilter, AbstractPreAuthenticatedProcessingFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
Filter Implementation:
public class SessionAuthFilter extends AbstractAuthenticationProcessingFilter {
private final CustomUserDetails userDetails;
protected SessionAuthFilter(RequestMatcher requestMatcher, AuthenticationManager authenticationManager,
CustomUserDetails userDetails) {
super(requestMatcher, authenticationManager);
this.userDetails = userDetails;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
var sessionToken = request.getHeader("SessionToken") != null ? request.getHeader("SessionToken").trim() : null;
var user = userDetails.loadUserByUsername(sessionToken);
var authentication = new PreAuthenticatedAuthenticationToken(user.getUsername(), user.getPassword(),
user.getAuthorities());
authentication.setAuthenticated(user.isCredentialsNonExpired());
authentication.setDetails(userDetails);
SecurityContextHolder.getContext().setAuthentication(authentication);
return this.getAuthenticationManager().authenticate(authentication);
}
}
Authentication Provider:
#Component
#Slf4j
public class PreAuthProvider implements AuthenticationProvider {
private boolean throwExceptionWhenTokenRejected;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!this.supports(authentication.getClass())) {
return null;
} else {
log.debug(String.valueOf(LogMessage.format("PreAuthenticated authentication request: %s", authentication)));
if (authentication.getPrincipal() == null) {
log.debug("No pre-authenticated principal found in request.");
if (this.throwExceptionWhenTokenRejected) {
throw new BadCredentialsException("No pre-authenticated principal found in request.");
} else {
return null;
}
} else if (authentication.getCredentials() == null) {
log.debug("No pre-authenticated credentials found in request.");
if (this.throwExceptionWhenTokenRejected) {
throw new BadCredentialsException("No pre-authenticated credentials found in request.");
} else {
return null;
}
} else if (!authentication.isAuthenticated()) {
throw new InsufficientAuthenticationException("Session token likely no longer valid.");
}
return authentication;
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(PreAuthenticatedAuthenticationToken.class);
}
public void setThrowExceptionWhenTokenRejected(boolean throwExceptionWhenTokenRejected) {
this.throwExceptionWhenTokenRejected = throwExceptionWhenTokenRejected;
}
}
It looks like if you set continueChainBeforeSuccessfulAuthentication in your AbstractAuthenticationProcessingFilter implementation to true, you can delay the redirection. Using your own success handler implementation will completely stop the redirect behavior. I only needed to modify the filter constructor which came out to be:
protected SessionAuthFilter(RequestMatcher requestMatcher, AuthenticationManager authenticationManager,
CustomUserDetails userDetails) {
super(requestMatcher, authenticationManager);
this.userDetails = userDetails;
this.setContinueChainBeforeSuccessfulAuthentication(true);
this.setAuthenticationSuccessHandler((request, response, authentication) -> {});
}
The other approach would be to implement a different Filter such as OncePerRequestFilter or a GenericFilterBean to handle the authentication yourself.
I use spring boot with thymeleaf, spring security and spring cloud gateway.
User enter login/password and get a token. I search a way to get this tokin and put it in a cookie or in a hidden field in fragment. Need to to do some ajax call from thymeleaf page.
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class WebFluxSecurityConfig {
#Autowired
private WebFluxAuthManager authManager;
#Autowired
private WebFluxSecurityContextRepository webFluxSecurityContextRepository;
#Bean
protected SecurityWebFilterChain securityFilterChange(ServerHttpSecurity http) throws Exception {
http.csrf().disable()
.securityContextRepository(webFluxSecurityContextRepository)
.authorizeExchange()
// URL that starts with / or /login/
.pathMatchers("/", "/login", "/js/**", "/img/**", "/css/**").permitAll()
.anyExchange().authenticated()
.and().formLogin().loginPage("/login")
.authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("/two")
).and().csrf().disable();
http.authenticationManager(authManager);
return http.build();
}
}
#Component
public class WebFluxSecurityContextRepository implements ServerSecurityContextRepository {
private final WebFluxAuthManager authManager;
public WebFluxSecurityContextRepository(WebFluxAuthManager authManager) {
this.authManager = authManager;
}
#Override
public Mono<Void> save(ServerWebExchange exchange, SecurityContext context) {
return Mono.empty();
}
#Override
public Mono<SecurityContext> load(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String authToken = authHeader.substring(7);
Authentication auth = new UsernamePasswordAuthenticationToken(authToken, authToken);
return this.authManager.authenticate(auth).map((authentication) -> {
return new SecurityContextImpl(authentication);
});
} else {
return Mono.empty();
}
}
}
#Component
public class WebFluxAuthManager implements ReactiveAuthenticationManager {
#Value("${gateway.url}")
private String gatewayUrl;
#Autowired
private WebClient webClient;
#Override
public Mono<Authentication> authenticate(Authentication authentication) {
Mono<ResponseEntity<String>> mResponse = webClient.post()
.uri("/auth/login")
.acceptCharset(Charset.forName("UTF-8"))
.body(Mono.just(loginRequest), LoginDto.class)
.retrieve()
.toEntity(String.class);
...
...
return Mono.just(new UsernamePasswordAuthenticationToken(username, password, authorities));
}
I have been following Dave Syer astounding tutorial to implement OAuth2 in microservices which provide RESTful APIs for mobile devices (Android and iOS). I have configured gateway security with the following code:
#SpringBootApplication
#EnableDiscoveryClient
#EnableZuulProxy
#EnableCircuitBreaker
#EnableFeignClients
#EnableOAuth2Client
public class GatewayApplication extends WebSecurityConfigurerAdapter {
private OAuth2ClientContext oauth2ClientContext;
private SimpleUrlAuthenticationSuccessHandler simpleUrlAuthenticationSuccessHandler;
private ScoreAuthorizationFilter scoreAuthorizationFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/test", "/login**", "/webjars/**", "/error**")
.permitAll()
.anyRequest()
.authenticated()
.and().logout().logoutSuccessUrl("/").permitAll()
.and().addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class)
.addFilterBefore(scoreAuthorizationFilter, BasicAuthenticationFilter.class)
;
}
private Filter ssoFilter() {
OAuth2ClientAuthenticationProcessingFilter facebookFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/facebook");
OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(facebook(), oauth2ClientContext);
facebookFilter.setRestTemplate(facebookTemplate);
UserInfoTokenServices tokenServices = new UserInfoTokenServices(facebookResource().getUserInfoUri(), facebook().getClientId());
tokenServices.setRestTemplate(facebookTemplate);
facebookFilter.setTokenServices(tokenServices);
facebookFilter.setAuthenticationSuccessHandler(simpleUrlAuthenticationSuccessHandler);
return facebookFilter;
}
#Bean
#ConfigurationProperties("facebook.client")
public AuthorizationCodeResourceDetails facebook() {
return new AuthorizationCodeResourceDetails();
}
#Bean
#ConfigurationProperties("facebook.resource")
public ResourceServerProperties facebookResource() {
return new ResourceServerProperties();
}
#Bean
public FilterRegistrationBean<OAuth2ClientContextFilter> oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
FilterRegistrationBean<OAuth2ClientContextFilter> registration = new FilterRegistrationBean<OAuth2ClientContextFilter>();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
#Bean
public RequestInterceptor getFeignClientInterceptor() {
return new FeignClientInterceptor();
}
}
It turns out that the user's session expires after a while. As I dug a little deeper, I found out that Facebook doesn't provide refresh tokens. Instead, we can exchange a short-lived token for a long-lived token (Facebook long-lived token). How can I override the standard OAuth2 flow implemented in Spring Security to send another request to Facebook for getting the long-lived token and then replacing the old access token?
You can achieve what you want by extending the OAuth2ClientAuthenticationProcessingFilter class like this:
public class CustomAuthenticationProcessingFilter extends OAuth2ClientAuthenticationProcessingFilter {
private ResourceServerTokenServices tokenServices;
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new OAuth2AuthenticationDetailsSource();
private ApplicationEventPublisher eventPublisher;
private AuthorizationCodeResourceDetails facebook;
private String longLivedTokenUri;
public CustomAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
setAuthenticationDetailsSource(authenticationDetailsSource);
}
#Override
public void setTokenServices(ResourceServerTokenServices tokenServices) {
this.tokenServices = tokenServices;
super.setTokenServices(tokenServices);
}
#Override
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
super.setApplicationEventPublisher(eventPublisher);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
OAuth2AccessToken accessToken;
try {
accessToken = restTemplate.getAccessToken();
} catch (OAuth2Exception e) {
BadCredentialsException bad = new BadCredentialsException("Could not obtain access token", e);
publish(new OAuth2AuthenticationFailureEvent(bad));
throw bad;
}
String longLivedToken = getFromFacebook(); //Get long lived token from facebook here
try {
OAuth2Authentication result = tokenServices.loadAuthentication(longLivedToken);
if (authenticationDetailsSource != null) {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, longLivedToken);
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType());
result.setDetails(authenticationDetailsSource.buildDetails(request));
}
publish(new AuthenticationSuccessEvent(result));
return result;
} catch (InvalidTokenException e) {
BadCredentialsException bad = new BadCredentialsException("Could not obtain user details from token", e);
publish(new OAuth2AuthenticationFailureEvent(bad));
throw bad;
}
}
private void publish(ApplicationEvent event) {
if (eventPublisher != null) {
eventPublisher.publishEvent(event);
}
}
}
I hope this helps.
I am using Spring boot with Spring security, with custom "Filter" Class calling to CIAM server with OAuth 2 authentication. I want to set explicitly or override the default setting so that I could set custom dynamic STATE parameter in the redirect URL that Spring Security prepares under the hood and sends the user to the CIAM server login page. This seamed trivial to me but it turned out to be far from that.
The goal is to add the custom STATE parameter of the OAuth2 redirect link so that after the authentication is finished and the CIAM server redirects me back to my page I take back the STATE parameter which is automatically included in the successful redirect link from the CIAM server.
The Security configuration
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true,
proxyTargetClass = true)
#EnableOAuth2Client
#Order(3)
public class OAuth2LoginWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
CiamOAuth2ClientFilter oAuth2CiamClientFilter;
#Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
return new InMemoryUserDetailsManager();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/**/*.css", "/**/*.png", "/**/*.gif", "/**/*.jpg", "/h2-console/**", "/css/**",
"/img/**", "/font-awesome/**", "/fonts/**", "/js/**", "/signout","/signout/**", "/health");
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/user/**").hasRole("USER")
.antMatchers("/backoffice/**").hasRole("ADMIN")
.antMatchers("/api/**").hasRole("API")
.antMatchers(/*"/", */"/login**", "/webjars/**", "/favicon.*", "/resources/**",
"/auth/**", "/signin/**","css/**","js/**", "/signup/**", "/signout/", "/health", "/awsTest/login")
.permitAll()
.anyRequest()
.authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login/callback"))
.and()
.addFilterBefore(oAuth2CiamClientFilter.ciamFilter(), BasicAuthenticationFilter.class)
.logout()
.logoutUrl("/signout")
.logoutSuccessUrl("/logout");
}
}
The custom filter class
#Configuration
public class CiamOAuth2ClientFilter {
#Autowired
AuthorizationCodeResourceDetails oauth2CiamResourceDetails;
#Autowired
CiamOAuth2ClientProperties oauth2CiamClientProperties;
#Autowired
OAuth2ClientContext oauth2ClientContext;
#Autowired
CiamPrincipalExtractor ciamPrincipalExtractor;
#Bean
public FilterRegistrationBean oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(filter);
registration.setOrder(-100);
registration.addInitParameter("test", "trrrrrrr");
System.out.println("333333333333333333333333");
System.out.println(registration);
return registration;
}
public Filter ciamFilter() {
System.out.println("postaeget");
System.out.println(oauth2CiamClientProperties);
System.out.println(" _-------------------------------: " + oauth2CiamClientProperties.getResource().getUserInfoUri());
UserInfoTokenServices tokenService = new UserInfoTokenServices(oauth2CiamClientProperties.getResource().getUserInfoUri(), oauth2CiamResourceDetails.getClientId());
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(oauth2CiamResourceDetails, oauth2ClientContext);
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter("/login/callback");
tokenService.setRestTemplate(restTemplate);
tokenService.setPrincipalExtractor(ciamPrincipalExtractor);
filter.setRestTemplate(restTemplate);
filter.setTokenServices(tokenService);
return filter;
}
}
Application yml settings file connected with the issue
security:
oauth2:
client:
clientId: ...
clientSecret: ....
accessTokenUri: ...
userAuthorizationUri: ...
useCurrentUri: false
preEstablishedRedirectUri: https://localhost/login/callback
clientAuthenticationScheme: query
authenticationScheme: header
serverLogoutUrl: ..
postLogoutRedirectUri: https://localhost/signout
scope:
- openid
- profile
- email
- offline_access
state: TEST
resource:
userInfoUri: ...
preferTokenInfo: ...
In my case
I configure OAuth2ClientAuthenticationProcessingFilter somewhere in #Configuration:
private Filter ssoFilter() {
OAuth2ClientAuthenticationProcessingFilter facebookFilter = new OAuth2ClientAuthenticationProcessingFilter(API_LOGIN_FACEBOOK);
OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(facebook(), oauth2ClientContext);
AuthorizationCodeAccessTokenProvider authorizationCodeAccessTokenProviderWithUrl = new AuthorizationCodeAccessTokenProvider();
authorizationCodeAccessTokenProviderWithUrl.setStateKeyGenerator(new StateKeyGeneratorWithRedirectUrl());
facebookTemplate.setAccessTokenProvider(authorizationCodeAccessTokenProviderWithUrl);
facebookFilter.setRestTemplate(facebookTemplate);
UserInfoTokenServices tokenServices = new CheckedUserInfoTokenServices(
facebookResource().getUserInfoUri(), facebook().getClientId(),
facebookPrincipalExtractor, blogPreAuthenticationChecks(), blogPostAuthenticationChecks());
tokenServices.setAuthoritiesExtractor(new FacebookAuthoritiesExtractor());
tokenServices.setRestTemplate(facebookTemplate);
facebookFilter.setTokenServices(tokenServices);
facebookFilter.setAuthenticationSuccessHandler(new OAuth2AuthenticationSuccessHandler());
return facebookFilter;
}
And you can access to current request in StateKeyGeneratorWithRedirectUrl with:
RequestContextHolder.getRequestAttributes()
so you can extract Referer header for example:
public class StateKeyGeneratorWithRedirectUrl extends DefaultStateKeyGenerator {
private RandomValueStringGenerator generator = new RandomValueStringGenerator();
#Override
public String generateKey(OAuth2ProtectedResourceDetails resource) {
HttpServletRequest currentHttpRequest = getCurrentHttpRequest();
if (currentHttpRequest!=null){
String referer = currentHttpRequest.getHeader("Referer");
if (!StringUtils.isEmpty(referer)){
return generator.generate()+","+referer;
}
}
return generator.generate();
}
private static HttpServletRequest getCurrentHttpRequest(){
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof ServletRequestAttributes) {
return ((ServletRequestAttributes)requestAttributes).getRequest();
}
return null;
}
}
Next - read state from callback:
public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
public static final String DEFAULT = "/";
#Override
protected String determineTargetUrl(HttpServletRequest request,
HttpServletResponse response) {
UriComponents uriComponents = UriComponentsBuilder.newInstance()
.query(request.getQueryString())
.build();
MultiValueMap<String, String> queryParams = uriComponents.getQueryParams();
String stateEncoded = queryParams.getFirst("state");
if (stateEncoded == null) {
return DEFAULT;
}
String stateDecoded = URLDecoder.decode(stateEncoded, StandardCharsets.UTF_8);
String[] split = stateDecoded.split(",");
String redirect;
if (split.length != 2){
return DEFAULT;
} else {
return split[1];
}
}
}