conversion to jakarta

This commit is contained in:
Jörg Henke
2023-07-28 20:23:31 +02:00
parent f07f8f3c06
commit 18d3efb87d
14 changed files with 61 additions and 83 deletions

View File

@ -1,23 +1,17 @@
package de.jottyfan.timetrack.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
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.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
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 de.jottyfan.timetrack.config.converter.KeycloakRealmRoleConverter;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
/**
*
@ -28,9 +22,11 @@ import de.jottyfan.timetrack.config.converter.KeycloakRealmRoleConverter;
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfiguration {
@Value("${spring.security.oauth2.client.provider.keycloak.jwk-set-uri}")
private String jwtSetUri;
@Bean
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity sec, InMemoryClientRegistrationRepository crr)
@ -40,19 +36,19 @@ 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 -> j.jwtAuthenticationConverter(getConverter())));
.oauth2ResourceServer(o -> o.jwt(j -> roleConverter()))
.sessionManagement(o -> o.init(sec));
// @formatter:on
return sec.build();
}
private Converter<Jwt, ? extends AbstractAuthenticationToken> getConverter() {
JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
jwtConverter.setJwtGrantedAuthoritiesConverter(new KeycloakRealmRoleConverter());
return jwtConverter;
}
@Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri(this.jwtSetUri).build();
public JwtAuthenticationConverter roleConverter() {
JwtGrantedAuthoritiesConverter gac = new JwtGrantedAuthoritiesConverter();
gac.setAuthorityPrefix("ROLE_");
gac.setAuthoritiesClaimName("roles");
JwtAuthenticationConverter jac = new JwtAuthenticationConverter();
jac.setJwtGrantedAuthoritiesConverter(gac);
return jac;
}
}

View File

@ -1,29 +0,0 @@
package de.jottyfan.timetrack.config.converter;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
/**
*
* @author henkej
*
*/
public class KeycloakRealmRoleConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
@Override
public Collection<GrantedAuthority> convert(Jwt jwt) {
Object o = jwt.getClaims().get("realm_access");
@SuppressWarnings("unchecked")
final Map<String, Object> realmAccess = (Map<String, Object>) o;
Object o2 = realmAccess.get("roles");
@SuppressWarnings("unchecked")
List<String> l = (List<String>) o2;
return l.stream().map(roleName -> "ROLE_" + roleName).map(SimpleGrantedAuthority::new).collect(Collectors.toList());
}
}

View File

@ -1,25 +1,24 @@
# include properties file from /etc
spring.config.import = /etc/timetrack.properties
# jooq
spring.datasource.driver-class-name = org.postgresql.Driver
# todo: export to /etc/timetrack
spring.datasource.url = jdbc:postgresql://localhost:5432/timetrack
spring.datasource.username = timetrack
spring.datasource.password = timetrack
spring.datasource.url = ${db.url}
spring.datasource.username = ${db.username}
spring.datasource.password = ${db.password}
# security
keycloak.url = http://localhost:8080/realms/jottyfan
keycloak.openid.url = ${keycloak.url}/protocol/openid-connect
spring.security.oauth2.client.registration.keycloak.client-id = timetrack
spring.security.oauth2.client.registration.keycloak.client-id = ${keycloak.client-id}
spring.security.oauth2.client.registration.keycloak.scope = openid
spring.security.oauth2.client.registration.keycloak.authorization-grant-type = authorization_code
# todo: export to /etc/timetrack
spring.security.oauth2.client.registration.keycloak.redirect-uri = http://localhost:8888/timetrack/login/oauth2/code/timetrack
spring.security.oauth2.client.provider.keycloak.issuer-uri = ${keycloak.url}
spring.security.oauth2.client.provider.keycloak.authorization-uri = ${keycloak.openid.url}/auth
spring.security.oauth2.client.provider.keycloak.token-uri = ${keycloak.openid.url}/token
spring.security.oauth2.client.provider.keycloak.user-info-uri = ${keycloak.openid.url}/userinfo
spring.security.oauth2.client.provider.keycloak.jwk-set-uri = ${keycloak.openid.url}/certs
spring.security.oauth2.client.registration.keycloak.redirect-uri = ${keycloak.redirect-uri}
spring.security.oauth2.client.provider.keycloak.issuer-uri = ${keycloak.issuer-uri}
spring.security.oauth2.client.provider.keycloak.authorization-uri = ${keycloak.openid-url}/auth
spring.security.oauth2.client.provider.keycloak.token-uri = ${keycloak.openid-url}/token
spring.security.oauth2.client.provider.keycloak.user-info-uri = ${keycloak.openid-url}/userinfo
spring.security.oauth2.client.provider.keycloak.jwk-set-uri = ${keycloak.openid-url}/certs
spring.security.oauth2.client.provider.keycloak.user-name-attribute = preferred_username
# application
server:.port = 8083
server.port = 8083
server.servlet.context-path = /timetrack

