conversion to jakarta
This commit is contained in:
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
Reference in New Issue
Block a user