Jan @Novoj Novotný
set of filters can differ for various path patterns
(org.springframework.security.web.SecurityFilterChain)
path matching is done via RequestMatcher
mostly via AntPathRequestMatcher but also RegexRequestMatcher and more
/foo/*/bar/**/data/*.jpg
/foo/**
/bar/*
/*.html
ChannelProcessingFilter - relocates user from HTTP to HTTPS (and vice versa) when ChannelDecisionManager says so
HeaderWriterFilter - takes care of writing HTTP security headers to the response
ConcurrentSessionFilter - tracks presence of all sessions,
SessionManagementFilter - enforces session management
Both types of configuration are similar one to another.
Typically UsernamePasswordAuthenticationFilter uses AuthenticationManager that calls AuthenticationProvider (usually DaoAuthenticationProvider) to load user details from UserDetailsService implementation.
DaoAuthenticationProvider checks validity of the password via PasswordEncoder.
When password is ok, new Authentication object is created and stored in SecurityContext.
SecurityContextPersistenceFilter fills SecurityContextHolder (usually ThreadLocal) with SecurityContext from SecurityContextRepository
/admin/**=ROLE_ADMINISTRATOR, ROLE_SUPER_ADMINISTRATOR
/userManagement/**=#{hasRole('ROLE_COMPANY_OWNER') and hasIpAddress('192.168.1.0/24')
/**=IS_AUTHENTICATED_ANONYMOUSLY
You can use either simple rules or expression rules, not both.
<http use-expressions="true">
<intercept-url pattern="/user/{userId}/**"
access="@myAuthService.checkUserId(authentication,#userId)"/>
</http>
@ refers to Spring bean objects
# refers to url parameters
Operates using Spring AOP abstraction.
Thus you can configure it manually by custom Aspects or exact mapping.
Method annotations:
@PreAuthorize("#contact.name == authentication.name")
public void doSomething(Contact contact);
@PreAuthorize("hasRole('USER')")
@PostFilter("hasPermission(filterObject, 'read')")
public List<Contact> getAll();
Filtering is not suitable for paged output.
But Spring Security can integrate with Spring Data!
Useful in scenarios when user having certain role
should also have another role.
ROLE_ADMIN > ROLE_STAFF
ROLE_STAFF > ROLE_USER
ROLE_USER > ROLE_GUEST
Admin will be also STAFF, USER, GUEST regarding access decision logic.
Hierarchy rules can be changed without touching user data.
If you need more robust authorization system - ie. when roles are not enough.
Implement your own PermissionEvaluator
public interface PermissionEvaluator extends AopInfrastructureBean {
boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission);
boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission);
}
And configure it:
Or use already implemented ACL system from Spring Security.
Allows you to mitigate String based "programming".
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("#contact.name == authentication.name")
public @interface ContactPermission {}
@ContactPermission
public void doSomething(Contact contact);
Currently doesn't allow multiple annotations on a single method.
Issue #4003
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@SecurityExecutionListeners
public class ExampleTestClass {
@Test(expected = AccessDeniedException.class)
@WithAnonymousUser
public void getMessageUnauthenticated() {
messageService.getMessage();
}
@Test
@WithUserDetails("client@domain.cz")
public void getMessageAsClientUser() {
assertNotNull(messageService.getMessage());
}
@Test
@WithUserDetails("admin@domain.cz")
public void getMessageAsAdminUser() {
assertNotNull(messageService.getMessage());
}
}
To avoid strings in annotations and add better maintainability use meta-annotations:
@Retention(RetentionPolicy.RUNTIME)
@WithUserDetails(value="admin@domain.cz")
public @interface RunAsAdministrator { }
And use it in test:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@SecurityExecutionListeners
public class ExampleTestClass {
@Test
@RunAsClient
public void getMessageAsClientUser() {
assertNotNull(messageService.getMessage());
}
@Test
@RunAsAdmin
public void getMessageAsAdminUser() {
assertNotNull(messageService.getMessage());
}
}
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class ExampleTestClass {
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@Before
public void setup() {
mvc = MockMvcBuilders.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
@Test
@WithAnonymousUser
public void shouldUserLogin() {
mvc.perform(formLogin("/auth").user("admin").password("pass")).with(csrf()))
.andExpect(authenticated());
}
@Test
@RunAsClient
public void shouldUserLogout() {
mvc.perform(logout("/signout")).with(csrf()))
.andExpect(unauthenticated());
}
}
Handy for developers as well as for customer administrators. Allows user with admin roles to switch to any other user with lower roles without knowing password.
@Override
protected void configure(HttpSecurity http) throws Exception {
final SwitchUserFilter filter = new SwitchUserFilter();
filter.setSwitchUserUrl("/switchUser");
filter.setUsernameParameter("username");
filter.setExitUserUrl("/restoreUserBack");
http.addFilterAfter(filter, FilterSecurityInterceptor.class)
.authorizeRequests()
.antMatchers("/switchUser").hasAnyAuthority("ROLE_ADMIN")
.antMatchers("/restoreUser").authenticated();
}
Consider also adding an IP check or other authentication checks to better secure user switching.
Warning! This feature is kind of a backdoor to your application.
Contact me at @Novoj or novotnaci@gmail.com
/userProfile/*=IS_AUTHENTICATED_FULLY
/login=IS_AUTHENTICATED_ANONYMOUSLY
/logout=IS_AUTHENTICATED_ANONYMOUSLY
/lostPassword=IS_AUTHENTICATED_ANONYMOUSLY
/**=IS_AUTHENTICATED_REMEMBERED
base64(username + ":" + expirationTime + ":" +
md5Hex(username + ":" + expirationTime + ":" password + ":" + key))
username: As identifiable to the UserDetailsService
password: That matches the one in the retrieved UserDetails
expirationTime: The date and time when the remember-me token expires, expressed in milliseconds
key: A private key to prevent modification of the remember-me token
series:token
Technique that misuses an authenticated relation to execute operations via HTTP calls invisible to the user using well known URLs and parameters.
HTTP GET method is easiest to hack, other methods require social engeneering or JavaScript to execute. But are not hard to do.
Originally Spring Security used CSRF token per request but it was quite impractical (browser back button, partial update) and thus CSRF token is now unique to session.
X-XSRF-TOKEN
to HTTP header (attacker won't be able to do this due to same origin policy) ... see
CookieCsrfTokenRepositoryNew W3C proposal First-Party-Only-Cookies suggests SameSite cookie that could bring an and to CSRF attack entirely.
Targeted for all types of authentication cookies.
These are defaults:
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
By only declaring:
Header example:
X-Content-Type-Options: nosniff
Tells browsers (Chrome,IE) not to guess content type of the document from it's content.
Attack scenario: malicious user uploads PDF file that contains JavaScript. Your server let other users to download such file and open it in their browser. Even if server supplies PDF content type, browser might decide not to trust it and detect content type by the contents of the file. Finally it handles PDF file as JavaScript one and executes the script.
Header example:
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
Tells browser that all resources on this domain must be reached by HTTPS protocol.
Except first request all subsequent ones will be loaded over HTTPS by the browser. Even if there is explicit link with HTTP protocol.
First request problem can be solved with HSTS Preload
Header example:
Public-Key-Pins-Report-Only: max-age=5184000;
pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=";
pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=";
report-uri="http://example.net/pkp-report";
includeSubDomains
Tells browser that public key in the server certificate must match any of presented SHA256 hashes. Protects you against compromised CA.
Trust on First Use.
When using Public-Key-Pins
and the server delivers an unknown public key, the client should present a warning to the user.
Reports can be processed with Report URI service
Header example:
X-Frame-Options: deny
Tells browser not to load any FRAMES on your domain (or only frames targeting your/some domain).
Standardized as part of Content Security Policy.
Alternative variants:
X-Frame-Options: deny
X-Frame-Options: sameorigin
X-Frame-Options: allow-from www.seznam.cz
Header example:
X-XSS-Protection: 1; mode=block
Ask browser to assist you with reflected XSS attack.
If browser detects suspicious script in request that is reflected in the server response, script is blocked or reported.
Alternative variant:
X-XSS-Protection: 1; report=https://report-uri.io/
Header example:
Content-Security-Policy: script-src 'self' https://trustedscripts.example.com;
report-uri /csp-report-endpoint/
Site tels browser from which sources it expects to load resources.
Documentation at https://content-security-policy.com
Alternative variant:
Content-Security-Policy-Report-Only: script-src 'self';
report-uri /csp-report-endpoint/
After successfull authentication all registered
SessionAuthenticationStrategy
are called.
Preprogrammed implementations provide:
Attack vector: Attacker will create it's own session in the application. Via social engineering sends link to a user (or uses similar technique) that will make user browser to use already existing attacker session. When user logs in, attacker can act on behalf of the user.
Create new session on user login:
<session-management session-fixation-protection="migrateSession"/>
Spring Security can track all sessions in SessionRegistry.
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" expired-url="/sessionExpired.html"/>
When error-if-maximum-exceeded is false and max-sessions is exceeded least recently used session is invalidated. User using this session will get expired-url page.
Contact me at @Novoj or novotnaci@gmail.com