TSK-868: Added Filter to map elytron Roles into JAAS-Subject

This commit is contained in:
Dennis Lehmann 2019-08-06 08:49:47 +02:00 committed by Holger Hagen
parent ed796b39a1
commit 72a9a98b16
10 changed files with 453 additions and 30 deletions

View File

@ -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>

View File

@ -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()

View File

@ -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>

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -1,3 +1,3 @@
{
"taskanaRestUrl": "http://localhost:8080/web-application/other-route"
"taskanaRestUrl": "http://localhost:8080/taskana"
}

View File

@ -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>

View File

@ -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>

View File

@ -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>