Spring Security

Java EE security

  • Lacks depth for typical enterprise applications
  • Not portable between environments  (WAR/EAR)

Spring security

  • Authentication - establishing principal (user, device, system)
  • Authorizatino - can principal execute operation?

Authentication

  • HTTP BASIC authentication headers (an IETF RFC-based standard)
  • HTTP Digest authentication headers (an IETF RFC-based standard)
  • HTTP X.509 client certificate exchange (an IETF RFC-based standard)
  • LDAP (a very common approach to cross-platform authentication needs, especially in large environments)
  • Form-based authentication (for simple user interface needs)
  • OpenID authentication
  • ...
  • ...
  • ...

Authorisation

  • Web requests
  • Method invocation
  • Access to individual domain object instance

Packages

  • spring-security-core.jar
  • spring-security-remoting.jar
  • spring-security-web.jar
  • spring-security-config.jar
  • spring-security-ldap.jar
  • spring-security-acl.jar
  • spring-security-cas.jar
  • spring-security-test.jar

Configuration

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
		auth
			.inMemoryAuthentication()
				.withUser("user").password("password").roles("USER");
	}
}

Configuration

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    protected void configure(HttpSecurity http) throws Exception {
	http
		.authorizeRequests()
			.anyRequest().authenticated()
			.and()
		.formLogin()
			.and()
		.httpBasic();
    }
}
<http>
	<intercept-url pattern="/**" access="authenticated"/>
	<form-login />
	<http-basic />
</http>

Form login

protected void configure(HttpSecurity http) throws Exception {
	http
		.authorizeRequests()
			.anyRequest().authenticated()
			.and()
		.formLogin()
			.loginPage("/login")
			.permitAll(); 

Authorisation

protected void configure(HttpSecurity http) throws Exception {
  http
    .authorizeRequests()
      .antMatchers("/resources/**", "/signup", "/about").permitAll()
      .antMatchers("/admin/**").hasRole("ADMIN")
      .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
      .anyRequest().authenticated()
      .and()
    // ...
    .formLogin();
}

Handling logouts

protected void configure(HttpSecurity http) throws Exception {
  http
    .logout()
      .logoutUrl("/my/logout")
      .logoutSuccessUrl("/my/index")
      .logoutSuccessHandler(logoutSuccessHandler)
      .invalidateHttpSession(true)
      .addLogoutHandler(logoutHandler)
      .deleteCookies(cookieNamesToClear)
      .and()
    ...
}

LogoutHandler and LogoutSuccessHandler

  • PersistentTokenBasedRememberMeServices
  • TokenBasedRememberMeServices
  • CookieClearingLogoutHandler
  • CsrfLogoutHandler
  • SecurityContextLogoutHandler
  • SimpleUrlLogoutSuccessHandler
  • HttpStatusReturningLogoutSuccessHandler

Authentication

  • In memory
  • JDBC
  • LDAP
  • Custom

In memory authentication

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
  auth
    .inMemoryAuthentication()
      .withUser("user").password("password").roles("USER").and()
      .withUser("admin").password("password").roles("USER", "ADMIN");
}

JDBC authentication

@Autowired
private DataSource dataSource;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
  auth
    .jdbcAuthentication()
      .dataSource(dataSource)
      .withDefaultSchema()
      .withUser("user").password("password").roles("USER").and()
      .withUser("admin").password("password").roles("USER", "ADMIN");
}

LDAP authentication

@Autowired
private DataSource dataSource;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
  auth
    .ldapAuthentication()
      .userDnPatterns("uid={0},ou=people")
      .groupSearchBase("ou=groups");
}
dn: ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: groups

dn: ou=people,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: people

dn: uid=admin,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Rod Johnson
sn: Johnson
uid: admin
userPassword: password

dn: uid=user,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Dianne Emu
sn: Emu
uid: user
userPassword: password

dn: cn=user,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: user
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
uniqueMember: uid=user,ou=people,dc=springframework,dc=org

dn: cn=admin,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: admin
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org

Custom AuthenticationProvider

public interface AuthenticationProvider {
  Authentication authenticate(Authentication authentication)
      throws AuthenticationException;
  boolean supports(Class<?> authentication);
}
@Bean
public SpringAuthenticationProvider springAuthenticationProvider() {
  return new SpringAuthenticationProvider();
}

Custom UserDetailsService and PasswordEncoder

