spring security for keycloak roles
This commit is contained in:
@ -7,7 +7,7 @@ plugins {
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
|
||||
group = 'de.jottyfan'
|
||||
version = '1.2.9'
|
||||
version = '1.3.0'
|
||||
|
||||
description = """timetrack"""
|
||||
|
||||
|
@ -0,0 +1,78 @@
|
||||
package de.jottyfan.timetrack.config;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jotty
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
public class AuthorizationConfiguration {
|
||||
|
||||
private static final String REALM_ACCESS_CLAIM = "realm_access";
|
||||
private static final String ROLES_CLAIM = "roles";
|
||||
private static final String RESOURCE_ACCESS_CLAIM = "resource_access";
|
||||
|
||||
@Value("${spring.security.oauth2.client.registration.keycloak.client-id}")
|
||||
private String clientId;
|
||||
|
||||
@Bean
|
||||
GrantedAuthoritiesMapper userAuthoritiesMapperForKeycloak() {
|
||||
return authorities -> {
|
||||
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
||||
var authority = authorities.iterator().next();
|
||||
boolean isOidc = authority instanceof OidcUserAuthority;
|
||||
|
||||
if (isOidc) {
|
||||
var oidcUserAuthority = (OidcUserAuthority) authority;
|
||||
var userInfo = oidcUserAuthority.getUserInfo();
|
||||
|
||||
if (userInfo.hasClaim(REALM_ACCESS_CLAIM)) {
|
||||
var realmAccess = userInfo.getClaimAsMap(REALM_ACCESS_CLAIM);
|
||||
@SuppressWarnings("unchecked")
|
||||
var roles = (Collection<String>) realmAccess.get(ROLES_CLAIM);
|
||||
mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
|
||||
}
|
||||
if (userInfo.hasClaim(RESOURCE_ACCESS_CLAIM)) {
|
||||
var resourceAccess = userInfo.getClaimAsMap(RESOURCE_ACCESS_CLAIM);
|
||||
if (resourceAccess.containsKey(clientId)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
var roles = (Collection<String>) ((Map<?, ?>) resourceAccess.get(clientId)).get(ROLES_CLAIM);
|
||||
mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var oauth2UserAuthority = (OAuth2UserAuthority) authority;
|
||||
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
|
||||
|
||||
if (userAttributes.containsKey(REALM_ACCESS_CLAIM)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
var realmAccess = (Map<String, Object>) userAttributes.get(REALM_ACCESS_CLAIM);
|
||||
@SuppressWarnings("unchecked")
|
||||
var roles = (Collection<String>) realmAccess.get(ROLES_CLAIM);
|
||||
mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
|
||||
}
|
||||
}
|
||||
|
||||
return mappedAuthorities;
|
||||
};
|
||||
}
|
||||
|
||||
private Collection<GrantedAuthority> generateAuthoritiesFromClaim(Collection<String> roles) {
|
||||
return roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).collect(Collectors.toList());
|
||||
}
|
||||
}
|
@ -2,20 +2,19 @@ package de.jottyfan.timetrack.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler;
|
||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
|
||||
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author henkej
|
||||
* @author jotty
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
@ -36,19 +35,9 @@ public class SecurityConfiguration {
|
||||
.oauth2Login(o -> o.defaultSuccessUrl("/"))
|
||||
.logout(o -> o.logoutSuccessHandler(new OidcClientInitiatedLogoutSuccessHandler(crr)))
|
||||
.authorizeHttpRequests(o -> o.requestMatchers("/public/**").permitAll().anyRequest().authenticated())
|
||||
.oauth2ResourceServer(o -> o.jwt(j -> roleConverter()))
|
||||
.oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults()))
|
||||
.sessionManagement(o -> o.init(sec));
|
||||
// @formatter:on
|
||||
return sec.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtAuthenticationConverter roleConverter() {
|
||||
JwtGrantedAuthoritiesConverter gac = new JwtGrantedAuthoritiesConverter();
|
||||
gac.setAuthorityPrefix("ROLE_");
|
||||
gac.setAuthoritiesClaimName("roles");
|
||||
JwtAuthenticationConverter jac = new JwtAuthenticationConverter();
|
||||
jac.setJwtGrantedAuthoritiesConverter(gac);
|
||||
return jac;
|
||||
}
|
||||
}
|
||||
|
@ -20,5 +20,5 @@ spring.security.oauth2.client.provider.keycloak.jwk-set-uri = ${keycloak.openid-
|
||||
spring.security.oauth2.client.provider.keycloak.user-name-attribute = preferred_username
|
||||
|
||||
# application
|
||||
server.port = 8083
|
||||
server.port = 9001
|
||||
server.servlet.context-path = /timetrack
|
||||
|
@ -6,7 +6,7 @@
|
||||
<body>
|
||||
<font layout:fragment="title">Kalender</font>
|
||||
<ul layout:fragment="menu">
|
||||
<li class="nav-item" th:if="${#authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('timetrack_user')}"><a class="btn btn-outline-success" th:href="@{/calendar/add}"><i class="fas fa-plus"></i></a></li>
|
||||
<li class="nav-item" sec:authorize="hasRole('timetrack_user')"><a class="btn btn-outline-success" th:href="@{/calendar/add}"><i class="fas fa-plus"></i></a></li>
|
||||
</ul>
|
||||
<main layout:fragment="content">
|
||||
<ul class="nav nav-tabs navback" role="tablist">
|
||||
@ -15,7 +15,7 @@
|
||||
</ul>
|
||||
<div class="tabdivblurred tab-content">
|
||||
<div id="div_dashboard" class="tab-pane active tab-pane-glassy">
|
||||
<div th:if="${#authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('timetrack_user')}">
|
||||
<div sec:authorize="hasRole('timetrack_user')">
|
||||
<div id="calendar"></div>
|
||||
<script th:inline="javascript">
|
||||
/*<![CDATA[*/
|
||||
@ -43,7 +43,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="div_options" class="tab-pane fade tab-pane-table">
|
||||
<div th:if="${#authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('timetrack_user')}">
|
||||
<div sec:authorize="hasRole('timetrack_user')">
|
||||
<!-- TODO: add the options here -->
|
||||
</div>
|
||||
</div>
|
||||
|
@ -47,7 +47,7 @@
|
||||
<div class="col-sm-10">
|
||||
<button type="submit" class="btn btn-success">speichern</button>
|
||||
<button type="submit" class="btn btn-secondary" th:formaction="@{/contact/list}">abbrechen</button>
|
||||
<div class="dropdown float-right" th:if="${contactBean.pk != null and #authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('timetrack_user')}">
|
||||
<div class="dropdown float-right" th:if="${contactBean.pk != null}" sec:authorize="hasRole('timetrack_user')">
|
||||
<button class="btn btn-danger dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Eintrag löschen</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" th:href="@{/contact/delete/{id}(id=${contactBean.pk})}">endgültig löschen</a></li>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<body>
|
||||
<font layout:fragment="title">Kontakte</font>
|
||||
<ul layout:fragment="menu">
|
||||
<li class="nav-item" th:if="${#authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('timetrack_user')}"><a class="nav-link btn btn-success btn-white-text" th:href="@{/contact/add}">Neuen
|
||||
<li class="nav-item" sec:authorize="hasRole('timetrack_user')"><a class="nav-link btn btn-success btn-white-text" th:href="@{/contact/add}">Neuen
|
||||
Kontakt anlegen</a></li>
|
||||
</ul>
|
||||
<main layout:fragment="content">
|
||||
@ -27,7 +27,7 @@
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-center align-items-center">
|
||||
<span th:text="${contact.type} + ': ' + ${contact.contact}"></span> <a
|
||||
th:href="@{/contact/edit/{id}(id=${contact.pk})}" th:if="${#authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('timetrack_user')}" style="margin-left: 8px;">
|
||||
th:href="@{/contact/edit/{id}(id=${contact.pk})}" sec:authorize="hasRole('timetrack_user')" style="margin-left: 8px;">
|
||||
<i class="fa fa-edit"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -93,7 +93,7 @@
|
||||
<div class="col-sm-10">
|
||||
<button id="okbtn" type="submit" class="btn btn-success">speichern</button>
|
||||
<a class="btn btn-secondary" th:href="@{/done/abort/{day}(day=${doneModel.dayString})}">abbrechen</a>
|
||||
<div class="dropdown float-right" th:if="${doneBean.pk != null and #authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('timetrack_user')}">
|
||||
<div class="dropdown float-right" th:if="${doneBean.pk != null}" sec:authorize="hasRole('timetrack_user')">
|
||||
<button class="btn btn-danger dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Eintrag löschen</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" th:href="@{/done/delete/{id}(id=${doneBean.pk})}">endgültig löschen</a></li>
|
||||
|
@ -27,7 +27,7 @@
|
||||
<div class="col-sm-10">
|
||||
<button id="okbtn" type="submit" class="btn btn-success">speichern</button>
|
||||
<a class="btn btn-secondary" th:href="@{/done/list}">abbrechen</a>
|
||||
<div class="dropdown float-right" th:if="${jobBean.pk != null and #authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('timetrack_user')}">
|
||||
<div class="dropdown float-right" th:if="${jobBean.pk != null}" sec:authorize="hasRole('timetrack_user')">
|
||||
<button class="btn btn-danger dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Eintrag löschen</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" th:href="@{/done/delete/job/{id}(id=${jobBean.pk})}">endgültig löschen</a></li>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<body>
|
||||
<font layout:fragment="title">Arbeitszeit</font>
|
||||
<ul layout:fragment="menuitem">
|
||||
<li class="nav-item" th:if="${#authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('timetrack_user')}">
|
||||
<li class="nav-item" sec:authorize="hasRole('timetrack_user')">
|
||||
<form th:action="@{/done/list}" th:object="${doneModel}" method="post">
|
||||
<div class="nav-link" style="padding-top: 5px !important; padding-bottom: 0px !important">
|
||||
<div class="input-group input-group-sm mb-3" style="margin-bottom: 0px !important">
|
||||
@ -19,7 +19,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
<ul layout:fragment="menu">
|
||||
<li class="nav-item" th:if="${#authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('timetrack_user')}">
|
||||
<li class="nav-item" sec:authorize="hasRole('timetrack_user')">
|
||||
<table>
|
||||
<tr>
|
||||
<td><a class="nav-link btn btn-success btn-white-text" th:href="@{/done/add/{day}(day=${doneModel.day})}">Neuer
|
||||
|
@ -27,7 +27,7 @@
|
||||
<div class="col-sm-10">
|
||||
<button id="okbtn" type="submit" class="btn btn-success">speichern</button>
|
||||
<a class="btn btn-secondary" th:href="@{/done/list}">abbrechen</a>
|
||||
<div class="dropdown float-right" th:if="${moduleBean.pk != null and #authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('timetrack_user')}">
|
||||
<div class="dropdown float-right" th:if="${moduleBean.pk != null}" sec:authorize="hasRole('timetrack_user')">
|
||||
<button class="btn btn-danger dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Eintrag löschen</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" th:href="@{/done/delete/module/{id}(id=${moduleBean.pk})}">endgültig löschen</a></li>
|
||||
|
@ -27,7 +27,7 @@
|
||||
<div class="col-sm-10">
|
||||
<button id="okbtn" type="submit" class="btn btn-success">speichern</button>
|
||||
<a class="btn btn-secondary" th:href="@{/done/list}">abbrechen</a>
|
||||
<div class="dropdown float-right" th:if="${projectBean.pk != null and #authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('timetrack_user')}">
|
||||
<div class="dropdown float-right" th:if="${projectBean.pk != null}" sec:authorize="hasRole('timetrack_user')">
|
||||
<button class="btn btn-danger dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Eintrag löschen</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" th:href="@{/done/delete/project/{id}(id=${projectBean.pk})}">endgültig löschen</a></li>
|
||||
|
@ -49,7 +49,7 @@
|
||||
<div class="col-sm-10">
|
||||
<button type="submit" class="btn btn-success">speichern</button>
|
||||
<button type="submit" class="btn btn-secondary" th:formaction="@{/note/list}">abbrechen</button>
|
||||
<div class="dropdown float-right" th:if="${noteBean.pk != null and #authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('timetrack_user')}">
|
||||
<div class="dropdown float-right" th:if="${noteBean.pk != null}" sec:authorize="hasRole('timetrack_user')">
|
||||
<button class="btn btn-danger dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Eintrag löschen</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" th:href="@{/note/delete/{id}(id=${noteBean.pk})}">endgültig löschen</a></li>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<body>
|
||||
<font layout:fragment="title">Notizen</font>
|
||||
<ul layout:fragment="menu">
|
||||
<li class="nav-item" th:if="${#authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('timetrack_user')}"><a class="nav-link btn btn-success btn-white-text" th:href="@{/note/add}">Neue Notiz
|
||||
<li class="nav-item" sec:authorize="hasRole('timetrack_user')"><a class="nav-link btn btn-success btn-white-text" th:href="@{/note/add}">Neue Notiz
|
||||
anlegen</a></li>
|
||||
</ul>
|
||||
<main layout:fragment="content">
|
||||
@ -27,7 +27,7 @@
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-center align-items-center">
|
||||
<pre th:text="${note.content}"></pre>
|
||||
<a th:href="@{/note/edit/{id}(id=${note.pk})}" th:if="${#authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('timetrack_user')}" style="margin-left: 8px;">
|
||||
<a th:href="@{/note/edit/{id}(id=${note.pk})}" sec:authorize="hasRole('timetrack_user')" style="margin-left: 8px;">
|
||||
<i class="fa fa-edit"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -41,18 +41,6 @@
|
||||
th:text="${sum.getBillingTime(null)}" class="distfat"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- for debugging only, set display to block -->
|
||||
<div class="card" style="display: none">
|
||||
<div class="card-body">
|
||||
<span th:text="${#authentication.principal.attributes['resource_access']['timetrack']['roles']}"></span><br />
|
||||
<span th:if="${#authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('timetrack_user')}">role timetrack_user found directly</span><br />
|
||||
<span sec:authorize="hasRole('timetrack_user')" style="color: green">well done</span>
|
||||
<span sec:authorize="!hasRole('timetrack_user')" style="color: rgb(165, 29, 45)">role timetrack_user not yet detected</span>
|
||||
<br />
|
||||
<span>found roles:</span><br />
|
||||
<span th:each="r : ${#authentication.principal.attributes['resource_access']['timetrack']['roles']}" th:if="${r}" th:text="${r + ' '}" style="color: royalblue"></span>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
|
Reference in New Issue
Block a user