Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Java Wrapping Up

API that serves Thymeleaf pages (for a web portal) and JSON responses for iOS and Android clients

I need a web backend that serves a web portal (primarily for admin purposes) as well as the iOS and Android versions of our mobile app. I have no intention of exposing the API beyond these components -- i.e., "our clients". What's the best way to go about this? Note that both the website and mobile clients will be CRUD'ing the same resources. Does it make sense to go with a Controller implementation (as in the giflib-hibernate and todotoday projects)? I'm thinking of having base controllers that do business logic but then either return a Thymleaf URI or serialized JSON (for the mobile clients). Is this even possible?

Thanks in advance!

Or maybe a hybrid approach would be better? In other words, one projects that uses Controllers for the web portal but the abstracted controller logic in this course for the mobile API? Then (necessarily) using different endpoints for either. Thoughts?

4 Answers

Craig Dennis
STAFF
Craig Dennis
Treehouse Teacher

I think you should watch the Build a REST API in Spring workshop. Just what you are looking for.

Craig, I watched and enjoyed the workshop. In addition to the building an API like what you made, I also want to serve Thymeleaf URI's for a web portal. That requires exposing the Controllers, I think? So, in short, I'm wondering how I serve JSON to mobile clients but also Thymeleaf URI's to a web portal.

Craig Dennis
Craig Dennis
Treehouse Teacher

Take a look at the Spring-A-Gram example project. There are a couple of talks from Greg Turnquist showing this off that will probably help solidify things. Hope to get to these Spring microservices soon here!

Juan Felipe Calle
PLUS
Juan Felipe Calle
Courses Plus Student 21,013 Points

Hi @SeanThomas, how did you manage your project ? I'm trying to implement a similar project and i want to hear about your experience.

Thanks

Binyamin Friedman
Binyamin Friedman
14,615 Points

You can have your api on the path /api/v1 like Craig did and then you can simply add controllers like you would on a regular project. Or do you want something different?

You need to make regular controllers as in any Spring course, but you have to alter the WebSecurity class with the following one. Just remember

package com.cieplastronaswiata.core;


import com.cieplastronaswiata.user.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.repository.query.spi.EvaluationContextExtension;
import org.springframework.data.repository.query.spi.EvaluationContextExtensionSupport;
import org.springframework.security.access.expression.SecurityExpressionRoot;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

@EnableWebSecurity
public class MultiHttpSecurityConfiguration {

    @Configuration
    @Order(1)
    public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
        protected void configure(HttpSecurity http) throws Exception {
            // TODO: REMOVE HTTP BASICS AND ENABLE CSRF FOR REAL-LIFE APPLICATION.
            http
                    .antMatcher("/api/**")
                    .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .httpBasic()
                    .and()
                    .csrf().disable();
        }
    }

    @Configuration
    public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

        @Autowired
        private UserService userService;

        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
        }

        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder(10);
        }

        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring().antMatchers("/assets/**");
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .authorizeRequests()
                    .anyRequest().hasRole("USER")
                    .and()
                    .formLogin()
                    .loginPage("/login")
                    .permitAll()
                    .successHandler(loginSuccessHandler())
                    .failureHandler(loginFailureHandler())
                    .and()
                    .logout()
                    .permitAll()
                    .logoutSuccessUrl("/login")
                    .and()
                    .csrf();
        }

        public AuthenticationSuccessHandler loginSuccessHandler() {
            return (request, response, autherntication) -> response.sendRedirect("/");
        }

        public AuthenticationFailureHandler loginFailureHandler() {
            return (request, response, autherntication) -> {
                request.getSession().setAttribute("flash", new FlashMessage("Incorrect username and/or password. Please try again.", FlashMessage.Status.FAILURE));
                response.sendRedirect("/login");
            };
        }

        @Bean
        public EvaluationContextExtension securityExtension() {
            return new EvaluationContextExtensionSupport() {
                @Override
                public String getExtensionId() {
                    return "security";
                }

                @Override
                public Object getRootObject() {
                    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                    return new SecurityExpressionRoot(authentication) {};
                }
            };
        }
    }
}