feat: interraction between the backend and keycloak
Some checks failed
CI / build (push) Failing after 9s
Some checks failed
CI / build (push) Failing after 9s
This commit is contained in:
parent
c32eea8a40
commit
249d00177c
@ -2,13 +2,13 @@ package enseirb.myinpulse.api;
|
|||||||
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
|
||||||
|
|
||||||
|
import javax.management.relation.RoleNotFoundException;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@RestController
|
@RestController
|
||||||
public class GetUserInfo {
|
public class GetUserInfo {
|
||||||
@ -22,7 +22,8 @@ public class GetUserInfo {
|
|||||||
|
|
||||||
@CrossOrigin(methods = {RequestMethod.GET, RequestMethod.OPTIONS})
|
@CrossOrigin(methods = {RequestMethod.GET, RequestMethod.OPTIONS})
|
||||||
@GetMapping("/random")
|
@GetMapping("/random")
|
||||||
public boolean rand(){
|
public boolean rand(@RequestHeader("Authorization") String token) throws RoleNotFoundException {
|
||||||
|
System.err.println(token);
|
||||||
System.err.println("HELLO");
|
System.err.println("HELLO");
|
||||||
return Math.random() > 0.5;
|
return Math.random() > 0.5;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package enseirb.myinpulse.exceptions;
|
||||||
|
|
||||||
|
public class RoleNotFoudException extends RuntimeException {
|
||||||
|
public RoleNotFoudException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package enseirb.myinpulse.exceptions;
|
||||||
|
|
||||||
|
public class UserNotFoundException extends RuntimeException {
|
||||||
|
public UserNotFoundException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,7 @@
|
|||||||
|
/*
|
||||||
|
* Source: https://github.com/ChristianHuff-DEV/secure-spring-rest-api-using-keycloak/blob/main/src/main/java/io/betweendata/RestApi/security/oauth2/KeycloakJwtRolesConverter.java
|
||||||
|
* edited by Pierre Tellier
|
||||||
|
*/
|
||||||
package enseirb.myinpulse.security;
|
package enseirb.myinpulse.security;
|
||||||
|
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
@ -41,17 +45,16 @@ public class KeycloakJwtRolesConverter implements Converter<Jwt, AbstractAuthent
|
|||||||
private static final String CLAIM_ROLES = "roles";
|
private static final String CLAIM_ROLES = "roles";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AbstractAuthenticationToken convert(Jwt source)
|
public AbstractAuthenticationToken convert(Jwt source) {
|
||||||
{
|
|
||||||
return new JwtAuthenticationToken(source, Stream.concat(new JwtGrantedAuthoritiesConverter().convert(source)
|
return new JwtAuthenticationToken(source, Stream.concat(new JwtGrantedAuthoritiesConverter().convert(source)
|
||||||
.stream(), TEMPORARNAME(source).stream())
|
.stream(), tokenRolesExtractor(source).stream())
|
||||||
.collect(toSet()));
|
.collect(toSet()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the realm and resource level roles from a JWT token distinguishing between them using prefixes.
|
* Extracts the realm and resource level roles from a JWT token distinguishing between them using prefixes.
|
||||||
*/
|
*/
|
||||||
public Collection<GrantedAuthority> TEMPORARNAME(Jwt jwt) {
|
public Collection<GrantedAuthority> tokenRolesExtractor(Jwt jwt) {
|
||||||
// Collection that will hold the extracted roles
|
// Collection that will hold the extracted roles
|
||||||
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
|
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
|
||||||
|
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
package enseirb.myinpulse.utils;
|
||||||
|
|
||||||
|
import enseirb.myinpulse.exceptions.UserNotFoundException;
|
||||||
|
import org.springframework.web.client.RestClient;
|
||||||
|
|
||||||
|
import javax.management.relation.RoleNotFoundException;
|
||||||
|
|
||||||
|
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||||
|
|
||||||
|
public class KeycloakApi {
|
||||||
|
|
||||||
|
static final String keycloakUrl = "http://localhost:7080";
|
||||||
|
static final String realmName = "test";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses Keycloak API to retrieve a role representation of a role by its name
|
||||||
|
* @param roleName name of the role
|
||||||
|
* @param bearer authorization header used by the client to authenticate to keycloak
|
||||||
|
*/
|
||||||
|
static public RoleRepresentation getRoleRepresentationByName(String roleName, String bearer) throws RoleNotFoundException {
|
||||||
|
RoleRepresentation[] response = RestClient.builder().baseUrl(keycloakUrl)
|
||||||
|
.defaultHeader("Authorization", bearer)
|
||||||
|
.build()
|
||||||
|
.get()
|
||||||
|
.uri("/admin/realms/{realmName}/roles/{roleName}", realmName, roleName)
|
||||||
|
.retrieve()
|
||||||
|
.body(RoleRepresentation[].class);
|
||||||
|
|
||||||
|
if (response == null || response.length == 0) {
|
||||||
|
throw new RoleNotFoundException("Role not found");
|
||||||
|
}
|
||||||
|
return response[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
static public String getUserIdByName(String username, String bearer) throws UserNotFoundException {
|
||||||
|
UserRepresentation[] response = RestClient.builder().baseUrl(keycloakUrl)
|
||||||
|
.defaultHeader("Authorization", bearer)
|
||||||
|
.build()
|
||||||
|
.get()
|
||||||
|
.uri("/admin/realms/{realmName}/users?username={username}", realmName, username)
|
||||||
|
.retrieve()
|
||||||
|
.body(UserRepresentation[].class);
|
||||||
|
|
||||||
|
if (response == null || response.length == 0) {
|
||||||
|
throw new UserNotFoundException("User not found");
|
||||||
|
}
|
||||||
|
return response[0].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public void setRoleToUser(String username, String roleName, String bearer) throws RoleNotFoundException, UserNotFoundException {
|
||||||
|
RoleRepresentation roleRepresentation = getRoleRepresentationByName(roleName, bearer);
|
||||||
|
String userId = getUserIdByName(username, bearer);
|
||||||
|
|
||||||
|
|
||||||
|
RestClient.builder().baseUrl(keycloakUrl)
|
||||||
|
.defaultHeader("Authorization", bearer)
|
||||||
|
.build()
|
||||||
|
.post()
|
||||||
|
.uri("/admin/realms/${realmName}/users/${userId}/role-mappings/realm", realmName, userId)
|
||||||
|
.body(roleRepresentation)
|
||||||
|
.contentType(APPLICATION_JSON)
|
||||||
|
.retrieve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RoleRepresentation {
|
||||||
|
public String id;
|
||||||
|
public String name;
|
||||||
|
public String description;
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserRepresentation {
|
||||||
|
public String id;
|
||||||
|
public String name;
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,8 @@ axiosInstance.interceptors.response.use(
|
|||||||
async (error) => {
|
async (error) => {
|
||||||
const originalRequest = error.config;
|
const originalRequest = error.config;
|
||||||
if (
|
if (
|
||||||
error.response.status === 401 &&
|
((error.response && error.response.status === 401) ||
|
||||||
|
error.code == "ERR_NETWORK") &&
|
||||||
!originalRequest._retry &&
|
!originalRequest._retry &&
|
||||||
store.authenticated
|
store.authenticated
|
||||||
) {
|
) {
|
||||||
|
73
front/MyINPulse-front/src/views/test.vue
Normal file
73
front/MyINPulse-front/src/views/test.vue
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { store } from "../main.ts";
|
||||||
|
import { callApi } from "@/services/api.ts";
|
||||||
|
import TempModal from "@/components/temp-modal.vue";
|
||||||
|
let roleName;
|
||||||
|
let username;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1>Test page</h1>
|
||||||
|
<table class="test" style="width: 100%">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Is Currently Authenticated ?</td>
|
||||||
|
<td>{{ store.authenticated }}</td>
|
||||||
|
<td><button @click="store.login">Login</button></td>
|
||||||
|
<td><button @click="store.logout">Logout</button></td>
|
||||||
|
<td><button @click="store.signup">Signup</button></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>current token</td>
|
||||||
|
<td>{{ store.user.token }}</td>
|
||||||
|
<td>
|
||||||
|
<button @click="store.refreshUserToken">Refresh</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Current refresh token</td>
|
||||||
|
<td>{{ store.user.refreshToken }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Entrepreneur API call</td>
|
||||||
|
<td><button @click="callApi('random')">call</button></td>
|
||||||
|
<td>res</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Admin API call</td>
|
||||||
|
<td><button @click="callApi('random2')">call</button></td>
|
||||||
|
<td>res</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Unauth API call</td>
|
||||||
|
<td><button @click="callApi('random3')">call</button></td>
|
||||||
|
<td>res</td>
|
||||||
|
<td id="3"></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<input v-model="username" />
|
||||||
|
<input v-model="roleName" />
|
||||||
|
<button @click="setRoleToUser(username, roleName)">callAPI</button>
|
||||||
|
<button @click="assignRoleToUser">callAPI2</button>
|
||||||
|
<temp-modal></temp-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
table {
|
||||||
|
width: 100px;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
border: solid 1px black;
|
||||||
|
width: 20%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
x
Reference in New Issue
Block a user