Appendix02.BuildAppGateway - haymant/trading GitHub Wiki

Build App Gateway

This document is to brief how to setup basic dev framework to support AntDesignPro front end.

Tech stack

App gateway is based on the following tech stack:

  • JDK 11
  • Lombok: reduce boilerplate code
  • Spring boot: Java framework
  • Gradle: Build tool
  • Spring Web: RESTFul
  • Sprint security: for authentication
  • Postgres database
  • MyBatis: database persistence framework, instead of ORM like EclipseLink/Hibernate
  • Redis: cache sessions

IntelliJ with multiple projects in one workspace

An IntelliJ's module here will be a Spring boot project, and IntelliJ project is a workspace containing all Spring boot projects. Only in this section, we'd use IntelliJ's concepts, i.e.:

  1. Module -> Spring boot project
  2. Project -> Spring boot project collection

We may put all Java/Js modules under one directory, e.g., /trading/appgateway for this app gateway, and /trading/tradui for UI consuming gateway services. To make IDE IntelliJ UI concise, we may create an empty project at /trading/ and then open menu File -> Project Structure and select Modules at left panel to add /trading/appgateway to the project.

Once all required modules are added into the IntelliJ project, we can add Run/Debug configuration for each module, and start them.

scaffolding

  1. Open https://start.spring.io/
  2. Configure details of the project
  3. Add dependencies base on Section Tech stack, except for Redia Reactive
  4. Generate and download the project

Authentication

JWT or Spring session

We would either reply on 'old school' authentication with sessions maintained at server side, or exploit JWT based client side session management. JWT could reduce the server workload for each call, so that server doesn't need to query database (or Redis or cache, etc.) to validate the session, with some caveats:

  • hard to revoke
  • security for jwt at rest/in transit
  • protocol/implementation issues for different JWT client libs

For the sample code, we will use Spring session + Redis. For testing, we could have a Redis listening to remote clients:

redis-server --protected-mode no

Add Redis Dependencies

build.gradle should contains:

dependencies {
...
	implementation 'org.springframework.boot:spring-boot-starter-data-redis'
	implementation 'org.springframework.session:spring-session-data-redis'
	implementation 'org.springframework.session:spring-session-core'

Add these configures to application.properties:

spring.session.store-type=redis
spring.redis.host=192.168.1.150
spring.redis.port=6379

With these configuration, and Spring security session integrated, we could see session data saved on Redis:

redis-cli
> KEYS *
1) "spring:session:xxxxx"
...

Custom Spring security form login

To decouple fronend UI server from backend App server with different domain/port, there are some concerns to take care:

  • CORS: need to allow UI server's Origin
  • Session: Spring session id is Set-Cookie in HTTP header, to be enabled by allow credentials
  • API: customize form login url to align with convention
  • Success/Failure handle: customize the behavior not to redirect

These could be configured by the sample WebSecurityConfigurerAdapter.

@Configuration
@EnableWebSecurity
public class SpringSecurityConfigurationFormSession extends WebSecurityConfigurerAdapter{

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues()).and()
                .cors().and()
                .csrf().disable()
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS,"/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().successHandler((request, response, authentication) -> {
                        response.setContentType("application/json;charset=utf-8");
                        PrintWriter out = response.getWriter();
                        response.setStatus(200);
                        Map<String,Object> map =new HashMap<>();
                        map.put("status","ok");
                        map.put("currentAuthority", "user");
                        ObjectMapper objectMapper=new ObjectMapper();
                        out.write(objectMapper.writeValueAsString(map));
                        out.flush();
                        out.close();
                })
                .failureHandler((request, response, exception) -> {
                    response.setContentType("application/json;charset=utf-8");
                    PrintWriter out = response.getWriter();
                    response.setStatus(401);
                    Map<String,Object> map = new HashMap<>();
                    map.put("status","error");
                    map.put("currentAuthority","guest");
                    ObjectMapper objectMapper =new ObjectMapper();
                    out.write(objectMapper.writeValueAsString(map));
                    out.flush();
                    out.close();
                }).loginProcessingUrl("/api/login/account");
    }

Minimal API set

To allow AntDesignPro to login, we need three API to be added.

  • /api/login/account
  • /api/currentUser
  • /api/notices
@RequestMapping("/api")
@RestController
public class NoticeController {

    @AllArgsConstructor
    public class Notice {
        @Getter
        private final String id;
        @Getter
        private final String avatar;
        @Getter
        private final String title;
        @Getter
        private final String datetime;
        @Getter
        private final String type;
    }

    @GetMapping (path = "/notices")
    public List<Notice> getCurrentUser(Principal principal) {
        return Arrays.asList(new Notice("000000001",
                "https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png",
                "你收到了 14 份新周报",
                "2017-08-09",
                "notification"));
    }
}
⚠️ **GitHub.com Fallback** ⚠️