Spring Boot : Oauth 2.0 : Pass access token in JSON request body - spring-boot

For Oauth2.0, the access token should be passed either in header or in request parameter. But My requirement is to somehow extract this token from request body to support existing public apis which are passing access token as a JSON field in request body.
Using ContentCachingRequestWrapper, I am able to extract the access token field from request JSON in a custom filter. This custom filter is configured to be invoked before OAuth2AuthenticationProcessingFilter. But I am not able to set it either in the request header or add it to request parameter for the
OAuth2AuthenticationProcessingFilter to be able to extract this token and authenticate.
I referred to this link Spring Security: deserialize request body twice (oauth2 processing) which had a similar requirement. This talks about overriding HttpServletRequestWrapper.
With latest versions of Spring and Spring Boot, is there any other cleaner way of doing this rather than creating a HttpServletRequestWrapper?
Or is there any way of injecting my own implementation for BearerTokenExtractor (used by OAuth2AuthenticationProcessingFilter) which can also try to get the token from request attributes?
#Component
public class CustomOAuthTokenFilter extends GenericFilterBean {
#Autowired
private ObjectMapper objectMapper;
#Override
public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
ContentCachingRequestWrapper reqWrapper = new ContentCachingRequestWrapper(httpRequest);
String requestBody = IOUtils.toString(reqWrapper.getInputStream(), java.nio.charset.StandardCharsets.UTF_8);
JsonNode requestJson = objectMapper.readTree(requestBody);
Iterator<JsonNode> jsonIter = requestJson.elements();
while (jsonIter.hasNext()){
JsonNode parent = jsonIter != null ? jsonIter.next() : null;
JsonNode hdr = parent != null ? parent.get("hdr") : null;
JsonNode accessTokenNode = hdr != null ? hdr.get("accessToken") : null;
String accessToken = accessTokenNode != null ? accessTokenNode.asText() : null;
}
chain.doFilter(reqWrapper, response);
}
}
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Autowired
private CustomOAuthTokenFilter customOAuthTokenFilter;
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/auth/token","/oauth/token", "/oauth/authorize**").permitAll().anyRequest()
.authenticated()
.and().addFilterBefore(customOAuthTokenFilter, AbstractPreAuthenticatedProcessingFilter.class);
}
}

Related

Spring authentication scheme using the authentication header

