Skip to content

Instantly share code, notes, and snippets.

@destan
Last active May 15, 2022 01:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save destan/7454349eb96c97529f82b23988094af4 to your computer and use it in GitHub Desktop.
Save destan/7454349eb96c97529f82b23988094af4 to your computer and use it in GitHub Desktop.
Spring Security cheetsheet
https://gist.asciidoctor.org/?7454349eb96c97529f82b23988094af4[View this document rendered]
## SecurityContextHolder
* Holds the details of the authentication (authenticated user)
* By default the SecurityContextHolder uses a ThreadLocal to store these details
** SecurityContextHolder.MODE_THREADLOCAL `default`
** SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
** SecurityContextHolder.MODE_GLOBAL
image::https://i.imgur.com/bUzfJAI.png[SecurityContextHolder & SecurityContext]
.**Accessing current user statically**
```java
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String username;
if (principal instanceof UserDetails) {
username = ((UserDetails)principal).getUsername();
}
else {
username = principal.toString();
}
```
## UserDetailsService
* The principal is just an `Object`.
** Most of the time this can be cast into a `UserDetails` object.
* Think of `UserDetails` as the adapter between your own user database and what Spring Security needs inside the `SecurityContextHolder`.
TIP: Being a representation of something from your own user database, quite often you will cast the `UserDetails` to the original object that your application provided, so you can call business-specific methods (like `getEmail()`, `getEmployeeNumber()` and so on).
There is a special interface called `UserDetailsService`. It has only one method:
```java
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
```
This is the most common approach to loading information for a user within Spring Security and you will see it used throughout the framework whenever information on a user is required.
## GrantedAuthority
* `Authentication#getAuthorities()`
* An authority that is granted to the principal.
* usually "roles", such as `ROLE_ADMINISTRATOR` or `ROLE_HR_SUPERVISOR`
* `GrantedAuthority` objects are usually loaded by the `UserDetailsService`
TIP: Usually the `GrantedAuthority` objects are application-wide permissions. They are not specific to a given domain object. Thus, you wouldn't likely have a `GrantedAuthority` to represent a permission to `Employee` object number 54, because if there are thousands of such authorities you would quickly run out of memory (or, at the very least, cause the application to take a long time to authenticate a user). Of course, Spring Security is expressly designed to handle this common requirement, but you’d instead use the project’s domain object security capabilities for this purpose.
.Summary
****
* `SecurityContextHolder`, to provide access to the `SecurityContext`.
* `SecurityContext`, to hold the `Authentication` and possibly request-specific security information.
* `Authentication`, to represent the principal in a Spring Security-specific manner.
* `GrantedAuthority`, to reflect the application-wide permissions granted to a principal.
* `UserDetails`, to provide the necessary information to build an `Authentication` object from your application’s DAOs or other source of security data.
* `UserDetailsService`, to create a UserDetails when passed in a String-based username (or certificate ID or the like).
****
## Authentication
.A standard authentication scenario that everyone is familiar with:
. A user is prompted to log in with a username and password.
. The system (successfully) verifies that the password is correct for the username.
. The context information for that user is obtained (their list of roles and so on).
. A security context is established for the user
. The user proceeds, potentially to perform some operation which is potentially protected by an access control mechanism which checks the required permissions for the operation against the current security context information.
The first three items constitute the authentication process.
.Behind the curtains, in Spring Security:
. The username and password are obtained and combined into an instance of `UsernamePasswordAuthenticationToken` (an instance of the `Authentication` interface, which we saw earlier).
. The token is passed to an instance of `AuthenticationManager` for validation.
. The `AuthenticationManager` returns a fully populated `Authentication` instance on successful authentication.
. The security context is established by calling `SecurityContextHolder.getContext().setAuthentication(…​)`, passing in the returned authentication object.
From that point on, the user is considered to be authenticated.
```java
import org.springframework.security.authentication.*;
import org.springframework.security.core.*;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
public class AuthenticationExample {
private static AuthenticationManager am = new SampleAuthenticationManager();
public static void main(String[] args) throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
while(true) {
System.out.println("Please enter your username:");
String name = in.readLine();
System.out.println("Please enter your password:");
String password = in.readLine();
try {
Authentication request = new UsernamePasswordAuthenticationToken(name, password);
Authentication result = am.authenticate(request);
SecurityContextHolder.getContext().setAuthentication(result);
break;
} catch(AuthenticationException e) {
System.out.println("Authentication failed: " + e.getMessage());
}
}
System.out.println("Successfully authenticated. Security context contains: " +
SecurityContextHolder.getContext().getAuthentication());
}
}
/**
* This AuthenticationManager will authenticate any user whose username and password are the same.
* It assigns a single role to every user.
*/
class SampleAuthenticationManager implements AuthenticationManager {
static final List<GrantedAuthority> AUTHORITIES = new ArrayList<GrantedAuthority>();
static {
AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));
}
public Authentication authenticate(Authentication auth) throws AuthenticationException {
if (auth.getName().equals(auth.getCredentials())) {
return new UsernamePasswordAuthenticationToken(auth.getName(),
auth.getCredentials(), AUTHORITIES);
}
throw new BadCredentialsException("Bad Credentials");
}
}
```
.Sample output
```text
Please enter your username:
bob
Please enter your password:
password
Authentication failed: Bad Credentials
Please enter your username:
bob
Please enter your password:
bob
Successfully authenticated. Security context contains: \
org.springframework.security.authentication.UsernamePasswordAuthenticationToken@441d0230: \
Principal: bob; Password: [PROTECTED]; \
Authenticated: true; Details: null; \
Granted Authorities: ROLE_USER
```
NOTE: Note that you don’t normally need to write any code like this. The process will normally occur internally, **in a web authentication filter** for example.
TIP: A user is authenticated when the `SecurityContextHolder` contains a fully populated `Authentication` object.
IMPORTANT: Spring Security doesn't mind how you put the `Authentication` object inside the `SecurityContextHolder`. The only critical requirement is that the `SecurityContextHolder` contains an `Authentication` before the `AbstractSecurityInterceptor` needs to authorize a user operation.
### ProviderManager
`ProviderManager` is the most commonly used implementation of `AuthenticationManager`. `ProviderManager` delegates to a List of `AuthenticationProviders`. Each `AuthenticationProvider` has an opportunity to indicate that authentication should be successful, fail, or indicate it cannot make a decision and allow a downstream `AuthenticationProvider` to decide. If none of the configured `AuthenticationProviders` can authenticate, then authentication will fail with a `ProviderNotFoundException` which is a special `AuthenticationException` that indicates the `ProviderManager` was not configured to support the type of `Authentication` that was passed into it.
image:https://i.imgur.com/wEec1C0.png[]
For more link:https://docs.spring.io/spring-security/site/docs/current/reference/html5/#servlet-authentication-authenticationmanager[see ProviderManager docs].
## Request Credentials with `AuthenticationEntryPoint`
`AuthenticationEntryPoint` is used to send an HTTP response that requests credentials from a client.
Sometimes a client will proactively include credentials such as a username/password to request a resource. In these cases, Spring Security does not need to provide an HTTP response that requests credentials from the client since they are already included.
In other cases, a client will make an unauthenticated request to a resource that they are not authorized to access. In this case, an implementation of `AuthenticationEntryPoint` is used to request credentials from the client. The `AuthenticationEntryPoint` implementation might perform a redirect to a log in page, respond with an WWW-Authenticate header, etc.
## AbstractAuthenticationProcessingFilter
`AbstractAuthenticationProcessingFilter` is used as a base `Filter` for authenticating a user’s credentials. Before the credentials can be authenticated, Spring Security typically requests the credentials using `AuthenticationEntryPoint`.
Next, the `AbstractAuthenticationProcessingFilter` can authenticate any authentication requests that are submitted to it.
image:https://i.imgur.com/XiYEJch.png[]
1. When the user submits their credentials, the `AbstractAuthenticationProcessingFilter` creates an `Authentication` from the `HttpServletRequest` to be authenticated. **The type of `Authentication` created depends on the subclass of `AbstractAuthenticationProcessingFilter`**. For example, `UsernamePasswordAuthenticationFilter` creates a `UsernamePasswordAuthenticationToken` from a username and password that are submitted in the `HttpServletRequest`.
2. Next, the `Authentication` is passed into the `AuthenticationManager` to be authenticated.
3. If authentication fails, then _Failure_
* The `SecurityContextHolder` is cleared out.
* `RememberMeServices.loginFail` is invoked. If remember me is not configured, this is a no-op.
* `AuthenticationFailureHandler` is invoked.
4. If authentication is successful, then _Success_.
* `SessionAuthenticationStrategy` is notified of a new log in.
* The `Authentication` is set on the `SecurityContextHolder`. Later the `SecurityContextPersistenceFilter` saves the `SecurityContext` to the `HttpSession`.
* `RememberMeServices.loginSuccess` is invoked. If remember me is not configured, this is a no-op.
* `ApplicationEventPublisher` publishes an `InteractiveAuthenticationSuccessEvent`.
* `AuthenticationSuccessHandler` is invoked.
TIP: In order to provide a custom authentication method you're most likely to implement a new `AuthenticationProvider`.
## Sources
. https://docs.spring.io/spring-security/site/docs/5.2.x/reference/htmlsingle/#overall-architecture[Spring Security docs - Architecture and Implementation]
. https://docs.spring.io/spring-security/site/docs/5.2.x/reference/htmlsingle/#core-services-authentication-manager[Spring Security docs - Core services]
. https://github.com/auth0/auth0-spring-security-api[Example implementation by Auth0]
@destan
Copy link
Author

destan commented May 15, 2022

Preventing redirect loop on successful login https://stackoverflow.com/a/62001941/878361

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment