TSK-868: Added Filter to map elytron Roles into JAAS-Subject
This commit is contained in:
parent
ed796b39a1
commit
72a9a98b16
1
pom.xml
1
pom.xml
|
@ -59,6 +59,7 @@
|
|||
<!-- java ee dependencies -->
|
||||
<version.resteasy>3.1.2.Final</version.resteasy>
|
||||
<version.thorntail>2.3.0.Final</version.thorntail>
|
||||
<version.wildfly.security>1.6.1.Final</version.wildfly.security>
|
||||
<version.javaee-api>7.0</version.javaee-api>
|
||||
<version.arquillian>1.1.10.Final</version.arquillian>
|
||||
|
||||
|
|
|
@ -41,18 +41,25 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||
"/img/**")
|
||||
.permitAll()
|
||||
.and()
|
||||
.csrf().disable()
|
||||
.csrf()
|
||||
.disable()
|
||||
.httpBasic()
|
||||
.and()
|
||||
.authenticationProvider(jaasAuthProvider())
|
||||
.authorizeRequests()
|
||||
.antMatchers(HttpMethod.GET, "/docs/**").permitAll()
|
||||
.antMatchers(HttpMethod.GET, "/docs/**")
|
||||
.permitAll()
|
||||
.and()
|
||||
.addFilter(new JaasApiIntegrationFilter());
|
||||
|
||||
if (devMode) {
|
||||
http.headers().frameOptions().sameOrigin()
|
||||
.and().authorizeRequests().antMatchers("/h2-console/**").permitAll();
|
||||
http.headers()
|
||||
.frameOptions()
|
||||
.sameOrigin()
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/h2-console/**")
|
||||
.permitAll();
|
||||
} else {
|
||||
AddLoginPageConfiguration(http);
|
||||
}
|
||||
|
@ -99,9 +106,12 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||
private void AddLoginPageConfiguration(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().fullyAuthenticated()
|
||||
.anyRequest()
|
||||
.fullyAuthenticated()
|
||||
.and()
|
||||
.formLogin().loginPage("/login").failureUrl("/login?error")
|
||||
.formLogin()
|
||||
.loginPage("/login")
|
||||
.failureUrl("/login?error")
|
||||
.defaultSuccessUrl("/")
|
||||
.permitAll()
|
||||
.and()
|
||||
|
|
|
@ -79,6 +79,17 @@
|
|||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.wildfly.security</groupId>
|
||||
<artifactId>wildfly-elytron</artifactId>
|
||||
<version>${version.wildfly.security}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- TEST -->
|
||||
<dependency>
|
||||
|
@ -86,16 +97,7 @@
|
|||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.thorntail</groupId>
|
||||
<artifactId>jaxrs</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.thorntail</groupId>
|
||||
<artifactId>cdi</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-client</artifactId>
|
||||
|
@ -132,10 +134,10 @@
|
|||
<activation>
|
||||
<activeByDefault>false</activeByDefault>
|
||||
</activation>
|
||||
<!-- Reading properties from file and use then as data source properties
|
||||
is a pending improvement of Jboss development team check out -> https://issues.jboss.org/browse/WFMP-70
|
||||
That's why we are including postgres connection properties directly in this pom file.
|
||||
-->
|
||||
<!-- Reading properties from file and use then as data source properties
|
||||
is a pending improvement of Jboss development team check out -> https://issues.jboss.org/browse/WFMP-70
|
||||
That's why we are including postgres connection properties directly in this
|
||||
pom file. -->
|
||||
<properties>
|
||||
<driver-class>org.postgresql.Driver</driver-class>
|
||||
<connection-url>jdbc:postgresql://localhost:50102/postgres</connection-url>
|
||||
|
@ -162,6 +164,22 @@
|
|||
<version>${version.maven.wildfly}</version>
|
||||
<configuration>
|
||||
<version>11.0.0.Final</version>
|
||||
<add-user>
|
||||
<users>
|
||||
<user>
|
||||
<username>admin</username>
|
||||
<password>admin</password>
|
||||
</user>
|
||||
<user>
|
||||
<username>admin</username>
|
||||
<password>admin</password>
|
||||
<groups>
|
||||
<group>testGroup</group>
|
||||
</groups>
|
||||
<applicationUser>true</applicationUser>
|
||||
</user>
|
||||
</users>
|
||||
</add-user>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
|
@ -208,6 +226,39 @@
|
|||
<goal>add-resource</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>edit-undertow</id>
|
||||
<phase>install</phase>
|
||||
<goals>
|
||||
<goal>execute-commands</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<commands>
|
||||
<command>/subsystem=undertow:write-attribute(name=default-server,value=default-server)</command>
|
||||
<command>/subsystem=undertow:write-attribute(name=default-virtual-host,value=default-host)</command>
|
||||
<command>/subsystem=undertow:write-attribute(name=default-servlet-container,value=default)</command>
|
||||
<command>/subsystem=undertow:write-attribute(name=default-security-domain,value=ApplicationDomain)</command>
|
||||
</commands>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>add-application-security-domain</id>
|
||||
<phase>install</phase>
|
||||
<goals>
|
||||
<goal>add-resource</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<address>subsystem=undertow</address>
|
||||
<resources>
|
||||
<resource>
|
||||
<address>application-security-domain=ApplicationDomain</address>
|
||||
<properties>
|
||||
<http-authentication-factory>application-http-authentication</http-authentication-factory>
|
||||
</properties>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
<!-- Deploy the application on install -->
|
||||
<execution>
|
||||
<id>wildfly-deploy</id>
|
||||
|
@ -216,14 +267,6 @@
|
|||
<goal>deploy</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<!-- undeploy the application on install -->
|
||||
<execution>
|
||||
<id>wildfly-undeploy</id>
|
||||
<phase>install</phase>
|
||||
<goals>
|
||||
<goal>undeploy</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<!-- shutdown the application on install -->
|
||||
<execution>
|
||||
<id>wildfly-shutdown</id>
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
package pro.taskana.wildfly.security;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.AccessController;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.filter.GenericFilterBean;
|
||||
import org.wildfly.security.auth.server.SecurityDomain;
|
||||
import org.wildfly.security.auth.server.SecurityIdentity;
|
||||
import org.wildfly.security.authz.Roles;
|
||||
|
||||
import pro.taskana.security.GroupPrincipal;
|
||||
|
||||
/**
|
||||
* Simple Filter to map all Elytron Roles to JAAS-Principals.
|
||||
*/
|
||||
public class ElytronToJaasFilter extends GenericFilterBean {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
SecurityIdentity securityIdentity = getSecurityIdentity();
|
||||
if (securityIdentity != null) {
|
||||
Roles roles = securityIdentity.getRoles();
|
||||
Subject subject = obtainSubject(request);
|
||||
if (subject != null) {
|
||||
if (subject.getPrincipals().size() == 0) {
|
||||
subject.getPrincipals().add(securityIdentity.getPrincipal());
|
||||
}
|
||||
if (subject.getPrincipals(GroupPrincipal.class).size() == 0) {
|
||||
roles.forEach(role -> subject.getPrincipals().add(new GroupPrincipal(role)));
|
||||
}
|
||||
}
|
||||
}
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
private SecurityIdentity getSecurityIdentity() {
|
||||
SecurityDomain current = SecurityDomain.getCurrent();
|
||||
if (current != null) {
|
||||
return current.getCurrentSecurityIdentity();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Obtains the <code>Subject</code> to run as or <code>null</code> if no <code>Subject</code> is available.
|
||||
* </p>
|
||||
* <p>
|
||||
* The default implementation attempts to obtain the <code>Subject</code> from the <code>SecurityContext</code>'s
|
||||
* <code>Authentication</code>. If it is of type <code>JaasAuthenticationToken</code> and is authenticated, the
|
||||
* <code>Subject</code> is returned from it. Otherwise, <code>null</code> is returned.
|
||||
* </p>
|
||||
*
|
||||
* @param request
|
||||
* the current <code>ServletRequest</code>
|
||||
* @return the Subject to run as or <code>null</code> if no <code>Subject</code> is available.
|
||||
*/
|
||||
protected Subject obtainSubject(ServletRequest request) {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Attempting to obtainSubject using authentication : " + authentication);
|
||||
}
|
||||
if (authentication == null) {
|
||||
return null;
|
||||
}
|
||||
if (!authentication.isAuthenticated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Subject.getSubject(AccessController.getContext());
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package pro.taskana.wildfly.security;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.view.RedirectView;
|
||||
|
||||
/**
|
||||
* The loginError controller.
|
||||
*/
|
||||
@Controller
|
||||
public class LoginErrorController {
|
||||
|
||||
@PostMapping
|
||||
@GetMapping
|
||||
@RequestMapping("/loginerror")
|
||||
public RedirectView loginError() {
|
||||
return new RedirectView("/login?error", true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
package pro.taskana.wildfly.security;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter;
|
||||
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.wildfly.security.auth.server.SecurityDomain;
|
||||
import org.wildfly.security.auth.server.SecurityIdentity;
|
||||
import org.wildfly.security.authz.Roles;
|
||||
|
||||
import pro.taskana.rest.security.WebSecurityConfig;
|
||||
|
||||
/**
|
||||
* Default basic configuration for taskana web example running on Wildfly / JBoss with Elytron or JAAS Security.
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@Order(1)
|
||||
public class WildflyWebSecurityConfig extends WebSecurityConfig {
|
||||
|
||||
@Value("${devMode:false}")
|
||||
private boolean devMode;
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests()
|
||||
.antMatchers(
|
||||
"/css/**",
|
||||
"/img/**")
|
||||
.permitAll()
|
||||
.and()
|
||||
.csrf()
|
||||
.disable()
|
||||
.httpBasic()
|
||||
.and()
|
||||
.authenticationProvider(preauthAuthProvider())
|
||||
.authorizeRequests()
|
||||
.antMatchers(HttpMethod.GET, "/docs/**")
|
||||
.permitAll()
|
||||
.and()
|
||||
.addFilter(preAuthFilter())
|
||||
.addFilterAfter(new ElytronToJaasFilter(), JaasApiIntegrationFilter.class)
|
||||
.addFilter(jaasApiIntegrationFilter());
|
||||
|
||||
if (devMode) {
|
||||
http.headers()
|
||||
.frameOptions()
|
||||
.sameOrigin()
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/h2-console/**")
|
||||
.permitAll();
|
||||
} else {
|
||||
addLoginPageConfiguration(http);
|
||||
}
|
||||
}
|
||||
|
||||
private JaasApiIntegrationFilter jaasApiIntegrationFilter() {
|
||||
JaasApiIntegrationFilter filter = new JaasApiIntegrationFilter();
|
||||
filter.setCreateEmptySubject(true);
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public J2eePreAuthenticatedProcessingFilter preAuthFilter() throws Exception {
|
||||
J2eePreAuthenticatedProcessingFilter filter = new J2eePreAuthenticatedProcessingFilter();
|
||||
filter.setAuthenticationManager(preAuthManager());
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager preAuthManager() {
|
||||
return new AuthenticationManager() {
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
return preauthAuthProvider().authenticate(authentication);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PreAuthenticatedAuthenticationProvider preauthAuthProvider() {
|
||||
PreAuthenticatedAuthenticationProvider preauthAuthProvider = new PreAuthenticatedAuthenticationProvider();
|
||||
preauthAuthProvider.setPreAuthenticatedUserDetailsService(
|
||||
authenticationUserDetailsService());
|
||||
return preauthAuthProvider;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService() {
|
||||
return new AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken>() {
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken token)
|
||||
throws UsernameNotFoundException {
|
||||
return new UserDetails() {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return token.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return (String) token.getCredentials();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
List<GrantedAuthority> authorities = new ArrayList<>();
|
||||
SecurityIdentity securityIdentity = getSecurityIdentity();
|
||||
if (securityIdentity != null) {
|
||||
Roles roles = securityIdentity.getRoles();
|
||||
roles.forEach(role -> authorities.add(new SimpleGrantedAuthority(role)));
|
||||
}
|
||||
return authorities;
|
||||
}
|
||||
|
||||
private SecurityIdentity getSecurityIdentity() {
|
||||
SecurityDomain current = SecurityDomain.getCurrent();
|
||||
if (current != null) {
|
||||
return current.getCurrentSecurityIdentity();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void addLoginPageConfiguration(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest()
|
||||
.fullyAuthenticated()
|
||||
.and()
|
||||
.formLogin()
|
||||
.loginPage("/login")
|
||||
.failureUrl("/login?error")
|
||||
.defaultSuccessUrl("/")
|
||||
.permitAll()
|
||||
.and()
|
||||
.logout()
|
||||
.invalidateHttpSession(true)
|
||||
.clearAuthentication(true)
|
||||
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
|
||||
.logoutSuccessUrl("/login?logout")
|
||||
.deleteCookies("JSESSIONID")
|
||||
.permitAll();
|
||||
}
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"taskanaRestUrl": "http://localhost:8080/web-application/other-route"
|
||||
"taskanaRestUrl": "http://localhost:8080/taskana"
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<link rel="icon" type="image/x-icon" th:href="@{/img/logo.png}">
|
||||
<title>Taskana login</title>
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/bootstrap/4.1.3/bootstrap.min.css}"/>
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/main.css}"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<h1 class="row justify-content-center">Welcome to Taskana</h1>
|
||||
<div class="row justify-content-center">
|
||||
<div>
|
||||
<img th:src="@{/img/logo.png}" class="logo" alt="memorynotfound logo"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center align-items-center">
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<form action="j_security_check" method="post">
|
||||
<div th:if="${param.error}">
|
||||
<div class="alert alert-danger">
|
||||
Invalid username or password.
|
||||
</div>
|
||||
</div>
|
||||
<div th:if="${param.logout}">
|
||||
<div class="alert alert-info">
|
||||
You have been logged out.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>:
|
||||
<input type="text"
|
||||
id="username"
|
||||
name="j_username"
|
||||
class="form-control"
|
||||
autofocus="autofocus"
|
||||
placeholder="Username">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>:
|
||||
<input type="password"
|
||||
id="password"
|
||||
name="j_password"
|
||||
class="form-control"
|
||||
placeholder="Password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-sm-6 col-sm-offset-3">
|
||||
<input type="submit"
|
||||
name="login-submit"
|
||||
id="login-submit"
|
||||
class="form-control btn btn-info"
|
||||
value="Log In">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -3,5 +3,5 @@
|
|||
xmlns="http://www.jboss.com/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-web_7_2.xsd">
|
||||
<context-root>/web-application/other-route</context-root>
|
||||
<context-root>/taskana</context-root>
|
||||
</jboss-web>
|
|
@ -4,4 +4,12 @@
|
|||
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
|
||||
|
||||
<display-name>TaskanaRestWildflySpring</display-name>
|
||||
|
||||
<login-config>
|
||||
<auth-method>FORM</auth-method>
|
||||
<form-login-config>
|
||||
<form-login-page>/login</form-login-page>
|
||||
<form-error-page>/loginerror</form-error-page>
|
||||
</form-login-config>
|
||||
</login-config>
|
||||
</web-app>
|
Loading…
Reference in New Issue