I am using a spring boot application and I have a web security config adapter set up to authenticate each request using the jwt.
I want to expand my service to allow a different api end point to be authenticated using the header. One of the services I am integrating with sends a web hook and all it sends is the request with the custom header I set it up to include. How can I set up a specific endpoint to only authenticate using a custom header?
You could use a OncePerRequestFilter to filter the requests to that endpoint and return a 401 if they are do not contain your header with the right value.
You would define your filter:
public class HeaderSecurityFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String value = request.getHeader("Token");
if(value == null || !value.equals("Secret")) {
response.sendError(401);
return;
}
chain.doFilter(request, response);
}
}
And then register it:
#Configuration
public class HeaderSecurityConfiguration {
#Bean
FilterRegistrationBean<HeaderSecurityFilter> filterRegistration() {
FilterRegistrationBean<HeaderSecurityFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new HeaderSecurityFilter());
registration.addUrlPatterns("/some/path/*");
return registration;
}
}
Which would require the header of Token be present with a value of Secret for anything under /some/path/*.
You would also need to ensure through your oauth configuration that you open up access to /some/path/*.

Passin Parameters from Filter to Business Service in SpringBoot

I have 3 REST services which are reading some common header parameters on the request. I need to use that parameters on my business services. instead of reading that common header parameters on each web service controller (#RestController), Is it possible to read that headers on request filter and make it available on the business services ? If yes, are there any examples to do this ?
You can get request object
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
and access the headers in business services using request object.
Like #Nitin suggest you can pass the request object from your controllers to your services and read the header there. There is no problem with that.
If you still want to read it in a filter and have it available in any #Service you can do as follows:
#Component
#Order(1)
public class HeaderReaderFilter implements Filter {
#Autowired
private HeaderDataHolder headerDataHolder;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
headerDataHolder.setHeaderContent(httpRequest.getHeader("header_field"));
chain.doFilter(request, response);
}
}
#RequestScope
#Component
public class HeaderDataHolder {
private String headerContent;
public String getHeaderContent() {
return headerContent;
}
public void setHeaderContent(String headerContent) {
this.headerContent = headerContent;
}
}
And then have the HeaderDataHolder #Autowired in your service classes. Notice the necessary #RequestScope so you have a different bean for each request.

Keycloak spring boot microservices

i have a few java micro services deployed on open shift . all of them are protected by a api-gateway application which uses keycloak for authentication & Authorization.
Down stream services need to log which user perform certain actions.
in my api-gateway application properties i have already set zuul.sensitiveHeaders to empty
zuul.sensitiveHeaders:
i can see bearer token in the downstream applications .
but how do i get the principal/user from token as downstream applications don't have keycloak dependency in gradle. ( if i add the dependency , i need to reconfigure realm and other properties ) .. is this the right way to do ?
i also tried adding a filter in api-gateway to separately set the user_name in header
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
System.out.println(" Filter doFilter "+req.getUserPrincipal());
if(req.getUserPrincipal() != null ){
res.setHeader("MYUSER",req.getUserPrincipal()==null?"NULL":req.getUserPrincipal().getName());
}
chain.doFilter(request, response);
}
But when i try to get the header in downstream microservices is null.
I wouldn't recommend doing this, or assuming that your non-web facing apps are completely secure. Realistically you should be re-validating the bearer token.
What you need is a zuul filter to add a header to the request. This is mostly from memory and you could update the filter to check if it should filter or not, that the request doesn't already contain an expected header etc.
#Component
public class AddUserHeader extends ZuulFilter {
private static final Logger LOG = LoggerFactory.getLogger(AddUserHeader.class);
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 0;
}
#Override
public boolean shouldFilter{
return true;
}
#Override
public Object run() {
RequestContext.getCurrentContext().addZuulRequestHeader("MYUSER", SecurityContextHolder.getAuthentication().getPrincipal().getName());
return null;
}

How to modify Http headers before executing request in spring boot mvc

I have multiple rest endpoints. userId (http header) is common in all endpoints. I want to apply a logic, let say set its default value if not provided or trim it if is provided in request before request enters the method (for eg: heartbeat). How can we achieve this in spring boot rest mvc.
#RestController
public class MyResource {
#RequestMapping(value = "/heartbeat", method= RequestMethod.GET)
public String heartbeat (#RequestHeader (value="userId", required=false) String userId)
{
...
}
}
Can you try this?
#Configuration
#Slf4j
public class HttpHeaderModificationConfig implements Filter {
private static final String HEADER_DEMO_NAME = "name";
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
final HttpServletRequest req = (HttpServletRequest) request;
// modify HTTP Request Header
final HttpServletRequestWrapper reqWrapper = new HttpServletRequestWrapper(req) {
#Override
public String getHeader(String name) {
if (HEADER_DEMO_NAME.equals(name)) {
return "Changed";
}
return super.getHeader(name);
}
};
log.info("After Changed with Name {}", reqWrapper.getHeader(HEADER_DEMO_NAME));
chain.doFilter(reqWrapper, response);
}
}
One option i can think of using Filters i.e. create a Filter to check for the header userId and trim it if its present or provide it a default value if its not present and store it in another header lets say customUserId. In your controller you can comfortably use your custom headers to be sure of that it will be valid as per your requirements. You can refer to the below article for creating a generic bean
https://www.baeldung.com/spring-boot-add-filter

How to get/set the principal and session attributes from Spring 4 stomp websocket methods

I'm doing experiments with Spring 4 websockets and stomp, and I have a hard time figuring out how to get/set the current user and other session attributes in a message handling method annotated with #MessageMapping.
The documentation says that the message handling methods can take a Principal as argument, and I found that the principal is retrieved by Spring by calling getUserPrincipal() on the native socket session, and then associated with the socket session, but I haven't found any way to easily customize this behavior, other than writing a servlet filter and wrap the original request into a wrapper returning the principal found in my cookie.
So my questions are:
How to manually set the principal to the socket session, when the client connects (I have this information thanks to a custom cookie, and I don't use Spring security)?
If 1 is not possible, how to add additional attributes to the socket session when the client connects?
How to access the socket session and its attributes from a message handling method?
Is there a way to access the login and passcode sent by the browser at connection time. They seem to be completely ignore by Spring and not accessible.
UPDATE: With Spring 4.1 it is possible to set the user on the handshake for #1 from above. Per the Spring documentation you can create a new class which extends DefaultHandshakeHandler and override the determineUser method. Additionally you can also create a security filter which sets the principal as well if you have a token. I have implemented the second one myself and I include some sample code for both below.
For #2 and #3 I do not think that it is possible still. For #4 Spring intentionally ignores these per the documentation here.
SAMPLE CODE FOR DefaultHandshakeHandler SUBCLASS:
#Configuration
#EnableWebSocketMessageBroker
public class ApplicationWebSocketConfiguration extends AbstractWebSocketMessageBrokerConfigurer {
public class MyHandshakeHandler extends DefaultHandshakeHandler {
#Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler,
Map<String, Object> attributes) {
// add your own code to determine the user
return null;
}
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/myEndPoint").setHandshakeHandler(new MyHandshakeHandler());
}
}
SAMPLE CODE FOR SECURITY FILTER:
public class ApplicationSecurityTokenFilter extends GenericFilterBean {
private final static String AUTHENTICATION_PARAMETER = "authentication";
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (servletRequest instanceof HttpServletRequest) {
// check to see if already authenticated before trying again
Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
if ((existingAuth == null) || !existingAuth.isAuthenticated()) {
HttpServletRequest request = (HttpServletRequest)servletRequest;
UsernamePasswordAuthenticationToken token = extractToken(request);
// dump token into security context (for authentication-provider to pick up)
if (token != null) { // if it exists
SecurityContextHolder.getContext().setAuthentication(token);
}
}
}
filterChain.doFilter(servletRequest,servletResponse);
}
private UsernamePasswordAuthenticationToken extractToken( HttpServletRequest request ) {
UsernamePasswordAuthenticationToken authenticationToken = null;
// do what you need to extract the information for a token
// in this example we assume a query string that has an authenticate
// parameter with a "user:password" string. A new UsernamePasswordAuthenticationToken
// is created and then normal authentication happens using this info.
// This is just a sample and I am sure there are more secure ways to do this.
if (request.getQueryString() != null) {
String[] pairs = request.getQueryString().split("&");
for (String pair : pairs) {
String[] pairTokens = pair.split("=");
if (pairTokens.length == 2) {
if (AUTHENTICATION_PARAMETER.equals(pairTokens[0])) {
String[] tokens = pairTokens[1].split(":");
if (tokens.length == 2) {
log.debug("Using credentials: " + pairTokens[1]);
authenticationToken = new UsernamePasswordAuthenticationToken(tokens[0], tokens[1]);
}
}
}
}
}
return authenticationToken;
}
}
// set up your web security for the area in question
#Configuration
public class SubscriptionWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/myEndPoint**","/myEndPoint/**").and()
.addFilterBefore(new ApplicationSecurityTokenFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic() // leave this if you want non web browser clients to connect and add an auth header
.and()
.csrf().disable();
}
}
** NOTE: ** DO NOT declare your filter as a Bean. If you do then it will also be picked up (at least using Spring Boot) in the generic filters so it will fire on every request.
This is impossible for the time being (Spring 4.0). An issue has been opened (and considered) at Spring: https://jira.springsource.org/browse/SPR-11228

Resources