public interface UserDetailsService {
  UserDetails loadUserByUsername(String username)
    throws UsernameNotFoundException;
}
@Bean
public SpringDataUserDetailsService springDataUserDetailsService() {
  return new SpringDataUserDetailsService();
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
  return new BCryptPasswordEncoder();
}

Method security (@Secured)

@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {
// ...
}
public interface BankService {

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();

@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}

Method security (@PreAuthorize)

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
// ...
}
public interface BankService {

@PreAuthorize("isAnonymous()")
public Account readAccount(Long id);

@PreAuthorize("isAnonymous()")
public Account[] findAccounts();

@PreAuthorize("hasAuthority('ROLE_TELLER')")
public Account post(Account account, double amount);
}

Method security and GlobalMethodSecurityConfiguration

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
	@Override
	protected MethodSecurityExpressionHandler createExpressionHandler() {
		// ... create and return custom MethodSecurityExpressionHandler ...
		return expressionHandler;
	}
}

XML Configuration

web.xml

<filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

xmlns:security

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/security
		http://www.springframework.org/schema/security/spring-security.xsd">
	...
</beans>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/security
		http://www.springframework.org/schema/security/spring-security.xsd">
	...
</beans:beans>

Minimal <http> configuration

<http>
  <intercept-url pattern="/**" access="hasRole('USER')" />
  <form-login />
  <logout />
</http>

Authentication

<authentication-manager>
<authentication-provider>
	<user-service>
	<user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
	<user name="bob" password="bobspassword" authorities="ROLE_USER" />
	</user-service>
</authentication-provider>
</authentication-manager>

Authentication

<authentication-manager>
	<authentication-provider user-service-ref='myUserDetailsService'/>
</authentication-manager>
<authentication-manager>
<authentication-provider>
	<jdbc-user-service data-source-ref="securityDataSource"/>
</authentication-provider>
</authentication-manager>
<authentication-manager>
	<authentication-provider ref='myAuthenticationProvider'/>
</authentication-manager>

Password encoder

<beans:bean name="bcryptEncoder"
  class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

<authentication-manager>
<authentication-provider>
  <password-encoder ref="bcryptEncoder"/>
  <user-service>
  <user name="jimi" password="d7e6351eaa13189a5a3641bab846c8e8c69ba39f"
      authorities="ROLE_USER, ROLE_ADMIN" />
  <user name="bob" password="4e7421b1b8765d8f9406d87e7cc6aa784c4ab97f"
      authorities="ROLE_USER" />
  </user-service>
</authentication-provider>
</authentication-manager>

Form login

<http>
  <intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
  <intercept-url pattern="/**" access="ROLE_USER" />
  <form-login login-page='/login.jsp'/>
</http>
<http pattern="/css/**" security="none"/>
<http pattern="/login.jsp*" security="none"/>

<http use-expressions="false">
  <intercept-url pattern="/**" access="ROLE_USER" />
  <form-login login-page='/login.jsp'/>
</http>
<http pattern="/login.htm*" security="none"/>
<http use-expressions="false">
  <intercept-url pattern='/**' access='ROLE_USER' />
  <form-login login-page='/login.htm' default-target-url='/home.htm'
		always-use-default-target='true' />
</http>

Basic authentication

<http use-expressions="false">
  <intercept-url pattern="/**" access="ROLE_USER" />
  <http-basic />
</http>

Filters

  • http://docs.spring.io/spring-security/site/docs/4.1.2.BUILD-SNAPSHOT/reference/html/ns-config.html#ns-custom-filters
  • We can create our own filters
<http>
<custom-filter position="FORM_LOGIN_FILTER" ref="myFilter" />
</http>

<beans:bean id="myFilter" class="com.mycompany.MySpecialAuthenticationFilter"/>

<global-method-security>

<global-method-security secured-annotations="enabled" />
public interface BankService {

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();

@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}
<global-method-security pre-post-annotations="enabled" />
public interface BankService {

@PreAuthorize("isAnonymous()")
public Account readAccount(Long id);

@PreAuthorize("isAnonymous()")
public Account[] findAccounts();

@PreAuthorize("hasAuthority('ROLE_TELLER')")
public Account post(Account account, double amount);
}

Security pointcuts

<global-method-security>
<protect-pointcut expression="execution(* com.mycompany.*Service.*(..))"
	access="ROLE_USER"/>
</global-method-security>

Internals

SecurityContextHolder

  • Bullet One
  • Bullet Two
  • Bullet Three

Copy of Spring Security

By Artur Owczarek

Copy of Spring Security

  • 462