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