Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add endpoint to return auth token with session management #119

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-jdbc</artifactId>
</dependency>
</dependencies>

<build>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package lk.gov.govtech.covid19.security;

import lk.gov.govtech.covid19.config.PortalUserConfiguration;
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.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.session.web.http.HeaderHttpSessionIdResolver;
import org.springframework.session.web.http.HttpSessionIdResolver;

import javax.servlet.http.HttpServletRequest;

import static lk.gov.govtech.covid19.util.Constants.*;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
/*
* Endpoints without auth
* - /application/** (all were GETs)
* - /dhis/** (includes both POSTS and GETs)
*
* Endpoints with auth: either http basic auth or login (/portal) based can be used
* - /notification/alert/add
* - /notification/case/add
* - /portal/**
*
* */

@Autowired
PortalUserConfiguration users;

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

@Bean
public HttpSessionIdResolver httpSessionIdResolver() {
return HeaderHttpSessionIdResolver.xAuthToken();
}

@Bean //An exposed user details bean which both the following WebSecurityConfigurerAdapters use
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
for (String[] aUser : users.getUserCredentials()) {
manager.createUser(User.builder()
.username(aUser[0])
.password(passwordEncoder().encode(aUser[1]))
.authorities(AUTHORITY_NOTIFICATION).build());
}
return manager;
}

@Configuration
@Order(1)
public static class StatelessSecurityConfig extends WebSecurityConfigurerAdapter {
/*
* This section
* - excludes /application/** and /dhis/** from auth
* - allows requests with http-basic-auth to be STATELESS
* */
@Override
protected void configure(final HttpSecurity http) throws Exception {

http
.authorizeRequests()
.mvcMatchers( // to exclude auth for GETs
APPLICATION_API_CONTEXT + "/**",
DHIS_API_CONTEXT + "/**")
.permitAll()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, // to exclude auth for POSTs
DHIS_API_CONTEXT + "/**")
.permitAll()
.and()
.csrf().disable()
.requestMatcher(new RequestMatcher() {
@Override
public boolean matches(HttpServletRequest request) {
return request.getHeader("Authorization") != null;
}
})
.httpBasic()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}

@Configuration
@Order(2)
public static class StatefulSecurityConfig extends WebSecurityConfigurerAdapter {
/*
* This section
* - adds auth to some paths, that are common to both basic-auth & auth-token (i.e. stateless and stateful)
* - specifies where the login-page is
* - creates a POST endpoint at path /auth (for form login)
* - created a GET endpoint at path /auth/logout (to logout)
* - stops saving anonymous requests in sessions
* */
@Override
protected void configure(final HttpSecurity http) throws Exception {

http
.csrf().disable()
.authorizeRequests() // Common to both: stateless & stateful. Only the paths and the authority matters
.mvcMatchers(
"/notification/alert/add",
"/notification/case/add",
PORTAL_API_CONTEXT + "/**")
.hasAuthority(AUTHORITY_NOTIFICATION)
.and()
.formLogin()
.loginPage(PORTAL_API_CONTEXT) //While specifying the login-page, this also creates a POST endpoint at path /portal (to send username password)
.loginProcessingUrl(AUTH_API_CONTEXT)
.successHandler(new SessionAuthenticationSuccessHandler()) //overriding the default handler to avoid redirect
.failureHandler(new SimpleUrlAuthenticationFailureHandler())
.permitAll()
.and()
.requestCache() // stops saving anonymous requests in sessions
.requestCache(new NullRequestCache())
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher(AUTH_API_CONTEXT + "/logout")) //logs out with a GET
.permitAll()
.logoutSuccessUrl(PORTAL_API_CONTEXT); //redirects once successful
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package lk.gov.govtech.covid19.security;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class SessionAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
Map<String, String> scope = new HashMap<>();
scope.put("scope", authentication.getAuthorities().toString());

response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().write(String.valueOf(new Gson().toJson(scope)));
}
}
3 changes: 3 additions & 0 deletions src/main/java/lk/gov/govtech/covid19/util/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ public class Constants {
public static final String APPLICATION_API_CONTEXT = "/application";
public static final String NOTIFICATION_API_CONTEXT = "/notification";
public static final String DOCUMENTS_API_CONTEXT = "/documents";
public static final String AUTH_API_CONTEXT = "/auth";

public static final String NEWS_PATH = "/news";
public static final String CASES_PATH = "/cases";
public static final String DASHBOARD_PATH = "/dashboard";

public static final String PUSH_NOTIFICATION_MESSAGE_TYPE_ALERT = "alert";
public static final String PUSH_NOTIFICATION_MESSAGE_TYPE_CASE = "case";

public static final String AUTHORITY_NOTIFICATION = "admin_notification";
}
7 changes: 7 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ spring:
- [[email protected], dev]
googleapi:
mapKey: abc
session:
timeout: 3600
jdbc:
initialize-schema: always
schema: classpath:session/schema-mysql.sql
initializer:
enabled: true

firebase:
config-path: credentials/covid-19-lk-dev-firebase-adminsdk.json
Expand Down
22 changes: 22 additions & 0 deletions src/main/resources/session/schema-mysql.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
CREATE TABLE SPRING_SESSION (
PRIMARY_ID CHAR(36) NOT NULL,
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
EXPIRY_TIME BIGINT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;

CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);

CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES BLOB NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
2 changes: 1 addition & 1 deletion src/main/resources/templates/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ <h2 class="mt-6 text-center text-lg leading-9 font-extrabold text-gray-800">
</h2>
</div>

<form class="mt-8" th:action="@{/portal}" method="POST">
<form class="mt-8" th:action="@{/auth}" method="POST">
<input type="hidden" name="remember" value="false" />
<div class="rounded-md shadow-sm">
<!-- Email Input-->
Expand Down