mirror of
https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC.git
synced 2024-12-22 12:30:16 +02:00
MOB-55 Fixed some issues with session management.
This commit is contained in:
parent
13a0a9430f
commit
8b78ddf51a
@ -7,9 +7,10 @@
|
||||
|
||||
<p class="text-center">Read more from <a href="https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC">here.</a></p>
|
||||
</div>
|
||||
<div id="canvas"></div>
|
||||
<div class="justify-content-center d-flex">
|
||||
|
||||
<div id="canvas"></div>
|
||||
</div>
|
||||
<div class="justify-content-center d-flex">
|
||||
<button type="button" class="btn loginButton btn-dark" v-on:click="authenticate">
|
||||
<div v-if="loading" class="d-flex justify-content-center">
|
||||
<div class="spinner-border text-light spinner-border-sm" role="status">
|
||||
@ -20,7 +21,7 @@
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<div class="btn-group-sm d-flex justify-content-center" role="group" aria-label="Basic radio toggle button group">
|
||||
<div class="btn-group-sm d-flex justify-content-center" v-if="!isAndroidDevice" role="group" aria-label="Basic radio toggle button group">
|
||||
<input type="radio" class="btn-check" name="btnradio" id="btnCardReader" autocomplete="off" v-on:click="useCardReader">
|
||||
<label class="btn btn-outline-secondary" for="btnCardReader">using ID-card reader</label>
|
||||
|
||||
@ -39,14 +40,11 @@ import router from "@/router";
|
||||
|
||||
export default {
|
||||
name: 'LoginComponent',
|
||||
props: {
|
||||
"csrftoken": String,
|
||||
"csrfHeaderName": String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
useAndroidApp: true,
|
||||
loading: false,
|
||||
challenge: "",
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -58,6 +56,8 @@ export default {
|
||||
this.useAndroidApp = false;
|
||||
},
|
||||
|
||||
|
||||
|
||||
authenticate: async function () {
|
||||
this.loading = true;
|
||||
|
||||
@ -69,7 +69,6 @@ export default {
|
||||
headers: {
|
||||
"sessionId": this.$store.getters.getSessionId
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
console.log(options);
|
||||
@ -78,7 +77,7 @@ export default {
|
||||
const response = await webeid.authenticate(options);
|
||||
console.log("Authentication successful! Response:", response);
|
||||
this.loading = false;
|
||||
this.$store.commit("setLoggedIn", true);
|
||||
this.$store.dispatch("setLoggedIn", true);
|
||||
await router.push("welcome");
|
||||
|
||||
} catch (error) {
|
||||
@ -95,7 +94,14 @@ export default {
|
||||
},
|
||||
loading() {
|
||||
return this.loading;
|
||||
},
|
||||
isAndroidDevice() {
|
||||
return this.$store.getters.getIsAndroid
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const isAndroid = webeid.isAndroidDevice();
|
||||
this.$store.dispatch("setIsAndroid", isAndroid);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -31,23 +31,21 @@ export default {
|
||||
fetch("/auth/logout", requestOptions)
|
||||
.then((response) => {
|
||||
console.log(response);
|
||||
this.$store.commit("setLoggedIn", false);
|
||||
this.$store.dispatch("setLoggedIn", false);
|
||||
router.push("/");
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.$store.getters.getSessionId == null) {
|
||||
const sessionId = this.$cookie.getCookie("JSESSIONID");
|
||||
this.$store.dispatch("fetchSessionId", sessionId);
|
||||
}
|
||||
const sessionId = this.$cookie.getCookie("JSESSIONID");
|
||||
this.$store.dispatch("fetchSessionId", sessionId);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
nav {
|
||||
height: 5vh;
|
||||
height: 7vh;
|
||||
}
|
||||
</style>
|
@ -1,8 +1,11 @@
|
||||
<template>
|
||||
<div class="container container-md d-flex flex-column">
|
||||
<div>
|
||||
<h3 class="text-center">Congratulations, you logged into the site. Log out to try again.</h3>
|
||||
<p class="text-center">Read more from <a href="https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC">here.</a></p>
|
||||
<h3 class="text-center">Welcome {{ userName }}!</h3>
|
||||
<h4 class="text-center">{{ userIdCode }}</h4>
|
||||
<p class="text-center">You've successfully logged into this site using your ID card.</p>
|
||||
<p class="text-center">Read more from <a
|
||||
href="https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC">here.</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -12,14 +15,46 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'WelcomeComponent',
|
||||
props: {
|
||||
"csrftoken": String,
|
||||
props: {},
|
||||
methods: {
|
||||
getUserData: async function () {
|
||||
const requestOptions = {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"sessionid": this.$store.getters.getSessionId
|
||||
}
|
||||
};
|
||||
fetch("/auth/userData", requestOptions)
|
||||
.then((response) => {
|
||||
let data = response.body;
|
||||
data.getReader().read().then((body) => {
|
||||
let authObject = JSON.parse(new TextDecoder().decode(body.value));
|
||||
this.$store.dispatch("setUserName", authObject.userData.name);
|
||||
let idCode = authObject.userData.idCode.substring(6)
|
||||
console.log(idCode)
|
||||
this.$store.dispatch("setUserIdCode", idCode);
|
||||
});
|
||||
console.log(data);
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isLoggedIn() {
|
||||
return this.$store.getters.getAuthenticated;
|
||||
},
|
||||
userName() {
|
||||
return this.$store.getters.getUserName;
|
||||
},
|
||||
userIdCode() {
|
||||
return this.$store.getters.getUserIdCode;
|
||||
}
|
||||
}
|
||||
,
|
||||
mounted() {
|
||||
// Get user data.
|
||||
this.getUserData();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -16,6 +16,9 @@ const store = createStore({
|
||||
return {
|
||||
authenticated: false,
|
||||
jSessionId: null,
|
||||
isAndroid: false,
|
||||
userName: null,
|
||||
userIdCode: null,
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
@ -24,11 +27,32 @@ const store = createStore({
|
||||
},
|
||||
setSessionId(state, sessionId) {
|
||||
state.jSessionId = sessionId;
|
||||
},
|
||||
setIsAndroid(state, isAndroid) {
|
||||
state.isAndroid = isAndroid;
|
||||
},
|
||||
setUserName(state, userName) {
|
||||
state.userName = userName;
|
||||
},
|
||||
setIdCode(state, idCode) {
|
||||
state.userIdCode = idCode;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
fetchSessionId(context, sessionId) {
|
||||
context.commit("setSessionId", sessionId);
|
||||
},
|
||||
setLoggedIn(context, isLoggedIn) {
|
||||
context.commit("setLoggedIn", isLoggedIn);
|
||||
},
|
||||
setIsAndroid(context, isAndroid) {
|
||||
context.commit("setIsAndroid", isAndroid);
|
||||
},
|
||||
setUserName(context, userName) {
|
||||
context.commit("setUserName", userName);
|
||||
},
|
||||
setUserIdCode(context, userIdCode) {
|
||||
context.commit("setIdCode", userIdCode);
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
@ -37,7 +61,16 @@ const store = createStore({
|
||||
},
|
||||
getSessionId: state => {
|
||||
return state.jSessionId;
|
||||
}
|
||||
},
|
||||
getIsAndroid: state => {
|
||||
return state.isAndroid;
|
||||
},
|
||||
getUserName: state => {
|
||||
return state.userName;
|
||||
},
|
||||
getUserIdCode: state => {
|
||||
return state.userIdCode;
|
||||
},
|
||||
},
|
||||
plugins: [createPersistedState()],
|
||||
})
|
||||
|
@ -8658,16 +8658,33 @@ class WebExtensionService {
|
||||
}
|
||||
publishMessage(message, timeout) {
|
||||
if (message.useAuthApp && message.useAuthApp == true) {
|
||||
if (this.isAndroidDevice()) {
|
||||
if (isAndroidDevice()) {
|
||||
// Launch auth app.
|
||||
console.log("Launching auth app");
|
||||
this.launchAuthApp(message);
|
||||
}
|
||||
else {
|
||||
// Display QR code.
|
||||
this.displayQRCode(message);
|
||||
}
|
||||
this.pollForLoginSuccess(message, timeout).then((res) => {
|
||||
console.log(res);
|
||||
console.log("Polling for success.");
|
||||
this.pollForLoginSuccess(message, timeout).then((req) => {
|
||||
req.on("response", (res) => {
|
||||
if (res.statusCode == 200) {
|
||||
console.log(res.statusCode);
|
||||
window.postMessage({ action: this.getRelevantSuccessAction(message) }, location.origin);
|
||||
res.on("data", (data) => {
|
||||
console.log("HERE WE GOOO:" + data);
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.removeFromQueue(message.action);
|
||||
return Promise.reject(new ServerRejectedError("Server rejected the authentication."));
|
||||
}
|
||||
}).on("error", () => {
|
||||
this.removeFromQueue(message.action);
|
||||
return Promise.reject(new ServerRejectedError("Server unreachable."));
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
@ -8703,35 +8720,29 @@ class WebExtensionService {
|
||||
if (!message.getAuthSuccessUrl.startsWith("https://")) {
|
||||
throw new ProtocolInsecureError(`HTTPS required for getAuthSuccessUrl ${message.getAuthSuccessUrl}`);
|
||||
}
|
||||
console.log("Polling for success.");
|
||||
const headers = message.headers;
|
||||
const url = new URL(message.getAuthSuccessUrl);
|
||||
const host = url.hostname;
|
||||
const port = url.port;
|
||||
const path = url.pathname;
|
||||
const options = {
|
||||
host: host,
|
||||
port: port,
|
||||
path: path,
|
||||
method: "GET",
|
||||
headers: headers,
|
||||
timeout: timeout,
|
||||
};
|
||||
return get(message.getAuthSuccessUrl, options).on("error", (e) => {
|
||||
console.error(e);
|
||||
return get(options, (res) => {
|
||||
console.log("Polling request answered.");
|
||||
}).on("data", (data) => {
|
||||
console.log("DATA: " + data);
|
||||
}).on("error", () => {
|
||||
throw new ServerRejectedError("Authentication failed.");
|
||||
}).on("timeout", () => {
|
||||
console.error("Timeout");
|
||||
throw new ServerTimeoutError("Server didn't respond in time");
|
||||
});
|
||||
// return await Promise.race([
|
||||
// https.get(message.getAuthSuccessUrl, options, (res) => {
|
||||
// if (res.statusCode < 200 || res.statusCode > 299) {
|
||||
// return reject(new Error(`HTTP status code ${res.statusCode}`))
|
||||
// }
|
||||
//
|
||||
// const body = []
|
||||
// res.on('data', (chunk) => body.push(chunk))
|
||||
// res.on('end', () => {
|
||||
// const resString = Buffer.concat(body).toString()
|
||||
// resolve(resString)
|
||||
// })
|
||||
//
|
||||
// this.throwAfterTimeout(
|
||||
// timeout,
|
||||
// new ServerTimeoutError(`server failed to respond in time - GET ${message.getAuthSuccessUrl}`),
|
||||
// ),
|
||||
// ]) as HttpResponse;
|
||||
}
|
||||
else {
|
||||
throw new MissingParameterError("getAuthSuccessUrl missing for Android auth app authentication option.");
|
||||
@ -8746,9 +8757,6 @@ class WebExtensionService {
|
||||
setTimeout(() => resolve(), milliseconds);
|
||||
});
|
||||
}
|
||||
isAndroidDevice() {
|
||||
return navigator.userAgent.toLowerCase().indexOf("android") > -1;
|
||||
}
|
||||
getRelevantAckAction(message) {
|
||||
let ackAction;
|
||||
switch (message.action) {
|
||||
@ -8767,6 +8775,24 @@ class WebExtensionService {
|
||||
}
|
||||
return ackAction;
|
||||
}
|
||||
getRelevantSuccessAction(message) {
|
||||
let ackAction;
|
||||
switch (message.action) {
|
||||
case Action$1.AUTHENTICATE:
|
||||
ackAction = Action$1.AUTHENTICATE_SUCCESS;
|
||||
break;
|
||||
case Action$1.SIGN:
|
||||
ackAction = Action$1.SIGN_SUCCESS;
|
||||
break;
|
||||
case Action$1.STATUS:
|
||||
ackAction = Action$1.STATUS_SUCCESS;
|
||||
break;
|
||||
default:
|
||||
ackAction = Action$1.STATUS_SUCCESS;
|
||||
break;
|
||||
}
|
||||
return ackAction;
|
||||
}
|
||||
onReplyTimeout(pending) {
|
||||
var _a;
|
||||
console.log("onReplyTimeout", pending.message.action);
|
||||
@ -8776,14 +8802,13 @@ class WebExtensionService {
|
||||
onAckTimeout(pending) {
|
||||
var _a, _b;
|
||||
console.log("onAckTimeout", pending.message.action);
|
||||
console.log("Pending message");
|
||||
console.log(pending.message.authApp);
|
||||
if (pending.message.useAuthApp && pending.message.useAuthApp == true) {
|
||||
(_a = pending.reject) === null || _a === void 0 ? void 0 : _a.call(pending, new AuthAppNotInstalledError());
|
||||
}
|
||||
else {
|
||||
(_b = pending.reject) === null || _b === void 0 ? void 0 : _b.call(pending, new ExtensionUnavailableError());
|
||||
}
|
||||
this.removeFromQueue(pending.message.action);
|
||||
clearTimeout(pending.replyTimer);
|
||||
}
|
||||
getPendingMessage(action) {
|
||||
@ -9021,5 +9046,8 @@ async function sign(options) {
|
||||
const result = await webExtensionService.send(message, timeout);
|
||||
return result.response;
|
||||
}
|
||||
function isAndroidDevice() {
|
||||
return navigator.userAgent.toLowerCase().indexOf("android") > -1;
|
||||
}
|
||||
|
||||
export { Action$1 as Action, ErrorCode$1 as ErrorCode, authenticate, config$1 as config, hasVersionProperties, sign, status };
|
||||
export { Action$1 as Action, ErrorCode$1 as ErrorCode, authenticate, config$1 as config, hasVersionProperties, isAndroidDevice, sign, status };
|
||||
|
@ -3,14 +3,13 @@ package com.tarkvaratehnika.demobackend.config
|
||||
class ApplicationConfiguration {
|
||||
|
||||
companion object {
|
||||
// URL for intent, do not edit.
|
||||
val AUTH_APP_LAUNCH_INTENT = "authapp://start/"
|
||||
// Endpoint for challenge.
|
||||
val CHALLENGE_ENDPOINT_URL = "/auth/challenge"
|
||||
// Endpoint for authentication
|
||||
val AUTHENTICATION_ENDPOINT_URL = "/auth/authentication"
|
||||
// URL for application. Use ngrok for HTTPS (or a tool of your own choice) and put the HTTPS link here.
|
||||
val WEBSITE_ORIGIN_URL = "https://6b9f-85-253-195-195.ngrok.io"
|
||||
val WEBSITE_ORIGIN_URL = "https://5a0b-85-253-195-195.ngrok.io"
|
||||
|
||||
// Authentication request timeout in seconds.
|
||||
val AUTH_REQUEST_TIMEOUT_MS = 120000
|
||||
|
||||
val USER_ROLE = "USER"
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.tarkvaratehnika.demobackend.security
|
||||
package com.tarkvaratehnika.demobackend.config
|
||||
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
@ -15,9 +15,10 @@ class SecurityConfiguration : WebSecurityConfigurerAdapter() {
|
||||
}
|
||||
|
||||
override fun configure(http: HttpSecurity) {
|
||||
http.authorizeRequests()?.antMatchers("/**")?.permitAll()
|
||||
?.antMatchers("/auth/**")?.permitAll()
|
||||
http.sessionManagement()?.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
|
||||
http.csrf().disable()
|
||||
http.authorizeRequests()
|
||||
?.antMatchers("/welcome")?.hasRole("USER")
|
||||
?.and()
|
||||
?.sessionManagement()?.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
|
||||
?.and()?.csrf()?.disable()
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
package com.tarkvaratehnika.demobackend.config
|
||||
|
||||
import com.tarkvaratehnika.demobackend.dto.AuthDto
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails
|
||||
|
||||
@Configuration
|
||||
class SessionManager {
|
||||
|
||||
companion object {
|
||||
|
||||
private val LOG = LoggerFactory.getLogger(SessionManager::class.java)
|
||||
|
||||
private val sessionRegistry = HashMap<String, AuthDto>()
|
||||
|
||||
fun registerSession(sessionId: String) {
|
||||
LOG.warn("REGISTERING SESSION $sessionId")
|
||||
if (sessionRegistry.containsKey(sessionId)) {
|
||||
LOG.debug("Session already exists.")
|
||||
} else {
|
||||
sessionRegistry[sessionId] = AuthDto(arrayListOf(), hashMapOf())
|
||||
}
|
||||
}
|
||||
|
||||
fun addRoleToSession(sessionId: String, role: GrantedAuthority): AuthDto {
|
||||
if (sessionRegistry.containsKey(sessionId)) {
|
||||
val session = sessionRegistry[sessionId]
|
||||
session!!.roles.add(role)
|
||||
return session
|
||||
} else {
|
||||
throw Exception("Session with sessionId: $sessionId does not exist.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function adds role and userdata specified in authDto to the current session.
|
||||
*/
|
||||
fun addRoleToCurrentSession(authDto: AuthDto) {
|
||||
val securityContext = SecurityContextHolder.getContext()
|
||||
var sessionId = getSessionId()
|
||||
if (sessionId == null) {
|
||||
// No sessionId attached to the session, get one from credentials.
|
||||
sessionId = securityContext.authentication.credentials.toString()
|
||||
}
|
||||
val authentication = UsernamePasswordAuthenticationToken(authDto.userData, sessionId, authDto.roles)
|
||||
securityContext.authentication = authentication
|
||||
}
|
||||
|
||||
fun removeRoleFromCurrentSession(headers: Map<String, String>) {
|
||||
val securityContext = SecurityContextHolder.getContext()
|
||||
var sessionId = securityContext.authentication.credentials
|
||||
if (sessionId == null) {
|
||||
// Fallback to when for some reason session object doesn't have sessionId attached.
|
||||
sessionId = getSessionId(headers)
|
||||
}
|
||||
sessionRegistry[sessionId]!!.roles = arrayListOf()
|
||||
val authentication = UsernamePasswordAuthenticationToken(null, sessionId, listOf())
|
||||
securityContext.authentication = authentication
|
||||
}
|
||||
|
||||
fun addUserDataToSession(sessionId: String, name: String, idCode: String): AuthDto {
|
||||
if (sessionRegistry.containsKey(sessionId)) {
|
||||
val session = sessionRegistry[sessionId]
|
||||
session!!.userData["name"] = name
|
||||
session.userData["idCode"] = idCode
|
||||
return session
|
||||
} else {
|
||||
throw Exception("Session with sessionId: $sessionId does not exist.")
|
||||
}
|
||||
}
|
||||
|
||||
fun getSessionHasRole(sessionId: String, role: String): Boolean {
|
||||
if (sessionRegistry.containsKey(sessionId)) {
|
||||
if (sessionRegistry[sessionId]!!.roles.contains(SimpleGrantedAuthority(role))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun getSessionAuth(sessionId: String?): AuthDto? {
|
||||
if (sessionId == null) {
|
||||
return null
|
||||
}
|
||||
return sessionRegistry[sessionId]
|
||||
}
|
||||
|
||||
fun getSessionId(headers: Map<String, String>): String? {
|
||||
return headers["sessionid"]
|
||||
}
|
||||
|
||||
fun getSessionId(): String? {
|
||||
val context = SecurityContextHolder.getContext()
|
||||
if (context.authentication != null && context.authentication.details != null) {
|
||||
return (context.authentication.details as WebAuthenticationDetails).sessionId
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -3,9 +3,12 @@ package com.tarkvaratehnika.demobackend.config
|
||||
import com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver
|
||||
import org.springframework.security.core.session.SessionRegistry
|
||||
import org.springframework.security.core.session.SessionRegistryImpl
|
||||
import org.webeid.security.exceptions.JceException
|
||||
import org.webeid.security.nonce.NonceGenerator
|
||||
import org.webeid.security.nonce.NonceGeneratorBuilder
|
||||
@ -13,7 +16,6 @@ import org.webeid.security.validator.AuthTokenValidator
|
||||
import org.webeid.security.validator.AuthTokenValidatorBuilder
|
||||
import java.io.IOException
|
||||
import java.net.URI
|
||||
import java.net.URL
|
||||
import java.security.KeyStore
|
||||
import java.security.KeyStoreException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
@ -26,12 +28,11 @@ import javax.cache.Cache
|
||||
import javax.cache.CacheManager
|
||||
import javax.cache.Caching
|
||||
import javax.cache.configuration.CompleteConfiguration
|
||||
import javax.cache.configuration.FactoryBuilder
|
||||
import javax.cache.configuration.FactoryBuilder.factoryOf
|
||||
import javax.cache.configuration.MutableConfiguration
|
||||
import javax.cache.expiry.CreatedExpiryPolicy
|
||||
import javax.cache.expiry.Duration
|
||||
|
||||
import javax.cache.configuration.FactoryBuilder.factoryOf
|
||||
|
||||
@Configuration
|
||||
class ValidationConfiguration {
|
||||
@ -43,9 +44,8 @@ class ValidationConfiguration {
|
||||
private val CERTS_RESOURCE_PATH = "/certs"
|
||||
private val TRUSTED_CERTIFICATES_JKS = "trusted_certificates.jks"
|
||||
private val TRUSTSTORE_PASSWORD = "changeit"
|
||||
companion object {
|
||||
const val ROLE_USER : String = "ROLE_USER"
|
||||
}
|
||||
|
||||
|
||||
|
||||
init {
|
||||
LOG.warn("Creating new ValidationConfiguration.")
|
||||
@ -56,6 +56,8 @@ class ValidationConfiguration {
|
||||
return Caching.getCachingProvider(CaffeineCachingProvider::class.java.name).cacheManager
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Bean
|
||||
fun nonceCache(): Cache<String, ZonedDateTime>? {
|
||||
val cacheManager: CacheManager = cacheManager()
|
||||
|
@ -0,0 +1,5 @@
|
||||
package com.tarkvaratehnika.demobackend.dto
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
|
||||
data class AuthDto(var roles: ArrayList<GrantedAuthority>, var userData: HashMap<String, String>)
|
@ -0,0 +1,5 @@
|
||||
package com.tarkvaratehnika.demobackend.dto
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
|
||||
class AuthTokenDTO (@JsonProperty("auth-token") val token : String)
|
@ -1,4 +0,0 @@
|
||||
package com.tarkvaratehnika.demobackend.security
|
||||
|
||||
class AuthTokenDTO (val token : String, val xsrfToken : String) {
|
||||
}
|
@ -23,14 +23,12 @@
|
||||
package com.tarkvaratehnika.demobackend.security
|
||||
|
||||
import com.tarkvaratehnika.demobackend.config.ValidationConfiguration
|
||||
import com.tarkvaratehnika.demobackend.config.ValidationConfiguration.Companion.ROLE_USER
|
||||
import com.tarkvaratehnika.demobackend.web.rest.AuthenticationController
|
||||
import com.tarkvaratehnika.demobackend.dto.AuthDto
|
||||
import com.tarkvaratehnika.demobackend.dto.AuthTokenDTO
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.security.authentication.AuthenticationServiceException
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.AuthenticationException
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
|
||||
import org.springframework.stereotype.Component
|
||||
import org.webeid.security.exceptions.TokenValidationException
|
||||
@ -44,23 +42,15 @@ object AuthTokenDTOAuthenticationProvider {
|
||||
|
||||
private val LOG = LoggerFactory.getLogger(AuthTokenDTOAuthenticationProvider::class.java)
|
||||
|
||||
|
||||
private val USER_ROLE: GrantedAuthority = SimpleGrantedAuthority(ROLE_USER)
|
||||
|
||||
|
||||
val tokenValidator: AuthTokenValidator = ValidationConfiguration().validator()
|
||||
|
||||
@Throws(AuthenticationException::class)
|
||||
fun authenticate(auth : Authentication) : Authentication {
|
||||
fun authenticate(auth : Authentication, sessionId: String?) : AuthDto {
|
||||
val authentication = auth as PreAuthenticatedAuthenticationToken
|
||||
val token = (authentication.credentials as AuthTokenDTO).token
|
||||
val challenge = (authentication.credentials as AuthTokenDTO)
|
||||
val authorities = arrayListOf<GrantedAuthority>()
|
||||
authorities.add(USER_ROLE)
|
||||
|
||||
try {
|
||||
val userCertificate: X509Certificate = tokenValidator.validate(token)
|
||||
return WebEidAuthentication.fromCertificate(userCertificate, authorities, "as")
|
||||
return WebEidAuthentication.fromCertificate(userCertificate, sessionId)
|
||||
} catch (e : TokenValidationException) {
|
||||
// Validation failed.
|
||||
throw AuthenticationServiceException("Token validation failed. " + e.message)
|
||||
|
@ -22,17 +22,27 @@
|
||||
|
||||
package com.tarkvaratehnika.demobackend.security
|
||||
|
||||
import com.tarkvaratehnika.demobackend.config.ApplicationConfiguration
|
||||
import com.tarkvaratehnika.demobackend.config.ApplicationConfiguration.Companion.USER_ROLE
|
||||
import com.tarkvaratehnika.demobackend.config.SessionManager
|
||||
import com.tarkvaratehnika.demobackend.dto.AuthDto
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.webeid.security.certificate.CertificateData
|
||||
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
import org.springframework.security.core.context.SecurityContext
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
import java.io.Serializable
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
import java.util.concurrent.ThreadLocalRandom
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.math.log
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
class WebEidAuthentication(
|
||||
private val principalName: String,
|
||||
@ -40,39 +50,71 @@ class WebEidAuthentication(
|
||||
private val authorities: ArrayList<GrantedAuthority>
|
||||
) : PreAuthenticatedAuthenticationToken(principalName, idCode, authorities), Authentication {
|
||||
|
||||
|
||||
// Companion object is for static functions.
|
||||
companion object {
|
||||
|
||||
private val loggedInUsers = HashMap<String, Authentication>()
|
||||
private val LOG = LoggerFactory.getLogger(WebEidAuthentication::class.java)
|
||||
|
||||
fun fromCertificate(
|
||||
userCertificate: X509Certificate,
|
||||
authorities: ArrayList<GrantedAuthority>,
|
||||
challenge: String
|
||||
): Authentication {
|
||||
val principalName = getPrincipalNameFromCertificate(userCertificate)
|
||||
sessionId: String?,
|
||||
): AuthDto {
|
||||
// Get user data.
|
||||
val name = getPrincipalNameFromCertificate(userCertificate)
|
||||
val idCode = Objects.requireNonNull(CertificateData.getSubjectIdCode(userCertificate))
|
||||
val authentication = WebEidAuthentication(principalName, idCode, authorities)
|
||||
loggedInUsers[challenge] = authentication
|
||||
return authentication
|
||||
|
||||
// Fetch valid sessionId.
|
||||
var methodIndependentSessionId = sessionId
|
||||
if (methodIndependentSessionId == null) {
|
||||
methodIndependentSessionId = SessionManager.getSessionId()
|
||||
if (methodIndependentSessionId == null) {
|
||||
throw Exception("No session")
|
||||
}
|
||||
}
|
||||
|
||||
// Add role and user data to the AuthDto and return it.
|
||||
SessionManager.addRoleToSession(methodIndependentSessionId, SimpleGrantedAuthority(USER_ROLE))
|
||||
return SessionManager.addUserDataToSession(methodIndependentSessionId, name, idCode)
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for getting a Spring authentication object by supplying a challenge.
|
||||
* TODO: Figure out a more secure solution in the future.
|
||||
*/
|
||||
fun fromChallenge(challenge: String): Authentication? {
|
||||
fun fromSession(headers: HashMap<String, String>): AuthDto {
|
||||
val currentTime = Date()
|
||||
|
||||
// Get sessionId for current session.
|
||||
var sessionId = SessionManager.getSessionId()
|
||||
|
||||
if (sessionId == null) {
|
||||
LOG.warn("SESSION IS NULL")
|
||||
sessionId = SessionManager.getSessionId(headers)
|
||||
if (sessionId == null) {
|
||||
LOG.warn("SESSION IS STILL NULL")
|
||||
throw ResponseStatusException(HttpStatus.FORBIDDEN, "Session ID not found.")
|
||||
}
|
||||
LOG.warn("SESSION IS NOW: " + sessionId)
|
||||
}
|
||||
|
||||
while (currentTime.time + ApplicationConfiguration.AUTH_REQUEST_TIMEOUT_MS > Date().time) {
|
||||
Thread.sleep(1000)
|
||||
|
||||
if (SessionManager.getSessionHasRole(sessionId, USER_ROLE)) {
|
||||
// Get AuthDto
|
||||
val auth = SessionManager.getSessionAuth(sessionId)
|
||||
|
||||
// Set role and user data to current session.
|
||||
SessionManager.addRoleToCurrentSession(auth!!)
|
||||
LOG.warn("ROLE ADDED AND LOGGING IN.")
|
||||
return auth
|
||||
}
|
||||
|
||||
}
|
||||
// if (ThreadLocalRandom.current().nextFloat() < 0.5f) { // TODO: For testing.
|
||||
// return null
|
||||
// }
|
||||
val auth = loggedInUsers[challenge]
|
||||
if (auth != null) {
|
||||
// If challenge is valid, delete the authentication object from the map (so this can only be fetched once).
|
||||
loggedInUsers.remove(challenge)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
return auth
|
||||
throw ResponseStatusException(HttpStatus.REQUEST_TIMEOUT, "Token not received in time.")
|
||||
}
|
||||
|
||||
// // TODO: DELETE
|
||||
|
@ -1,15 +1,15 @@
|
||||
package com.tarkvaratehnika.demobackend.web.rest
|
||||
|
||||
import com.tarkvaratehnika.demobackend.security.AuthTokenDTO
|
||||
import com.tarkvaratehnika.demobackend.config.SessionManager
|
||||
import com.tarkvaratehnika.demobackend.dto.AuthDto
|
||||
import com.tarkvaratehnika.demobackend.dto.AuthTokenDTO
|
||||
import com.tarkvaratehnika.demobackend.security.AuthTokenDTOAuthenticationProvider
|
||||
import com.tarkvaratehnika.demobackend.security.WebEidAuthentication
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
|
||||
@RestController
|
||||
@RequestMapping("auth")
|
||||
@ -19,30 +19,31 @@ class AuthenticationController {
|
||||
|
||||
|
||||
@PostMapping("login", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
fun authenticate(@RequestBody body : String): Authentication {
|
||||
val parts = body.split("\"")
|
||||
val authToken = AuthTokenDTO(parts[3], parts[7])
|
||||
fun authenticate(@RequestHeader headers: Map<String, String>, @RequestBody body : AuthTokenDTO): AuthDto {
|
||||
|
||||
val sessionId = SessionManager.getSessionId(headers)
|
||||
|
||||
// Create Spring Security Authentication object with supplied token as credentials.
|
||||
val auth = PreAuthenticatedAuthenticationToken(null, authToken)
|
||||
val auth = PreAuthenticatedAuthenticationToken(null, body)
|
||||
|
||||
// Return authentication object if success.
|
||||
return AuthTokenDTOAuthenticationProvider.authenticate(auth)
|
||||
return AuthTokenDTOAuthenticationProvider.authenticate(auth, sessionId)
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("login", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
fun getAuthenticated(headers: String) : Authentication? {
|
||||
val auth = WebEidAuthentication.fromChallenge("as")
|
||||
if (auth == null) {
|
||||
throw ResponseStatusException(HttpStatus.FORBIDDEN, "Not allowed.")
|
||||
}
|
||||
return auth
|
||||
fun getAuthenticated(@RequestHeader headers: HashMap<String, String>) : AuthDto {
|
||||
return WebEidAuthentication.fromSession(headers)
|
||||
}
|
||||
|
||||
@GetMapping("userData", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
fun getUserData(@RequestHeader headers: Map<String, String>) : AuthDto? {
|
||||
return SessionManager.getSessionAuth(SessionManager.getSessionId(headers))
|
||||
}
|
||||
|
||||
@PostMapping("logout", consumes = [MediaType.APPLICATION_JSON_VALUE])
|
||||
fun logOut(@RequestBody body: String) : HttpStatus? {
|
||||
LOG.warn("I WAS HERE")
|
||||
LOG.warn(body)
|
||||
fun logOut(@RequestHeader headers: Map<String, String>, @RequestBody body: String) : HttpStatus? {
|
||||
SessionManager.removeRoleFromCurrentSession(headers)
|
||||
return HttpStatus.ACCEPTED
|
||||
|
||||
}
|
||||
|
@ -22,14 +22,24 @@
|
||||
|
||||
package com.tarkvaratehnika.demobackend.web.rest
|
||||
|
||||
import com.tarkvaratehnika.demobackend.config.SessionManager
|
||||
import com.tarkvaratehnika.demobackend.dto.ChallengeDto
|
||||
import com.tarkvaratehnika.demobackend.security.WebEidAuthentication
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RequestHeader
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.client.HttpClientErrorException
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
import org.webeid.security.nonce.NonceGenerator
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("auth")
|
||||
class ChallengeController (val nonceGenerator: NonceGenerator) {
|
||||
@ -37,10 +47,30 @@ class ChallengeController (val nonceGenerator: NonceGenerator) {
|
||||
private val LOG = LoggerFactory.getLogger(ChallengeController::class.java)
|
||||
|
||||
@GetMapping("challenge")
|
||||
fun challenge(): ChallengeDto {
|
||||
fun challenge(@RequestHeader headers: Map<String, String>): ChallengeDto {
|
||||
|
||||
val sessionId = SessionManager.getSessionId(headers)
|
||||
|
||||
if (sessionId == null) {
|
||||
LOG.warn("SESSION ID MISSING FOR CHALLENGE")
|
||||
throw ResponseStatusException(HttpStatus.FORBIDDEN, "SessionId missing.")
|
||||
}
|
||||
|
||||
SessionManager.registerSession(sessionId)
|
||||
|
||||
// val context = SecurityContextHolder.getContext()
|
||||
// val authorities = arrayListOf<GrantedAuthority>()
|
||||
// authorities.add(SimpleGrantedAuthority("USER"))
|
||||
// authorities.add(SimpleGrantedAuthority("ROLE_USER"))
|
||||
// val auth = context.authentication
|
||||
//
|
||||
// val newAuth: Authentication =
|
||||
// UsernamePasswordAuthenticationToken(auth.principal, auth.credentials, authorities)
|
||||
// SecurityContextHolder.getContext().authentication = newAuth;
|
||||
|
||||
// SessionManager.createSession(SessionManager.getSessionId(headers))
|
||||
val challengeDto = ChallengeDto(nonceGenerator.generateAndStoreNonce())
|
||||
LOG.warn(challengeDto.nonce)
|
||||
// WebEidAuthentication.addAuth(challengeDto.nonce) // For testing.
|
||||
return challengeDto
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2021 The Web eID Project
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.tarkvaratehnika.demobackend.web.rest
|
||||
|
||||
import com.tarkvaratehnika.demobackend.config.SessionManager
|
||||
import com.tarkvaratehnika.demobackend.dto.ChallengeDto
|
||||
import com.tarkvaratehnika.demobackend.security.WebEidAuthentication
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RequestHeader
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.webeid.security.nonce.NonceGenerator
|
||||
|
||||
@RestController
|
||||
@RequestMapping("auth")
|
||||
class Test (val nonceGenerator: NonceGenerator) {
|
||||
|
||||
private val LOG = LoggerFactory.getLogger(ChallengeController::class.java)
|
||||
|
||||
@GetMapping("test")
|
||||
fun test(@RequestHeader headers: Map<String, String>): String {
|
||||
return "<h1>JOUUUUUUUU</h1>"
|
||||
}
|
||||
|
||||
@GetMapping("test2")
|
||||
fun test2(@RequestHeader headers: Map<String, String>): String {
|
||||
return "<h1>JOUUUUUUUU22222222222222222</h1>"
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user