A Little guide of Spring Web(MVC) with Custom Security for REST API
Tags: java, spring, security, tutorial
Published: 2021-09-10 05:30:24

## Getting started

If you want security feature in your REST API App, USE Spring security. BETTER THAN PLAIN `Interceptor` for WebMVC or `WebFilter` for Webflux your spring app.
I'll show you small tutorial with code. Let's get started.

### Step 1. `AuthenticationToken`

Prepare your Authentication Token. If your authentication data is nothing special, just use `UsernamePasswordAuthenticationToken`. Are you using JWT? No worries. or, you can implement class with `AbstractAuthenticationToken` for identity your authentication token.

### Step 2. `AuthenticationFilter`

When client request your app, You will get authentication infomation by request entities. like header, request body, etc.

How to make about getting authentication infomation from HTTP request? then you need to define a servlet filter. usually implements of `GenericFilterBean` or `OncePerRequestFilter`. I chose `OncePerRequestFilter` because it can implements method with `HttpServletRequest` and `HttpServletResponse` rather then more abstract interface like `ServletRequest` and `ServletResponse`.

```java
@Component
public class ApiAuthenticationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String principal = "Your Principal(ex. User ID) from request";
        String credentials = "Your Credentials(ex. User Password) from request";
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(principal , credentials, /* DEFAULT ROLES */);
        SecurityContextHolder.getContext().setAuthentication(token);
        filterChain.doFilter(request, response);
    }
}
```

### Step 3. `AuthenticationProvider`

Next, You will need verify the authentication infomation. you need implement `AuthenticationProvider` for what kind of verify authentication in your app. such as Verfy JWT Signature, get from your DB, etc.

```java
@Component
public class ApiAuthenticationProvider implements AuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        Object principal = authentication.getPrincipal();
        Object credentials = authentication.getCredentials();

        // TODO: Your verify authentication logic from principal and credentials.
        if(verified) {
            return new UsernamePasswordAuthenticationToken(principal , credentials, /* REAL ROLES FOR authenticated */);
        } else return null;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        // ... or equals your AuthenticationToken class.
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}
```

### Step 4. `AuthenticationEntryPoint`

If you have plan of REST API, when authentication failed or unauthorized request. You have to implement own class of `AuthenticationEntryPoint`, you'll see unwanted unauthorizaed response by Spring Security.

```java
@Component
public class ApiAuthenticationEntryPoint implements AuthenticationEntryPoint {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        log.error("UnAuthorizaed!!! message : " + e.getMessage());

        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        try (OutputStream os = response.getOutputStream()) {
            // write your own unauthorized response here.
            objectMapper.writeValue(os, e);
            os.flush();
        }
    }
}
```

### Step 5. `AccessDeniedHandler`

also you need implement class of `AccessDeniedHandler` for your own response when authentication successful but no have permission roles.

```java
@Component
public class ApiAccessDeniedHandler implements AccessDeniedHandler {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
        log.error("Forbidden!!! message : " + e.getMessage());

        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(HttpStatus.FORBIDDEN.value());
        try (OutputStream os = response.getOutputStream()) {
            // write your own FORBIDDEN response here.
            objectMapper.writeValue(os, e);
            os.flush();
        }
    }
}
```

### Step 6. `WebSecurityConfigurerAdapter`

At last, configure your app for secure, implement `WebSecurityConfigurerAdapter` and import security classes you made.

```java
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
@Import({ ApiAuthenticationFilter.class, ApiAuthenticationProvider.class, ApiAuthenticationEntryPoint.class, ApiAccessDeniedHandler.class})
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final ApiAuthenticationFilter apiAuthenticationFilter;
    private final ApiAuthenticationProvider apiAuthenticationProvider;
    private final ApiAuthenticationEntryPoint apiAuthenticationEntryPoint;
    private final ApiAccessDeniedHandler apiAccessDeniedHandler;

    @Autowired
    public SecurityConfig() {
        // TODO: Autowire these beans...
        // or use @RequiredArgsConstructor instead if you are using lombok.
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(apiAuthenticationProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic().disable()
            .csrf().disable() // required if rest api.
            .cors().and() // reuired if client is browser.
            // Required if you want stateless authentication method like JWT.
             .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .exceptionHandling()
            .authenticationEntryPoint(apiAuthenticationEntryPoint)
            .accessDeniedHandler(apiAccessDeniedHandler)
            .and()
            .authorizeRequests()
            .antMatchers("/public/**").permitAll() // for public resources
            .anyRequest().authenticated()
            .expressionHandler(defaultWebSecurityExpressionHandler())
            .and()
            .addFilterBefore(apiAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
            .formLogin().disable();
    }

    // optional: if you want role names without prefix.
    private DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler() {
        DefaultWebSecurityExpressionHandler result = new DefaultWebSecurityExpressionHandler();
        result.setDefaultRolePrefix("");
        return result;
    }
}
```

Done. next start your spring boot app and check it works.
Happy Coding!