How to Protect a Resource using Spring Security OAuth2 and MITREID Connect Introspect? - spring

We are building a REST resource server (a Java sample app) which we plan to protect using an identity propagation mechanism defined by RFC7662 available from the MITREID Connect project. We tested both configuration methods, the XML setup, as well as the annotation based setup added to the resource server class (see sample code attached below).
Our testing shows successful initialization of the Spring Security routine, but we did not succeed to trigger the Bearer token passage through the authorization header. The request and resource executes successfully but no token parsing and introspect validation took place. Please check the configuration settings and logs attached below.
Support is welcome to isolate the missing wire between the components (Spring Security, Spring Oauth2 and Mitreid Connect Introspect).
Setup file: spring-security.xml
<?xml version="1.0" encoding="UTF-8"?>
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-4.0.xsd
http://www.springframework.org/schema/security/oauth2
http://www.springframework.org/schema/security/spring-security-oauth2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd">
<sec:http pattern="/css/**" security="none" />
<sec:http pattern="/js/**" security="none" />
<sec:http auto-config="true" use-expressions="true"
disable-url-rewriting="true" entry-point-ref="oauthAuthenticationEntryPoint"
pattern="/rest/service/sample/restService">
<sec:custom-filter before="PRE_AUTH_FILTER" ref="resourceServerFilter" />
</sec:http>
<sec:authentication-manager alias="authenticationManager">
</sec:authentication-manager>
<!-- Begin OAuth2 Introspect configuration -->
<bean id="oauthAuthenticationEntryPoint"
class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
<property name="realmName" value="W3IDRealm" />
</bean>
<oauth:resource-server id="resourceServerFilter"
token-services-ref="introspectingService" />
<bean id="introspectingService"
class="org.mitre.oauth2.introspectingfilter.IntrospectingTokenService">
<property name="introspectionConfigurationService" ref="staticIntrospectionConfigurationService">
</property>
</bean>
<!-- <oauth:resource
id="protectedResource"
access-token-uri="${oidc.tokenEndpointUri}"
client-secret="${oidc.clientSecret}"
client-id="${oidc.clientId}"></oauth:resource> -->
<bean
class="org.mitre.oauth2.introspectingfilter.service.impl.StaticIntrospectionConfigurationService"
id="staticIntrospectionConfigurationService">
<property name="introspectionUrl" value="${oidc.introspectEndpointUri}" />
<property name="clientConfiguration">
<bean class="org.mitre.oauth2.model.RegisteredClient">
<property name="clientId" value="${oidc.clientId}" />
<property name="clientSecret" value="${oidc.clientSecret}" />
</bean>
</property>
<!-- <property name="introspectionAuthorityGranter">
<bean class="org.mitre.oauth2.introspectingfilter.SimpleIntrospectionAuthorityGranter">
<property name="authorities">
<value>ROLE_API</value>
</property>
</bean>
</property> -->
</bean>
resource.java
package com.red.sampleoidcservice;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
#Controller
#EnableWebSecurity
#Configuration
#EnableResourceServer
public class RestController {
private static final Logger logger = LoggerFactory.getLogger(RestController.class);
#RequestMapping(value = "/restService", method = RequestMethod.POST)
public #ResponseBody String restService(HttpServletRequest request, HttpServletResponse respose) {
logger.info("Calling rest service");
String requestToString = request.toString();
String headerType = request.getHeader("Content-Type");
String headerAuth = request.getHeader("Authorization");
Map map = request.getParameterMap();
String attributes = request.getAttributeNames().toString();
// String someParam = request.getParameter("someParam");
return "{\"status\":\"OK\"}";
}
protected static class ResourceServer extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers("/rest/service/sample/restService").and().authorizeRequests()
.anyRequest().access("#oauth2.hasScope('read')");
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("W3IDRealm");
}
}
}
post.java
// HTTP POST request
private void sendPost(String token) throws Exception {
try {
token = "blablabla";
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
} };
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
SSLConnectionSocketFactory f = new SSLConnectionSocketFactory(sc, new String[] { "TLSv1.2" }, null,
org.apache.http.conn.ssl.SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
httpClient = HttpClients.custom().setSSLSocketFactory(f).build();
HttpPost postRequest = new HttpPost("https://localhost:9444/rest/service/sample/restService");
postRequest.addHeader("Content-Type", "application/x-www-form-urlencoded");
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair("client_id", clientId));
formparams.add(new BasicNameValuePair("client_secret", clientSecret));
formparams.add(new BasicNameValuePair("token", token));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, "utf-8");
postRequest.setEntity(entity);
postRequest.setHeader("Authorization", "Bearer " + token + "");
HttpResponse response = httpClient.execute(postRequest, new BasicHttpContext());
int statusCode = response.getStatusLine().getStatusCode();
logger.info("HTTP status code : " + statusCode);
} catch (Exception e) {
logger.error(e.getMessage());
}
}
Trace:
INFO : org.springframework.web.servlet.DispatcherServlet - FrameworkServlet 'appServlet': initialization completed in 5872 ms
DEBUG: org.springframework.web.servlet.DispatcherServlet - Servlet 'appServlet' configured successfully
DEBUG: org.springframework.web.context.support.StandardServletEnvironment - Adding [servletConfigInitParams] PropertySource with lowest search precedence
DEBUG: org.springframework.web.context.support.StandardServletEnvironment - Adding [servletContextInitParams] PropertySource with lowest search precedence
DEBUG: org.springframework.web.context.support.StandardServletEnvironment - Adding [jndiProperties] PropertySource with lowest search precedence
DEBUG: org.springframework.web.context.support.StandardServletEnvironment - Adding [systemProperties] PropertySource with lowest search precedence
DEBUG: org.springframework.web.context.support.StandardServletEnvironment - Adding [systemEnvironment] PropertySource with lowest search precedence
DEBUG: org.springframework.web.context.support.StandardServletEnvironment - Initialized StandardServletEnvironment with PropertySources [servletConfigInitParams,servletContextInitParams,jndiProperties,systemProperties,systemEnvironment]
DEBUG: org.springframework.web.filter.DelegatingFilterProxy - Initializing filter 'springSecurityFilterChain'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.security.filterChainProxy'
DEBUG: org.springframework.web.filter.DelegatingFilterProxy - Filter 'springSecurityFilterChain' configured successfully
DEBUG: org.springframework.web.servlet.DispatcherServlet - DispatcherServlet with name 'appServlet' processing POST request for [/rest/service/sample/restService]
DEBUG: org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Looking up handler method for path /restService
DEBUG: org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Returning handler method [public java.lang.String com.red.sampleoidcservice.RestController.restService(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)]
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'restController'
INFO : com.red.sampleoidcservice.RestController - Calling rest service
DEBUG: org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Written [{"status":"OK"}] as "text/plain" using [org.springframework.http.converter.StringHttpMessageConverter#6912d551]
DEBUG: org.springframework.web.servlet.DispatcherServlet - Null ModelAndView returned to DispatcherServlet with name 'appServlet': assuming HandlerAdapter completed request handling
DEBUG: org.springframework.web.servlet.DispatcherServlet - Successfully completed request
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'delegatingApplicationListener'
Solution Found
Configuration with Annotation
/*******************************************************************************
* Copyright 2014 The MITRE Corporation
* and the MIT Kerberos and Internet Trust Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package com.RED.sampleoidcservice;
import java.io.IOException;
import java.security.Principal;
import java.util.Locale;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.mitre.oauth2.introspectingfilter.IntrospectingTokenService;
import org.mitre.oauth2.introspectingfilter.service.impl.StaticIntrospectionConfigurationService;
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
import org.mitre.oauth2.model.RegisteredClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.authentication.BearerTokenExtractor;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.ModelAndView;
#Controller
#EnableWebSecurity
#Configuration
#EnableResourceServer // [2]
#ComponentScan({ "com.RED.sampleoidcservice" })
public class ResourceServer extends ResourceServerConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(ResourceServer.class);
#Value("${oidc.jwks.keys}")
private String jwksString;
#Value("${oidc.introspectEndpointUri}")
private String introspectURL;
#Value("${oidc.clientId}")
private String clientId;
#Value("${oidc.clientSecret}")
private String clientSecret;
IntrospectingTokenService introspectTokenService = new IntrospectingTokenService();
#RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView modelHome(Locale locale, Principal p) {
logger.info("Initializing service resource");
ModelAndView model = new ModelAndView("/home.tiles");
return model;
}
#RequestMapping(value = "/jwk", method = RequestMethod.GET, produces = "application/json")
public #ResponseBody String jwk() {
return jwksString;
}
#RequestMapping(value = "/restService", method = RequestMethod.POST)
public #ResponseBody String restService(HttpServletRequest request, HttpServletResponse respose) {
logger.info("Calling rest service");
String requestToString = request.toString();
String headerType = request.getHeader("Content-Type");
String headerAuth = request.getHeader("Authorization");
String token = headerAuth.split(" ")[1];
// introspectTokenService.readAccessToken(token);
Map map = request.getParameterMap();
String attributes = request.getAttributeNames().toString();
// String someParam = request.getParameter("someParam");
return "{\"status\":\"OK\"}";
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatcher(new OAuthRequestedMatcher())
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated();
// http.addFilterBefore(new TokenExtractorFilter(), BasicAuthenticationFilter.class).requestMatchers()
// .antMatchers("/rest/service/sample/restService").and().authorizeRequests().anyRequest()
// .access("ROLE_API");
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("W3IDRealm");
resources.tokenExtractor(new BearerTokenExtractor());
StaticIntrospectionConfigurationService introspectConfig = new StaticIntrospectionConfigurationService();
introspectConfig.setIntrospectionUrl(introspectURL);
RegisteredClient client = new RegisteredClient();
client.setClientId(clientId);
client.setClientSecret(clientSecret);
client.setTokenEndpointAuthMethod(AuthMethod.NONE);
introspectConfig.setClientConfiguration(client);
introspectTokenService.setIntrospectionConfigurationService(introspectConfig);
resources.tokenServices(introspectTokenService);
}
private static class OAuthRequestedMatcher implements RequestMatcher {
public boolean matches(HttpServletRequest request) {
String auth = request.getHeader("Authorization");
// Determine if the client request contained an OAuth Authorization
boolean haveOauth2Token = (auth != null) && auth.startsWith("Bearer");
boolean haveAccessToken = request.getParameter("access_token")!=null;
return haveOauth2Token || haveAccessToken;
}
}
class TokenExtractorFilter extends OncePerRequestFilter implements Filter, InitializingBean {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//UserDetails details = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
BearerTokenExtractor bte = new BearerTokenExtractor();
String mytoken = bte.extract(request).toString();
logger.info("Filter activated");
}
}
}

I've published a solution using an annotation based configuration, which helped us to debug the flow details using Java breakpoints. The OAuth2 Bearer token validation using /introspect is working now. Thanks to all debug support delivered by MITREID. See the code solution posted in the final section of the report, shown above.

Related

I'm using Cognito + Spring security. There are any way to use authorization?

I'm using spring Security and cognito for authentication and authorization. I entered some custom roles via aws IAM and I would like to know if there was a method to grant controlled access to resources. On the web I found some that set the cognito:groups as a role and used that, but they use deprecated classes and methods on it. Is there any way to do this with the latest versions?
I tried to create a class:
package com.projectname.name.Configurations;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import java.util.Collection;
import java.util.Collections;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class CognitoAccessTokenConverter implements Converter<Jwt, AbstractAuthenticationToken> {
private final JwtGrantedAuthoritiesConverter defaultGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
public CognitoAccessTokenConverter() {
}
#Override
public AbstractAuthenticationToken convert(#NonNull final Jwt jwt) {
Collection<GrantedAuthority> authorities = Stream
.concat(defaultGrantedAuthoritiesConverter.convert(jwt).stream(), extractResourceRoles(jwt).stream())
.collect(Collectors.toSet());
return new JwtAuthenticationToken(jwt, authorities);
}
private static Collection<? extends GrantedAuthority> extractResourceRoles(final Jwt jwt) {
Collection<String> userRoles = jwt.getClaimAsStringList("cognito:groups");
//System.out.println("\n!!!!!!!!" +userRoles +"!!!!!!!!!!\n"); DEBUG
if (userRoles != null)
return userRoles
.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toSet());
return Collections.emptySet();
}
}
/*
import java.util.Map;
import java.util.Set;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
import org.springframework.stereotype.Component;
import org.springframework.
#Component
public class CognitoAccessTokenConverter extends OAuth2AuthorizationCodeRequestAuthenticationToken{
private static final String COGNITO_GROUPS = "cognito:groups";
private static final String SPRING_AUTHORITIES = "authorities";
private static final String COGNITO_USERNAME = "username";
private static final String SPRING_USER_NAME = "user_name";
}
#Component
public class CognitoAccessTokenConverter extends {
// Note: This the core part.
private static final String COGNITO_GROUPS = "cognito:groups";
private static final String SPRING_AUTHORITIES = "authorities";
private static final String COGNITO_USERNAME = "username";
private static final String SPRING_USER_NAME = "user_name";
#SuppressWarnings("unchecked")
#Override
public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
if (claims.containsKey(COGNITO_GROUPS))
((Map<String, Object>) claims).put(SPRING_AUTHORITIES, claims.get(COGNITO_GROUPS));
if (claims.containsKey(COGNITO_USERNAME))
((Map<String, Object>) claims).put(SPRING_USER_NAME, claims.get(COGNITO_USERNAME));
return super.extractAuthentication(claims);
}
} */
how can I use this conversion in my spring security configuration?
package com.SSDProject.Booked.Configurations;
import java.io.*;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
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.oauth2.client.endpoint.DefaultRefreshTokenTokenResponseClient;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration {
#Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/admin").hasAuthority("max")
.requestMatchers("/**").permitAll()
.anyRequest().authenticated()
)
.oauth2Login();
return http.build();
}
Help me, I tried to implements it and search everywhere. Some helps? Have you an idea?
I've recently created the same PoC using SpringBoot 2.x and Java 17.
In my case I don't have any deprecation warning from my IDE, here my example:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf()
.and()
.requestMatchers().antMatchers("/api/**")
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.userDetailsService(null)
.oauth2ResourceServer(oauth2 ->
oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(grantedAuthoritiesExtractor())));
return http.build();
}
private JwtAuthenticationConverter grantedAuthoritiesExtractor() {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwt -> {
String[] scopes;
if (jwt.getClaims().containsKey("cognito:groups")) {
scopes = ((JSONArray) jwt.getClaims().get("cognito:groups")).toArray(new String[0]);
} else {
scopes = ((String) jwt.getClaims().getOrDefault("scope", "")).split(" ");
}
return Arrays.stream(scopes)
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase(Locale.ROOT)))
.collect(Collectors.toSet());
}
);
return jwtAuthenticationConverter;
}
Exactly which line is deprecated in your code? And what version of resource-server are you using? For me spring-boot-starter-oauth2-resource-server is 2.7.5.
This is actually not an answer but I don't have the reputation for add comment to the question :)
Is your Spring application serving server-side rendered UI (Thymeleaf, JSF or alike) or is it a REST API (#RestController or #Controller with #ResponseBody)?
In second case, your app is a resource-server. OAuth2 login should be handled by clients, not resource-server: clients acquire access token and send it as Authorization header to resource-server.
In my answer to Use Keycloak Spring Adapter with Spring Boot 3, I explain how to configure both Spring resource-servers and clients. All you'll have to adapt for Cognito are issuer URI and the private-claims name to extract authorities from.
Configuring a resource-server with authorities mapped from cognito:groups using my starters (thin wrappers around spring-boot-starter-oauth2-resource-server) can be as simple as:
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
<version>6.0.10</version>
</dependency>
#Configuration
#EnableMethodSecurity
public class SecurityConfig {
}
com.c4-soft.springaddons.security.issuers[0].location=https://cognito-idp.Region.amazonaws.com/your user pool ID/.well-known/openid-configuration
com.c4-soft.springaddons.security.issuers[0].authorities.claims=cognito:groups
# This is probably too permissive but can be fine tuned (origins, headers and methods can be defined per path)
com.c4-soft.springaddons.security.cors[0].path=/**
If your application is only a client, my starters won't be of any help.
If your app is both a resource-server and a client (serves JSON payloads and server-side rendered UI with, for instance, Thymeleaf), then you'll have to define a second SecurityFilterChain bean. Details in the answer linked earlier.
If you don't want to use my starters, then you'll have to write quite some java conf. Details in the previously linked answer, again.

Unable to Login in Spring Boot

I am new to spring boot, i am trying to login in my application. I am facing some issues.
I am not able to login. It cant authenticate to login with my credential and return with message login invalid.
I want the user to be authenticate when they try to access client site (eg localhost:8080/). I also want to implement logout when user dont valid on a link file.
Here is my main application
package oidc.controller;
import eu.olympus.client.interfaces.UserClient;
import eu.olympus.model.Attribute;
import eu.olympus.model.AttributeIdentityProof;
import eu.olympus.model.Operation;
import eu.olympus.model.Policy;
import eu.olympus.model.Predicate;
import eu.olympus.model.exceptions.AuthenticationFailedException;
import eu.olympus.model.exceptions.ExistingUserException;
import eu.olympus.model.exceptions.OperationFailedException;
import eu.olympus.model.exceptions.TokenGenerationException;
import eu.olympus.model.exceptions.UserCreationFailedException;
import eu.olympus.model.server.rest.IdentityProof;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import oidc.model.AttributeContainer;
import oidc.model.ChangeAttributesRequest;
import oidc.model.ChangePasswordRequest;
import oidc.model.CreateUserRequest;
import oidc.model.DeleteAccountRequest;
import oidc.model.LoginRequest;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.view.RedirectView;
#Controller
public class OidcController {
private static final Logger logger = LoggerFactory.getLogger(OidcController.class);
#Autowired
UserClient userClient;
#Autowired
Policy policy;
// Login
#RequestMapping("/login")
public String login(Model model, #RequestParam String redirect_uri, #RequestParam String state, #RequestParam String nonce, HttpServletRequest request) {
request.getSession().setAttribute("redirectUrl", redirect_uri);
request.getSession().setAttribute("state", state);
request.getSession().setAttribute("nonce", nonce);
LoginRequest loginRequest = new LoginRequest();
model.addAttribute("loginRequest", loginRequest);
policy.setPolicyId(nonce);
return "/login";
}
#RequestMapping("/loginFailed")
public String login(Model model) {
LoginRequest loginRequest = new LoginRequest();
model.addAttribute("loginRequest", loginRequest);
model.addAttribute("loginError", true);
return "/login";
}
#RequestMapping("/loginPage")
public String loginPage(Model model) {
LoginRequest loginRequest = new LoginRequest();
model.addAttribute("loginRequest", loginRequest);
model.addAttribute("hasCreated", false);
return "/login";
}
#PostMapping("/authenticate")
public RedirectView authenticate(LoginRequest loginRequest, Model model, HttpServletRequest request) throws AuthenticationFailedException, TokenGenerationException {
try {
// TODO We need to get the audience somehow?
policy.getPredicates().add(new Predicate("audience", Operation.REVEAL, new Attribute("olympus-service-provider")));
String token = userClient.authenticate(loginRequest.getUsername(), loginRequest.getPassword(), policy, null, "NONE");
model.addAttribute("username", loginRequest.getUsername());
model.addAttribute("token", token);
String redirectUrl = (String) request.getSession().getAttribute("redirectUrl");
String state = (String) request.getSession().getAttribute("state");
return new RedirectView(redirectUrl + "#state=" + state + "&id_token=" + token + "&token_type=bearer");
} catch (Exception e) {
e.printStackTrace();
if (ExceptionUtils.indexOfThrowable(e, AuthenticationFailedException.class) != -1) {
return new RedirectView("/loginFailed", true);
} else {
throw e;
}
} finally {
userClient.clearSession();
}
}
here is login Request
package oidc.model;
import lombok.Getter;
import lombok.Setter;
/**
* A container for a login request
*/
#Getter
#Setter
public class LoginRequest {
private String username;
private String password;
}
I suggest you to use Spring Security. It is a dependency and you can add it via your build tool such as Maven, Gradle etc. After studying your code what I can see is that you are trying to build security mechanism from scratch.
I wouldn't advice you to do that unless you have a high motivation factor to do so. If you can use Spring Security, it is very powerful and equipped with all the features you are looking for. You can easily overcome user authentication, authorization and even it can provide a default login page.
When it comes to authentication, you can have few types of user stores such as in-memory user store, JDBC user store, LDAP user store or even your own custom user store. Apart from username and password authentication via a GUI, you are able to do sys-to-sys authentication. You can easily achieve JWT token authentication with few steps just like adding a filter and minor configuration.
It is very difficult to cover and give the whole source code as an answer here but I will provide you a sample code such that you can get a glimpse of it. Please be advice that the below mentioned code is purely for demonstration purpose and can modify it to your standards.
Spring Security Configuration Class
package com.example.sankalpaspringbootcicd.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* Security configuration class.
* Created date - 2021/08/02
*/
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
/**
* Authentication
* Responsible for configuring user-store.
* Overridden from WebSecurityConfigurerAdapter level.
* #param theAuthenticationManager AuthenticationManagerBuilder
* #throws Exception - Exception
*/
#Override
public void configure(AuthenticationManagerBuilder theAuthenticationManager) throws Exception {
theAuthenticationManager.inMemoryAuthentication()
//Admin user
.withUser("admin")
.password("super")
.roles("ADMIN")
.and()
//Normal user
.withUser("user")
.password("password")
.roles("USER");
}
/**
* Authorization
* Responsible for security configuration.
* Overridden from WebSecurityConfigurerAdapter level.
* #param theHttpSecurity HttpSecurity
* #throws Exception - Exception
*/
#Override
public void configure(HttpSecurity theHttpSecurity) throws Exception {
theHttpSecurity.csrf().disable();
theHttpSecurity
.authorizeRequests()
.antMatchers("/welcome/**").access("permitAll")
.antMatchers("/user/**").hasRole("ADMIN")
.anyRequest().fullyAuthenticated() //All requests should be authenticated
.and().headers().frameOptions().sameOrigin() //To allow H2-Console
.and().httpBasic();
}
/**
* Method constructing a password encoder bean.
* Constructs 'NoOpPasswordEncoder'.
* #return PasswordEncoder
*/
#Bean
public PasswordEncoder getPasswordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
This is a very simple example and I will take you through each method. The first method is configure(AuthenticationManagerBuilder theAuthenticationManager) and what it does is basically creating you a in-memory user store.
The second method is configure(HttpSecurity theHttpSecurity) and it does allow you to do customizations to your security behaviour. It does allow some routes for everyone, restrict some routes for only with users with particular roles, allows route for H2 embedded database console, disable CSRF (Cross-Site Request Forgery) etc. This will prompt you a default login page as well. You can further add logics related to your login and logout mechanisms here as well.
The third method is PasswordEncoder getPasswordEncoder() and it does create a password encoder bean and put it in Spring Application Context to be used anytime.

Mule Authentication via Spring doesn't return 403

I'm trying to create an authentication for a Rest service on Mule 3. If I pass some user/password Spring intercept it and I can authenticate on my java class. However if is passed a wrong user/password and I return null Mule doesn't respond a 403 error. How can I could make it works?
mule xml:
<spring:beans>
<spring:bean class="br.com.arizona.custom_authentication_provider.CustomAuthenticationProvider" id="customAuthenticationProvider"/>
</spring:beans>
<spring:beans>
<security:authentication-manager alias="secAuthSample">
<security:authentication-provider ref="customAuthenticationProvider"/>
</security:authentication-manager>
</spring:beans>
<spring-security:security-manager>
<spring-security:delegate-security-provider name="memory-provider" delegate-ref="secAuthSample"/>
</spring-security:security-manager>
<flow name="testFlow">
<http:listener config-ref="HTTP_Listener_Configuration" path="/teste" doc:name="HTTP"/>
<spring-security:http-security-filter realm="mule-realm" securityProviders="memory-provider" />
<response>
<logger message="response" level="INFO" doc:name="Logger"/>
</response>
<logger message="passed" level="INFO" doc:name="Logger"/>
</flow>
CustomAuthenticationProvider.java:
package br.com.arizona.custom_authentication_provider;
import java.util.ArrayList;
import java.util.List;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
System.out.println(name);
System.out.println(password);
if (name.equals("admin") && password.equals("system")) {
List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
Authentication auth = new UsernamePasswordAuthenticationToken(name, password, grantedAuths);
return auth;
} else {
//throw new BadCredentialsException("Bad Credentials");
return null;
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Did you add the security filters: https://developer.mulesoft.com/docs/display/current/Configuring+the+Spring+Security+Manager#ConfiguringtheSpringSecurityManager-SecurityFilters ?
What does it return instead, do you get a 200?
Things depend a bit on the rest of your flow. You could manually check if the authentication was ok, and set the http.status to 403 in the outbound property scope with a message property transformer. Thats normally how you handle your other responsecodes while working with REST.
I assume your authentication mechanism are working, just that the 403 is missing on a incorrect login.

Mule not displaying/reading custom messages from ReloadableResourceBundles

I can't figure out how to get my custom message from the Spring Errors object. It seems like I have stuff wired up properly. I've seen examples where the default message is actually the custom message, but that seems a hack and contrary to the documentation.
As I understand it, using ValidationUtils, I could have the following:
ValidationUtils.rejectIfEmpty(errors, "myfield", "myfield.required", "Column 'myfield' is required.");
I also think I understand that the second parameter "myfield.required" is in essence the key to a properties file entry.
So, I have this properties file sitting my src/main/resouces folder named validation-messages_en_US.properties with this entry:
myfield.required=Column 'myfield' is a custom message !
My Spring Bean definition:
<spring:beans>
<context:component-scan base-package="com.mycompany.myproject" />
<spring:bean id="messageSource" name="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<spring:property name="basenames" value="classpath:validation-messages"/>
</spring:bean>
</spring:beans>
My Validator:
import java.util.List;
import org.apache.log4j.Logger;
import org.mule.api.annotations.param.Payload;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Component;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import com.mycompany.myproject.vsd.request.VSDRequest;
#Component
public class VSDRequestValidator implements Validator {
private static final Logger logger = Logger.getLogger(VSDRequestValidator.class);
#Autowired
private MessageSource messageSource;
#Override
public boolean supports(Class<?> clazz) {
return VSDRequest.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object object, Errors errors) {
VSDRequest request = (VSDRequest) object;
ValidationUtils.rejectIfEmpty(errors, "myfield", "myfield.required", "Column 'myfield' is required.");
if (errors.hasErrors()) {
List<ObjectError> objErrors = errors.getAllErrors();
for (ObjectError e : objErrors) {
logger.info(e.getDefaultMessage());
}
}
}
/**
* Receive <code>payload</code> to perform validations on.
*
* #param object
*/
public void validate(#Payload Object object) {
if (object == null) {
throw new IllegalArgumentException("The supplied [Validator] is required and must not be null.");
}
if (!supports(object.getClass())) {
throw new IllegalArgumentException("The supplied [Validator] must support the validation of [VSDRequest] instances.");
}
BeanPropertyBindingResult errors = new BeanPropertyBindingResult((VSDRequest) object, "VSDRequest");
logger.info(messageSource.getMessage("myfield.required", null, null)); // This get the message I want
validate(object, errors);
}
}
My MessageSource properly injects. I injected it into my class just to check it and know it's being recognized. I shouldn't even need to do that if Spring is reading the ReloadableResourceBundle.
Does the getDefaultMessage() return either the custom message or the default message? Maybe I'm looking at this all wrong?

#Autowired does not work with my SOAP handler class. Throws a nullPointerException?

This is my SOAP Handler class to generate security service handlers for a CRM. Everything was working fine as I hard coded my credentials - Username & Password. Now I tried to remove the hard-coding by defining the credentials in a properties file and autowiring it in this class. This method is not working and Spring throws a NullPointerExc (autowiring not happening I guess!) everytime I try to access my CRM. Why does #Autowired not work here while it works perfectly well my #Service, #Controller classes? Here is my code:
package com.myPortlet.crmService;
import java.util.Properties;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPFactory;
import javax.xml.soap.SOAPHeader;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class ECMClientHeaderHandler implements SOAPHandler<SOAPMessageContext> {
final static Logger logger = LoggerFactory
.getLogger(ECMClientHeaderHandler.class);
private static final String AUTH_NS = "http://schemas.xmlsoap.org/ws/2002/12/secext";
private static final String AUTH_PREFIX = "wss";
public ECMClientHeaderHandler() {
}
public boolean handleFault(SOAPMessageContext smc) {
return true;
}
public void close(MessageContext mc) {
}
#Autowired
private Properties applicationProperties;
public boolean handleMessage(SOAPMessageContext smc) {
boolean direction = ((Boolean) smc
.get(SOAPMessageContext.MESSAGE_OUTBOUND_PROPERTY))
.booleanValue();
String userName = applicationProperties.getProperty("myCRM.userName"); /*previously hard-coded*/
String password = applicationProperties.getProperty("myCRM.password"); /*previously hard-coded*/
logger.info("This is USERNAME:"+ userName);
logger.info("This is PASSWORD:"+ password);
if (direction) {
try {
SOAPEnvelope envelope = smc.getMessage().getSOAPPart()
.getEnvelope();
SOAPFactory soapFactory = SOAPFactory.newInstance();
// WSSecurity <Security> header
SOAPElement wsSecHeaderElm = soapFactory.createElement(
"Security", AUTH_PREFIX, AUTH_NS);
SOAPElement userNameTokenElm = soapFactory.createElement(
"UsernameToken", AUTH_PREFIX, AUTH_NS);
SOAPElement userNameElm = soapFactory.createElement("Username",
AUTH_PREFIX, AUTH_NS);
userNameElm.addTextNode(userName);
SOAPElement passwdElm = soapFactory.createElement("Password",
AUTH_PREFIX, AUTH_NS);
passwdElm.addTextNode(password);
userNameTokenElm.addChildElement(userNameElm);
userNameTokenElm.addChildElement(passwdElm);
// add child elements to the root element
wsSecHeaderElm.addChildElement(userNameTokenElm);
// create SOAPHeader instance for SOAP envelope
SOAPHeader sh;
if(envelope.getHeader()==null){
logger.info("SOAPHeader null.Add header");
sh = envelope.addHeader();
}else{
logger.info("SOAPHeader already present");
sh = envelope.getHeader();
}
// add SOAP element for header to SOAP header object
sh.addChildElement(wsSecHeaderElm);
} catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
}
return true;
}
public java.util.Set<QName> getHeaders() {
return null;
}
}
The "myCRM.userName" & "myCRM.password" is defined in my application.properties file. And the classPath of application.properties is defined in applicationContext.xml:
<util:properties id="applicationProperties" location="classpath:/i18n/application.properties"/>
What is going wrong?
The Spring Context has to be made aware that it needs to load some autowired components on a specific class. The #Controller annotation and a reference in the spring-servlet.xml ensure just that.
You can try adding this to your spring-servlet.xml
<context:component-scan base-package="com.myPortlet.crmService" />
Also Add a #Controller annotation in your class to initiate auto wiring at server startup. Else your Properties instance will be null everytime you try to access it.
I had a similar problem trying injecting a dependency in my #webservice class. I solved it adding the method below in the class (org.springframework.web.context.support.SpringBeanAutowiringSupport;)
#PostConstruct
#WebMethod(exclude = true)
public void init() {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
}

Resources