Securing a GWT app with a request param to be checked in a crosscontext attribute - gwt

My application is supposed to received a request parameter called sessionId which is supposed to be used to lookup for a crosscontext attribute.
I was looking at Spring Security to implement this and I think already have a good implementation of my AuthenticationProvider :
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
ServletContext servletContext = request.getSession().getServletContext();
String sessionId = request.getParameter("sessionId");
if (sessionId != null) {
ServletContext sc = request.getSession().getServletContext();
Object obj = sc.getContext("/crosscontext").getAttribute(sessionId);
if (obj != null) {
// return new Authentication
}
} else {
logger.error("No session id provided in the request");
return null;
}
if (!GWT.isProdMode()) {
// return new Authentication
} else {
logger.error("No session id provided in the request");
return null;
}
}
Now, what I would like to do is to configure Spring Security to not prompt for a user name and password, to let it reach this authentication provider call the authenticate method.
How can I achieve this ?

I fixed my issue by reviewing the design of my security and going for something closer to the preauthenticated mechanisms that are already provided by Spring Security.
I extended 2 components of Spring Security.
First one is an AbstractPreAuthenticatedProcessingFilter, usually his role is to provide the principal provided in the headers. In my case, I retrieve the header value and search in the context shared between 2 application for an attribute that corresponds to that header and returns it as principal :
public class MyApplicationPreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter {
private static final Logger logger = Logger.getLogger(MyApplicationPreAuthenticatedProcessingFilter.class);
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
if (MyApplicationServerUtil.isProdMode()) {
String principal = request.getHeader("MY_HEADER");
String attribute = (String) request.getSession().getServletContext().getContext("/crosscontext").getAttribute(principal);
logger.info("In PROD mode - Found value in crosscontext: " + attribute);
return attribute;
} else {
logger.debug("In DEV mode - passing through ...");
return "";
}
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return null;
}
}
The other component is the AuthenticationProvider which will just check if the authentication contains a principal when it runs in prod mode (GWT prod) :
public class MyApplicationAuthenticationProvider implements AuthenticationProvider {
private static final Logger logger = Logger.getLogger(MyApplicationAuthenticationProvider.class);
public static final String SESSION_ID = "sessionId";
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (MyApplicationServerUtil.isProdMode()) {
if (StringUtils.isNotEmpty((String) authentication.getPrincipal())) {
logger.warn("Found credentials: " + (String) authentication.getPrincipal());
Authentication customAuth = new CustomAuthentication("ROLE_USER");
customAuth.setAuthenticated(true);
return customAuth;
} else {
throw new PreAuthenticatedCredentialsNotFoundException("Nothing returned from crosscontext for sessionId attribute ["
+ (String) authentication.getPrincipal() + "]");
}
} else {
Authentication customAuth = new CustomAuthentication("ROLE_USER");
customAuth.setAuthenticated(true);
return customAuth;
}
}
#Override
public boolean supports(Class<?> authentication) {
return PreAuthenticatedAuthenticationToken.class.isAssignableFrom(authentication);
}
}
I understand that it might not be the most secure application. However, it will already be running in a secure environment. But if you have suggestions for improvement, they're welcome !

Related

SSO between App and webview inside the app

