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:
		| @@ -2,13 +2,13 @@ package enseirb.myinpulse.api; | ||||
|  | ||||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||
| import org.springframework.security.core.context.SecurityContextHolder; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.RequestMethod; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
| import org.springframework.web.bind.annotation.CrossOrigin; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
|  | ||||
| import javax.management.relation.RoleNotFoundException; | ||||
| import java.security.Principal; | ||||
|  | ||||
|  | ||||
|  | ||||
| @SpringBootApplication | ||||
| @RestController | ||||
| public class GetUserInfo { | ||||
| @@ -22,7 +22,8 @@ public class GetUserInfo { | ||||
|  | ||||
|     @CrossOrigin(methods = {RequestMethod.GET, RequestMethod.OPTIONS}) | ||||
|     @GetMapping("/random") | ||||
|     public boolean rand(){ | ||||
|     public boolean rand(@RequestHeader("Authorization") String token) throws RoleNotFoundException { | ||||
|         System.err.println(token); | ||||
|         System.err.println("HELLO"); | ||||
|         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; | ||||
|  | ||||
| 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"; | ||||
|  | ||||
|     @Override | ||||
|     public AbstractAuthenticationToken convert(Jwt source) | ||||
|     { | ||||
|     public AbstractAuthenticationToken convert(Jwt source) { | ||||
|         return new JwtAuthenticationToken(source, Stream.concat(new JwtGrantedAuthoritiesConverter().convert(source) | ||||
|                         .stream(), TEMPORARNAME(source).stream()) | ||||
|                         .stream(), tokenRolesExtractor(source).stream()) | ||||
|                 .collect(toSet())); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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<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) => { | ||||
|         const originalRequest = error.config; | ||||
|         if ( | ||||
|             error.response.status === 401 && | ||||
|             ((error.response && error.response.status === 401) || | ||||
|                 error.code == "ERR_NETWORK") && | ||||
|             !originalRequest._retry && | ||||
|             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> | ||||
		Reference in New Issue
	
	Block a user