View File

@ -6,7 +6,7 @@
<body>
<font layout:fragment="title">Kalender</font>
<ul layout:fragment="menu">
<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>
<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>
</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 sec:authorize="hasRole('timetrack_user')">
<div th:if="${#authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('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 sec:authorize="hasRole('timetrack_user')">
<div th:if="${#authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('timetrack_user')}">
<!-- TODO: add the options here -->
</div>
</div>

View File

@ -10,7 +10,7 @@
<main layout:fragment="content">
<div class="container formpane">
<form th:action="@{/contact/upsert}" th:object="${contactBean}" method="post">
<div class="row mb-3">
<div class="row mb-3" th:style="${contactBean.pk == null ? 'display: none' : ''}">
<label for="inputPk" class="col-sm-2 col-form-label">Inhalt von Eintrag</label>
<div class="col-sm-10">
<input id="inputPk" type="text" th:field="*{pk}" class="form-control" readonly="readonly" />
@ -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}" sec:authorize="hasRole('timetrack_user')">
<div class="dropdown float-right" th:if="${contactBean.pk != null and #authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('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>

View File

@ -7,7 +7,7 @@
<body>
<font layout:fragment="title">Kontakte</font>
<ul layout:fragment="menu">
<li class="nav-item" sec:authorize="hasRole('timetrack_user')"><a class="nav-link btn btn-success btn-white-text" th:href="@{/contact/add}">Neuen
<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
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})}" sec:authorize="hasRole('timetrack_user')" style="margin-left: 8px;">
th:href="@{/contact/edit/{id}(id=${contact.pk})}" th:if="${#authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('timetrack_user')}" style="margin-left: 8px;">
<i class="fa fa-edit"></i>
</a>
</div>

View File

@ -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}" sec:authorize="hasRole('timetrack_user')">
<div class="dropdown float-right" th:if="${doneBean.pk != null and #authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('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>

View File

@ -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}" sec:authorize="hasRole('timetrack_user')">
<div class="dropdown float-right" th:if="${jobBean.pk != null and #authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('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>

View File

@ -7,7 +7,7 @@
<body>
<font layout:fragment="title">Arbeitszeit</font>
<ul layout:fragment="menuitem">
<li class="nav-item" sec:authorize="hasRole('timetrack_user')">
<li class="nav-item" th:if="${#authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('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" sec:authorize="hasRole('timetrack_user')">
<li class="nav-item" th:if="${#authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('timetrack_user')}">
<table>
<tr>
<td><a class="nav-link btn btn-success btn-white-text" th:href="@{/done/add/{day}(day=${doneModel.day})}">Neuer

View File

@ -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}" sec:authorize="hasRole('timetrack_user')">
<div class="dropdown float-right" th:if="${moduleBean.pk != null and #authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('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>

View File

@ -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}" sec:authorize="hasRole('timetrack_user')">
<div class="dropdown float-right" th:if="${projectBean.pk != null and #authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('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>

View File

@ -10,7 +10,7 @@
<main layout:fragment="content">
<div class="container formpane">
<form th:action="@{/note/upsert}" th:object="${noteBean}" method="post">
<div class="row mb-3">
<div class="row mb-3" th:style="${noteBean.pk == null ? 'display: none' : ''}">
<label for="inputPk" class="col-sm-2 col-form-label">Inhalt von Eintrag</label>
<div class="col-sm-10">
<input id="inputPk" type="text" th:field="*{pk}" class="form-control" readonly="readonly" />
@ -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}" sec:authorize="hasRole('timetrack_user')">
<div class="dropdown float-right" th:if="${noteBean.pk != null and #authentication.principal.attributes['resource_access']['timetrack']['roles'].contains('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>

View File

@ -7,7 +7,7 @@
<body>
<font layout:fragment="title">Notizen</font>
<ul layout:fragment="menu">
<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
<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
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})}" sec:authorize="hasRole('timetrack_user')" style="margin-left: 8px;">
<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;">
<i class="fa fa-edit"></i>
</a>
</div>

View File

@ -41,6 +41,18 @@
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>