My user signs into my app using Amazon Cognito using this plugin.
I also have a spring boot application ui, secured by cognito as well.
At some point in my app flow, i want to show a webview of the spring boot application to let the user configure additional stuff.
How do i do it without having the user sign in again?
Would it be bad practice if i created an endpoint called /login/{username}/{password} that uses the SecurityContextHolder to sign the user in and redirect to /home?
I finally got it working.
First i logged in, and made my code stop somewhere using the debugger, so i could look up the SecurityContextHolder.getContext().getAuthentication(). My Authentication object is of type OAuth2AuthenticationToken. I took a close look at it, and decided to replicate it.
I did so inside a custom AuthenticationManager, and returned my OAuth2AuthenticationToken in the overriden authenticate method.
CustomAuthenticationManager.java
#Component
public class CustomAuthenticationManager implements AuthenticationManager {
#Bean
protected PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String token = ((Jwt)authentication.getPrincipal()).getTokenValue();
if (token == null)
throw new BadCredentialsException("Invalid token");
return convertAccessToken(token);
}
public OAuth2AuthenticationToken convertAccessToken(String accessToken){
Jwt decode = Tools.parseToken(accessToken);
List<GrantedAuthority> authorities = new ArrayList<>();
for (String s : ((String[]) decode.getClaims().get("cognito:groups"))) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + s));
}
Map<String, Object> claims = decode.getClaims();
OidcIdToken oidcIdToken = new OidcIdToken(decode.getTokenValue(), decode.getIssuedAt(), decode.getExpiresAt(), claims);
DefaultOidcUser user = new DefaultOidcUser(authorities, oidcIdToken, "email");
return new OAuth2AuthenticationToken(user, authorities, "cognito");
}
}
Also i put this in a static Tools.java
public static Jwt parseToken(String accessToken) {
DecodedJWT decode = com.auth0.jwt.JWT.decode(accessToken);
HashMap<String, Object> headers = new HashMap<>();
headers.put("alg", decode.getHeaderClaim("alg").asString());
headers.put("kid", decode.getHeaderClaim("kid").asString());
HashMap<String, Object> claims = new HashMap<>();
decode.getClaims().forEach((k, v) -> {
switch(k){
case "cognito:roles":
case "cognito:groups":
claims.put(k, v.asArray(String.class));
break;
case "auth_time":
case "exp":
case "iat":
claims.put(k, v.asLong());
break;
default:
claims.put(k, v.asString());
break;
}
});
return new Jwt(accessToken, decode.getIssuedAt().toInstant(), decode.getExpiresAt().toInstant(), headers, claims);
}
Then i created two endpoints. One that is my "login page", and one that my filter goes to. So in my login page i take in an access token, store it in the sesion, then redirect to my other endpoint that pasess through the filter.
TokenLoginController.java
#Component
#RestController
public class TokenLoginController {
#GetMapping(value="/login/token/{token}")
#PermitAll
public void setSession(#PathVariable("token") String token, HttpSession session, HttpServletResponse response) throws IOException {
session.setAttribute("access_token", token);
response.sendRedirect("/login/token");
}
#GetMapping(value="/login/token")
#PermitAll
public void setSession() {
}
}
The filter extends AbstractAuthenticationProcessingFilter and looks up the access token from the session, creates the OAuth2AuthenticationToken, and authenticates with it.
StickyAuthenticationFilter.java
public class StickyAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public StickyAuthenticationFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) {
super(defaultFilterProcessesUrl);
setAuthenticationManager(authenticationManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws AuthenticationException, IOException, ServletException {
String access_token = (String)servletRequest.getSession().getAttribute("access_token");
if (access_token != null) {
JwtAuthenticationToken authRequest = new JwtAuthenticationToken(Tools.parseToken(access_token));
return getAuthenticationManager().authenticate(authRequest);
}
throw new RuntimeException("Invalid access token");
}
}
And finally, my SecurityConfig ties it all together like this:
#EnableWebSecurity
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends VaadinWebSecurity {
private final ClientRegistrationRepository clientRegistrationRepository;
public SecurityConfig(ClientRegistrationRepository clientRegistrationRepository) {
this.clientRegistrationRepository = clientRegistrationRepository;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/login/token/*", "/login/token").permitAll().and()
.addFilterBefore(new StickyAuthenticationFilter("/login/token", new CustomAuthenticationManager()), BearerTokenAuthenticationFilter.class)
.oauth2ResourceServer(oauth2 -> oauth2.jwt())
.authorizeRequests()
.antMatchers("/user/**")
.authenticated();
super.configure(http);
setOAuth2LoginPage(http, "/oauth2/authorization/cognito");
http.oauth2Login(l -> l.userInfoEndpoint().userAuthoritiesMapper(userAuthoritiesMapper()));
}
#Override
public void configure(WebSecurity web) throws Exception {
// Customize your WebSecurity configuration.
super.configure(web);
}
#Bean
public GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
Optional<OidcUserAuthority> awsAuthority = (Optional<OidcUserAuthority>) authorities.stream()
.filter(grantedAuthority -> "ROLE_USER".equals(grantedAuthority.getAuthority()))
.findFirst();
if (awsAuthority.isPresent()) {
if (awsAuthority.get().getAttributes().get("cognito:groups") != null) {
mappedAuthorities = ((JSONArray) awsAuthority.get().getAttributes().get("cognito:groups")).stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toSet());
}
}
return mappedAuthorities;
};
}
}

Spring Cloud Gateway altering form data does not work

I defined this GatewayFilter:
EDIT More context information:
What I would like to achieve is to avoid the client providing its credentials to get an access token from an authorization server.
The client sends a POST request with user's credentials (username/password) and the gateway adds all complementary information like scope, client_id, grant_type etc... before forwarding the request to the authorization server.
#Component
public class OAuth2CredentialsAppenderGatewayFilterFactory extends AbstractGatewayFilterFactory<OAuth2CredentialsAppenderGatewayFilterFactory.Config> {
public OAuth2CredentialsAppenderGatewayFilterFactory() {
super(Config.class);
}
#Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
ServerHttpRequest.Builder requestBuilder = exchange.getRequest().mutate();
if ("x-www-form-urlencoded".equals(request.getHeaders().getContentType().getSubtype())) {
//This code is not executed, the call of formData.put does not do anything, even a breakpoint is not reached!
if (request.getMethod().equals(HttpMethod.POST)) {
exchange.getFormData().map(formData -> {
formData.put("key1", List.of("value1"));
formData.put("key2", List.of("value2"));
formData.put("key3", List.of("value3"));
return formData;
});
}
//This part of code works well, the header is added to the forwarded request
requestBuilder.header(HttpHeaders.AUTHORIZATION,
"Basic " + Base64Utils.encodeToString((this.uiClientId + ":" + this.uiClientSecret).getBytes()));
}
return chain.filter(exchange.mutate().request(requestBuilder.build()).build());
};
}
}
I use the filter like this:
- id: keycloak_token_route
uri: http://localhost:8180
predicates:
- Path=/kc/token
filters:
- OAuth2CredentialsAppender
- SetPath=/auth/realms/main/protocol/openid-connect/token
- name: RequestRateLimiter
args:
key-resolver: "#{#userIpKeyResolver}"
redis-rate-limiter.replenishRate: 20
redis-rate-limiter.burstCapacity: 30
denyEmptyKey: false
The filter is well invoked but altering the incoming request body does not work.
I am new to the reactive world so I am a bit confused, any help will be appreciated.
For those who would like to do the same thing, this is how I solved my problem. Again I am not an expert of Reactive programming, I am still learning it so it might be a better answer.
#Component
public class OAuth2CredentialsAppenderGatewayFilterFactory extends AbstractGatewayFilterFactory<OAuth2CredentialsAppenderGatewayFilterFactory.Config> {
#Value("${uiservice.clientId}")
private String uiClientId;
#Value("${uiservice.clientSecret}")
private String uiClientSecret;
public OAuth2CredentialsAppenderGatewayFilterFactory() {
super(Config.class);
}
#Override
public GatewayFilter apply(Config config) {
return (ServerWebExchange exchange, GatewayFilterChain chain) -> {
ServerHttpRequest request = exchange.getRequest();
ServerHttpRequest.Builder requestBuilder = exchange.getRequest().mutate();
if (nonNull(request.getHeaders().getContentType()) && request.getHeaders().getContentType().equals(MediaType.APPLICATION_FORM_URLENCODED)) {
if (requireNonNull(request.getMethod()).equals(HttpMethod.POST)) {
//Use this filter to modify the request body
ModifyRequestBodyGatewayFilterFactory.Config requestConf = new ModifyRequestBodyGatewayFilterFactory.Config()
.setContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.setRewriteFunction(String.class, String.class, this.completeRequestBody());
requestBuilder.header(HttpHeaders.AUTHORIZATION, base64Encoding(this.uiClientId, this.uiClientSecret));
return new ModifyRequestBodyGatewayFilterFactory().apply(requestConf).filter(exchange.mutate().request(requestBuilder.build()).build(), chain);
}
}
return chain.filter(exchange.mutate().request(requestBuilder.build()).build());
};
}
/** Add some config params if needed */
public static class Config {
}
/** Complete request by adding required information to get the access token. Here we can get 2 type of token: client_credentials or password. If the param client_only=true we should get a client_credentials token */
private RewriteFunction<String, String> completeRequestBody() {
return (ServerWebExchange ex, String requestBody) -> {
requireNonNull(requestBody, "Body is required");
//if body contains only this, we should get a client_credentials token
var idForClientCredentialsOnly = "client=ui&client_only=true";
String finalRequestBody;
var joiner = new StringJoiner("");
if (idForClientCredentialsOnly.equalsIgnoreCase(requestBody)) {
joiner.add("grant_type=").add("client_credentials");
}
else {
joiner.add(requestBody);
if (!containsIgnoreCase(requestBody, "grant_type")) {
joiner.add("&grant_type=").add("password");
}
}
if (!containsIgnoreCase(requestBody, "scope")) {
joiner.add("&scope=").add("uiclient");//I use Keycloak so I specify the scope to get some extra information
}
finalRequestBody = joiner.toString();
return Mono.just(isBlank(finalRequestBody) ? requestBody : finalRequestBody);
};
}
}

How can we conditionally route to a different URL in Spring Cloud Gateway? Is there a reference sample?

Trying to change the exchange target URL conditionally. Is there a way this can be achieved in Spring Cloud Gateway?
To elaborate, upon inspecting a particular cookie value in the incoming request, I would like to route it to a different URL.
We do something similar with request headers here. We have an abstract filter that handles setting the uri correctly, you just have to determine the uri from the ServerWebExchange.
public class CookieToRequestUriGatewayFilterFactory extends
AbstractChangeRequestUriGatewayFilterFactory<AbstractGatewayFilterFactory.NameConfig> {
private final Logger log = LoggerFactory
.getLogger(RequestHeaderToRequestUriGatewayFilterFactory.class);
public RequestHeaderToRequestUriGatewayFilterFactory() {
super(NameConfig.class);
}
#Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(NAME_KEY);
}
#Override
protected Optional<URI> determineRequestUri(ServerWebExchange exchange,
NameConfig config) {
String cookieValue = exchange.getRequest().getCookies().getFirst(config.getName());
String requestUrl = determineUrlFromCookie(cookieValue);
return Optional.ofNullable(requestUrl).map(url -> {
try {
return new URL(url).toURI();
}
catch (MalformedURLException | URISyntaxException e) {
log.info("Request url is invalid : url={}, error={}", requestUrl,
e.getMessage());
return null;
}
});
}
}
It would be up to you to implement determineUrlFromCookie().

How do I extract information from an incoming JWT that was generated by an external service?

How do I extract information from an incoming JWT that was generated by an external service? (Okta)
I need to perform a database lookup of user information based on one of the fields in the JWT. (I also want method-level security based on the scope of the JWT.)
The secret seems to be in using an AccessTokenConverter to extractAuthentication() and then use that to lookup UserDetails. I am stuck because every example I can find includes setting up an Authorization Server, which I don't have, and I can't tell if the JwtAccessTokenConverter will work on the Resource Server.
My resource server runs and handles requests, but my custom JwtAccessTokenConverter is never getting called during incoming requests;
All of my requests are coming in with a principal of anonymousUser.
I am using Spring 5.1.1.
My Resource Server Configuration
#Configuration
#EnableResourceServer
public class OauthResourceConfig extends ResourceServerConfigurerAdapter {
#Value("${oauth2.audience}")
String audience;
#Value("${oauth2.baseUrl}/v1/keys")
String jwksUrl;
#Override
public void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.authorizeRequests()
.anyRequest().authenticated()
.antMatchers("/api/**").permitAll();
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources
.tokenServices(tokenServices())
.resourceId(audience);
}
#Primary
#Bean
public DefaultTokenServices tokenServices() throws Exception {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
return tokenServices;
}
#Bean
public TokenStore tokenStore() {
return new JwkTokenStore(jwksUrl, accessTokenConverter());
}
#Bean
public AccessTokenConverter accessTokenConverter() {
return new CustomJwtAccessTokenConverter();
}
}
My Custom Access Token Converter
public class CustomJwtAccessTokenConverter extends JwtAccessTokenConverter {
#Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
OAuth2Authentication authentication = super.extractAuthentication(map);
Authentication userAuthentication = authentication.getUserAuthentication();
if (userAuthentication != null) {
LinkedHashMap userDetails = (LinkedHashMap) map.get("userDetails");
if (userDetails != null) {
... Do the database lookup here ...
Collection<? extends GrantedAuthority> authorities = userAuthentication.getAuthorities();
userAuthentication = new UsernamePasswordAuthenticationToken(extendedPrincipal,
userAuthentication.getCredentials(), authorities);
}
}
return new OAuth2Authentication(authentication.getOAuth2Request(), userAuthentication);
}
}
And my Resource
#GET
#PreAuthorize("#oauth2.hasScope('openid')")
public Response getRecallsByVin(#QueryParam("vin") String vin,
#QueryParam("page") Integer pageNumber,
#QueryParam("pageSize") Integer pageSize) {
List<VehicleNhtsaCampaign> nhtsaCampaignList;
List<OpenRecallsDto> nhtsaCampaignDtoList;
SecurityContext securityContext = SecurityContextHolder.getContext();
Object principal = securityContext.getAuthentication().getPrincipal();
... More irrelevant code follows ...
First of all, the #PreAuthorize annotation isn't doing anything. If I change it to #PreAuthorize("#oauth2.hasScope('FooBar')") it still lets the request in.
Secondly, I need to grab other information off the JWT so I can do a user lookup in my database. I thought that by adding the accessTokenConverter() in the resource server config, the JWT would be parsed and placed into the securityContext.getAuthentication() response. Instead all I'm getting is "anonymousUser".
UPDATE: I later found out the data I need is coming in a custom header, so I don't need to extract anything from the JWT. I was never able to validate any of the suggested answers.
Are you using Spring Boot?
The Spring Security 5.1 has support for JWT access tokens. For example, you could just supply a new JwtDecoder:
https://github.com/okta/okta-spring-boot/blob/spring-boot-2.1/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2ResourceServerAutoConfig.java#L62-L84
You can create a filter that validates and sets token to SecurityContextHolder. This is what I have done in my project using jsonwebtoken dependency:
public class JWTFilter extends GenericFilterBean {
private String secretKey = 'yoursecret';
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String jwt = resolveToken(httpServletRequest);
if (validateToken(jwt)) {
Authentication authentication = getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(servletRequest, servletResponse);
}
private String resolveToken(HttpServletRequest request){
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody();
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
public boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
} catch (MalformedJwtException e) {
} catch (ExpiredJwtException e) {
} catch (UnsupportedJwtException e) {
} catch (IllegalArgumentException e) {
}
return false;
}
}
You can then access your token from SecurityContextHolder.
For cleaner way to access token fields, I have created POJO models of my token from http://www.jsonschema2pojo.org/

