You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@camel.apache.org by Tobias Letschka <tl...@covernet.de> on 2019/12/13 12:26:42 UTC
Secure Undertow RestRoute with SpringSecurity - Username + Password
(Basic Header) - Java DSL
Hi,
Use Case:
I used undertow and https and want to authorize at web service with username and password defined in application.propterties.
Source Code
(RestControllerRouteBuilder.java)
// Setup
restConfiguration().apiContextRouteId("swagger")
.component("undertow")
.host("{{http.address}}")
.port("{{http.port}}")
.bindingMode(RestBindingMode.json)
.scheme("https")
.dataFormatProperty("prettyPrint", "true")
.apiContextPath("/api-doc")
.enableCORS(true)
.corsHeaderProperty("Access-Control-Allow-Headers", "Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, Authorization")
.apiContextPath("/v1/swagger")
.apiProperty("api.title", "Order import API")
.apiProperty("api.version", "1.0.0")
.apiProperty("cors", "true")
.endpointProperty("chunked", "true")
;
// Route
rest("/v1")
.put("/order").description("Order JSON import")
.type(Order.class)
.param().name("Authorization").type(RestParamType.header).description("The Auth token").endParam()
.param().name("body").type(RestParamType.body).description("The \"" + ROOT_ELEMENT + "\" as JSON").endParam()
.consumes(MediaType.APPLICATION_JSON_VALUE)
.produces(MediaType.APPLICATION_JSON_VALUE)
.responseMessage().code(HttpStatus.SC_ACCEPTED).message("<h2>No Content</h2><p>The request has been accepted for processing, but the processing has not been completed.</p><p>The request might or might not be eventually acted upon, and may be disallowed when processing occurs.</p>").endResponseMessage()
.responseMessage().code(HttpStatus.SC_BAD_REQUEST).message("<h2>Bad Request</h2><p>Response Body Contains a JSON Object with an error message in field \"message\"</p><h1>Example</h1><p>{\"message\" : \"Request body is missing or not a valid JSON\"}</p>").endResponseMessage()
.responseMessage().code(HttpStatus.SC_UNAUTHORIZED).message("<h2>Not Authorized</h2><p>No Authorization header provided</p>").endResponseMessage()
.responseMessage().code(HttpStatus.SC_FORBIDDEN).message("<h2>Not Authorized</h2><p>You are not allowed to do this action</p>").endResponseMessage()
.responseMessage().code(HttpStatus.SC_INTERNAL_SERVER_ERROR).message("<h2>Internal Server Error</h2><p>Please contact the Service Provider</p><p>Response Body Contains a JSON Object with an message in field \"message\"</p><h1>Example</h1><p>{\"message\" : \"Validation of 'Authorization' token failed, client management service cannot be reached.\"}</p>").endResponseMessage()
.to("direct:processRequest")
;
In ServiceRouteBuilder.java
from("direct:processRequest").routeId("responseRoute")
.log(INFO, "Request received at order import endpoint")
// check if "Authorization" header exists...
.choice()
// ... if not
.when(header(AUTHORIZATION).isNull())
.log(WARN, "No Authorization header provided")
.setHeader(HTTP_RESPONSE_CODE, constant(HttpStatus.SC_UNAUTHORIZED))
.setBody(constant(new Document().append("message", "No Authorization Header").toJson()))
.when().body(Order.class, order -> order == null)
.setHeader(HTTP_RESPONSE_CODE, constant(HttpStatus.SC_UNAUTHORIZED))
.setBody(constant(new Document().append("message", "Request Body is empty").toJson()))
.otherwise()
.setProperty(REQUEST_BODY).body(Order.class, order -> new Document().append("order",order.getOrder()))
.to("direct:validateToken")
.endChoice()
.end()
;
from("direct:validateToken").routeId("authorizationValidationRoute")
.bean(AuthServiceHandler.class)
.to("direct:processBody")
;
from("direct:processBody").routeId("jsonBodyProcessorRoute")
// save the Order JSON into MongoDB
.setBody().exchangeProperty(REQUEST_BODY)
.to(MONGODB_INSERT)
.log(INFO, "Imported Order to MongoDB")
// send the OID of the MongoDB document to the next service via ActiveMQ
.setBody().header(OID)
.convertBodyTo(String.class)
.to(QUEUE_PROCESS)
.log(INFO, "Processing done, message sent to " + QUEUE_PROCESS)
.log(DEBUG, "OID of MongoDB document containing Order JSON: ${header.CamelMongoOid}")
;
And try to validate by username and password
AuthServiceHandler.java
public class AuthServiceHandler {
@Autowired
private Logger logger;
private static CustomAuthenticationProvider am = new CustomAuthenticationProvider();
@Handler
public void handler(Exchange exchange) throws Exception {
// get the username and password from the HTTP header
// http://en.wikipedia.org/wiki/Basic_access_authentication
String base64 = exchange.getIn().getHeader(AUTHORIZATION, String.class).replace("Basic", "").trim();
String userpass = new String(Base64.decodeBase64(base64));
String[] tokens = userpass.split(":");
// create an Authentication object
//UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(tokens[0], tokens[1]);
Authentication request = new UsernamePasswordAuthenticationToken(tokens[0], tokens[1]);
Authentication result = am.authenticate(request);
SecurityContextHolder.getContext().setAuthentication(result);
logger.debug("Successfully authenticated. Security context contains: " +
SecurityContextHolder.getContext().getAuthentication());
}
}
BasicWebSecurityConfigurerAdapter,java
public class BasicWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**").hasRole("USER")
.anyRequest()
.authenticated()
.and()
.formLogin();
}
@Override
public void configure(AuthenticationManagerBuilder builder)
throws Exception {
builder.authenticationProvider(new CustomAuthenticationProvider());
}
}
CustomAuthenticationProvider.java:
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
private Logger logger;
private static List <User> users = new ArrayList();
public CustomAuthenticationProvider() {
users.add(new User(„userxxx", „passwordxxx", „xxx_USER"));
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName();
Object credentials = authentication.getCredentials();
if (!(credentials instanceof String)) {
return null;
}
String password = credentials.toString();
Optional <User> userOptional = users.stream()
.filter(u -> u.match(name, password))
.findFirst();
if (!userOptional.isPresent()) {
throw new BadCredentialsException("Authentication failed for " + name);
}
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority(userOptional.get().role));
Authentication auth = new
UsernamePasswordAuthenticationToken(name, password, grantedAuthorities);
return auth;
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
private class User {
private String name;
private String password;
private String role;
public User(String name, String password, String role) {
this.name = name;
this.password = password;
this.role = role;
}
public boolean match(String name, String password) {
return this.name.equals(name) && this.password.equals(password);
}
}
It works but I am trying to find a better solution.
Regards, Tobias