Web API 2 use Windows Authentication for public users

How do I use Windows Authentication in WEB API for internal users who will also be on the public network? The REST API will be public facing and will need to authenticate intranet users as well as internet users. Basically, anybody not on Active Directory won't be able to access it and one more AD groups will be authorized.
The REST service at the moment has a security filter to validate token using attribute filter.
public class RestAuthorizeAttribute : AuthorizeAttribute
{
private const string SecurityToken = "token";
public override void OnAuthorization(HttpActionContext actionContext)
{
if (Authorize(actionContext))
{
return;
}
HandleUnauthorizedRequest(actionContext);
}
private bool Authorize(HttpActionContext actionContext)
{
try
{
HttpRequestMessage request = actionContext.Request;
//Extract Token from the Request. This will work for all.
// E.g \api\Facilitiles\Token\298374u23lknndsjlkfds==
// \api\Ward\123\Token\298374u23lknndsjlkfds==
string path = request.RequestUri.LocalPath;
int indexOfToken = path.IndexOf(SecurityToken) + SecurityToken.Length + 1;
string token = path.Substring(indexOfToken);
bool isValid = SecurityManager.IsTokenValid(token, IpResolver.GetIp(request),request.Headers.UserAgent.ToString());
return isValid;
}
catch (Exception ex)
{
string av = ex.Message;
return false;
}
}
}
This is then applied to specific controllers like this:
[RestAuthorize]
[RoutePrefix("api/patient")]
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class PatientDetailsController : ApiController
{
PatientDetailsRetriever _patientDetailsRetriever;
// GET: api/patient/meds/personId/{personId}/token/{token}
[Route("meds/personId/{personId}/token/{token}")]
[HttpGet]
public HttpResponseMessage GetMeds(Int64 personId, string token)
{
List<Medication> meds;
.....
The client generates the token which includes username, password and domain and among other things.
Enabling Windows Authentication in IIS (web.config) will be enough to validate local users. But how does this work when the user is outside the network and sends in the credentials?
I have found the answer on this SO post.
//create a "principal context" - e.g. your domain (could be machine, too)
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YOURDOMAIN"))
{
// validate the credentials
bool isValid = pc.ValidateCredentials("myuser", "mypassword");
}