Spring Security Concepts: Difference between revisions
(65 intermediate revisions by the same user not shown) | |||
Line 3: | Line 3: | ||
* [[Spring_Security#Concepts|Spring Security]] | * [[Spring_Security#Concepts|Spring Security]] | ||
= | =Spring Boot and Security= | ||
Spring Security is enabled by the following [[Spring_Boot_Concepts#Spring_Boot_Starter_Dependency|Spring Boot starter dependency]]: | |||
<syntaxhighlight lang='groovy'> | |||
dependencies { | |||
implementation('org.springframework.boot:spring-boot-starter-security') | |||
} | |||
</syntaxhighlight> | |||
Spring Boot autoconfiguration will detect that Spring Security artifacts are in the class path and for a web application, basic security will be enabled: | |||
* All HTTP request paths require authentication. | |||
* No specific [[#Role|roles]] or [[#Authority|authorities]] are required. | |||
* There is only one user, with the user name of ''user''. The password is generated and displayed in the boot logs: | |||
Using generated security password: a18ff68c-bdc1-4990-933e-6bdf896e2b72 | |||
=Security Configuration= | |||
Security components can be configured via [[Spring Security XML Configuration|XML]] or via [[Java-Based Spring Security Configuration|Java-based configuration, by extending WebSecurityConfigurerAdapter]]. | |||
=User= | =User= | ||
Line 14: | Line 33: | ||
=Authority= | =Authority= | ||
A common implementation is [https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/core/authority/SimpleGrantedAuthority.html SimpleGrantedAuthority]("ROLE_USER"). | |||
=Cryptography Support= | =Cryptography Support= | ||
=Security API= | =Security API= | ||
==Accessing Authenticated User Information== | |||
There are several ways to determine who the user is. | |||
====Inject a Principal==== | |||
Inject a <tt>Principal</tt> object into a controller method: | |||
<syntaxhighlight lang='java'> | |||
@PostMapping | |||
public String processOrder(..., Principal principal) { | |||
... | |||
String username = principal.getName(); | |||
User user = userRepository.findByUsername(username); | |||
} | |||
</syntaxhighlight> | |||
====Inject an Authentication ==== | |||
Inject an <tt>Authentication</tt> object into a controller method: | |||
<syntaxhighlight lang='java'> | |||
@PostMapping | |||
public String processOrder(..., Authentication authentication) { | |||
... | |||
User user = (User)authentication.getPrincipal(); | |||
} | |||
</syntaxhighlight> | |||
====Use the @AuthenticationPrincipal Annotation==== | |||
Use an <tt>@AuthenticationPrincipal</tt>-annotated controller method argument: | |||
<syntaxhighlight lang='java'> | |||
@PostMapping | |||
public String processOrder(..., @AuthenticationPrincipal User user) { | |||
// use the User instance | |||
} | |||
</syntaxhighlight> | |||
====SecurityContextHolder==== | |||
Use <code>SecurityContextHolder</code> to get the security context: | |||
<syntaxhighlight lang='java'> | |||
SecurityContext securityContext = SecurityContextHolder.getContext(); | |||
Authentication authentication = sc.getAuthentication(); | |||
User user = (User)authentication.getPrincipal(); | |||
</syntaxhighlight> | |||
This can be used anywhere, not only in Controller methods. | |||
=User Store= | |||
The user store can be configured overriding the <tt>configure(AuthenticationManagerBuilder)</tt> method defined in [[Java-Based Spring Security Configuration#ConfigurationClass|WebSecurityConfigurerAdapter]]. | |||
==In-Memory User Store== | |||
A user store appropriate for the situation when there is a small, static set of users, which can be defined as part of the [[#SecurityConfiguration|security configuration]]. This method is convenient for testing purposes, but if you need to add, remove or update users, the application has to be rebuilt and redeployed. | |||
{{Internal|In-Memory User Store Example|In-Memory User Store Example}} | |||
==JDBC-based User Store== | |||
A user store where user information is maintained in a relational database, in a set of tables that are not part of the application domain model. JDBC-based user store assumes a set of [[#Default_Tables|default tables]], but the table names and the queries can be also [[#Custom_Tables|customized]]. Spring also provides integration support for the situation when user information is part of the application's domain model, with a [[Spring_Security_Concepts#Custom_User_Detail_Service|Custom User Detail service]]. | |||
{{Internal|JDBC-basedUser Store Example|JDBC-basedUser Store Example}} | |||
===Default Tables=== | |||
Without additional configuration, the JDBC-based used store assumes there is a "USERS" table from which the username, password and the boolean flag indicating whether a user is enabled or not can be obtained with the following query: | |||
<syntaxhighlight lang='sql'> | |||
SELECT USERNAME, PASSWORD, ENABLED FROM USERS WHERE USERNAME = ? | |||
</syntaxhighlight> | |||
This query is used to authenticate the user. | |||
The JDBC-based user store also assumes the existence of an "AUTHORITIES" table from which the authorities of a user can be obtained with the following query, for authorization purposes: | |||
<syntaxhighlight lang='sql'> | |||
SELECT USERNAME, AUTHORITY FROM AUTHORITIES WHERE USERNAME = ? | |||
</syntaxhighlight> | |||
Finally, it assumes the existence of tables "GROUPS", "GROUP_MEMBERS" and "GROUP_AUTHORITIES" from which group information and group-associated authorities can be obtained with the following query, also for authorization purposes: | |||
<syntaxhighlight lang='sql'> | |||
SELECT G.ID, G.GROUP_NAME, GA.AUTHORITY | |||
FROM GROUPS G, GROUP_MEMBERS GM, GROUP_AUTHORITIES GA | |||
WHERE GM.USERNAME = ? AND G.ID = GA.GROUP_ID AND G.ID = GM.GROUP_ID | |||
</syntaxhighlight> | |||
The H2 DDL that creates conforming tables is available [https://github.com/ovidiuf/playground/blob/master/spring/spring-in-action/cap4-security/src/main/resources/schema-h2.sql here]. | |||
===Custom Tables=== | |||
JDBC-based User Store can be configured to use custom tables, by configuring custom "users by name", "authorities by username" and "group authorities by username" queries ([[SIA]] page 90). The basic contract is that all take the user name as the only parameter. The authentication query selects the username, password and enabled status. The authorities query selects zero or more rows containing the username and a granted authority. The group authorities query selects zero or more rows, each with a group ID, a group name and an authority. | |||
==LDAP-Backed User Store== | |||
[[SIA]] page 92. | |||
==Custom User Detail Service== | |||
[[Spring_Security_Concepts#JDBC-based_User_Store|JDBC-based user stores]] are useful when the user information is maintained in tables that are not part of the application's domain model. For the case the user information should be part of the application's domain model, the integration is done via a user-details service. | |||
{{Internal|Spring Security Custom User Detail Service|Custom User Detail Service}} | |||
==User Stores and Password Encoder== | |||
A password encoder must be specified. It seems that if we don't configure any password encoder on the user store, we get: | |||
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id ... | |||
As a quick workaround, not recommended for production, configure the store with a NoOpPasswordEncoder: | |||
<syntaxhighlight lang='java'> | |||
auth.[...]Authentication()[...].passwordEncoder(NoOpPasswordEncoder.getInstance()); | |||
</syntaxhighlight> | |||
Alternatively, prefix the password values with "{noop}" | |||
The password encoder must implement the <tt>PasswordEncoder</tt> interface. Several implementations are available: <tt>BCryptPasswordEncoder</tt>, <tt>NoOpPasswordEncoder</tt>, <tt>Pbkdf2PasswordEncoder</tt>, <tt>SCryptPasswordEncoder</tt>, <tt>StandardPasswordEncoder</tt>. Regardless of the password encoder in use, the password in the database is never decoded. Rather, the password that comes over wire is encoded and compared with the encoded form stored in the database. IF those representations match, authentication is successful. The matching is performed by <tt>PasswordEncoder.matches(CharSequence rawPassword, String encodedPassword)</tt>. | |||
=Cross-Site Request Forgery (CSRF)= | |||
{{Internal|Spring and Cross-Site Request Forgery|Spring and Cross-Site Request Forgery}} | |||
=CORS Support= | |||
{{Internal|@CrossOrigin|@CrossOrigin}} |
Latest revision as of 23:49, 29 March 2019
Internal
Spring Boot and Security
Spring Security is enabled by the following Spring Boot starter dependency:
dependencies {
implementation('org.springframework.boot:spring-boot-starter-security')
}
Spring Boot autoconfiguration will detect that Spring Security artifacts are in the class path and for a web application, basic security will be enabled:
- All HTTP request paths require authentication.
- No specific roles or authorities are required.
- There is only one user, with the user name of user. The password is generated and displayed in the boot logs:
Using generated security password: a18ff68c-bdc1-4990-933e-6bdf896e2b72
Security Configuration
Security components can be configured via XML or via Java-based configuration, by extending WebSecurityConfigurerAdapter.
User
Group
Group ID
Group Name
Authority
A common implementation is SimpleGrantedAuthority("ROLE_USER").
Cryptography Support
Security API
Accessing Authenticated User Information
There are several ways to determine who the user is.
Inject a Principal
Inject a Principal object into a controller method:
@PostMapping
public String processOrder(..., Principal principal) {
...
String username = principal.getName();
User user = userRepository.findByUsername(username);
}
Inject an Authentication
Inject an Authentication object into a controller method:
@PostMapping
public String processOrder(..., Authentication authentication) {
...
User user = (User)authentication.getPrincipal();
}
Use the @AuthenticationPrincipal Annotation
Use an @AuthenticationPrincipal-annotated controller method argument:
@PostMapping
public String processOrder(..., @AuthenticationPrincipal User user) {
// use the User instance
}
SecurityContextHolder
Use SecurityContextHolder
to get the security context:
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = sc.getAuthentication();
User user = (User)authentication.getPrincipal();
This can be used anywhere, not only in Controller methods.
User Store
The user store can be configured overriding the configure(AuthenticationManagerBuilder) method defined in WebSecurityConfigurerAdapter.
In-Memory User Store
A user store appropriate for the situation when there is a small, static set of users, which can be defined as part of the security configuration. This method is convenient for testing purposes, but if you need to add, remove or update users, the application has to be rebuilt and redeployed.
JDBC-based User Store
A user store where user information is maintained in a relational database, in a set of tables that are not part of the application domain model. JDBC-based user store assumes a set of default tables, but the table names and the queries can be also customized. Spring also provides integration support for the situation when user information is part of the application's domain model, with a Custom User Detail service.
Default Tables
Without additional configuration, the JDBC-based used store assumes there is a "USERS" table from which the username, password and the boolean flag indicating whether a user is enabled or not can be obtained with the following query:
SELECT USERNAME, PASSWORD, ENABLED FROM USERS WHERE USERNAME = ?
This query is used to authenticate the user.
The JDBC-based user store also assumes the existence of an "AUTHORITIES" table from which the authorities of a user can be obtained with the following query, for authorization purposes:
SELECT USERNAME, AUTHORITY FROM AUTHORITIES WHERE USERNAME = ?
Finally, it assumes the existence of tables "GROUPS", "GROUP_MEMBERS" and "GROUP_AUTHORITIES" from which group information and group-associated authorities can be obtained with the following query, also for authorization purposes:
SELECT G.ID, G.GROUP_NAME, GA.AUTHORITY
FROM GROUPS G, GROUP_MEMBERS GM, GROUP_AUTHORITIES GA
WHERE GM.USERNAME = ? AND G.ID = GA.GROUP_ID AND G.ID = GM.GROUP_ID
The H2 DDL that creates conforming tables is available here.
Custom Tables
JDBC-based User Store can be configured to use custom tables, by configuring custom "users by name", "authorities by username" and "group authorities by username" queries (SIA page 90). The basic contract is that all take the user name as the only parameter. The authentication query selects the username, password and enabled status. The authorities query selects zero or more rows containing the username and a granted authority. The group authorities query selects zero or more rows, each with a group ID, a group name and an authority.
LDAP-Backed User Store
SIA page 92.
Custom User Detail Service
JDBC-based user stores are useful when the user information is maintained in tables that are not part of the application's domain model. For the case the user information should be part of the application's domain model, the integration is done via a user-details service.
User Stores and Password Encoder
A password encoder must be specified. It seems that if we don't configure any password encoder on the user store, we get:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id ...
As a quick workaround, not recommended for production, configure the store with a NoOpPasswordEncoder:
auth.[...]Authentication()[...].passwordEncoder(NoOpPasswordEncoder.getInstance());
Alternatively, prefix the password values with "{noop}"
The password encoder must implement the PasswordEncoder interface. Several implementations are available: BCryptPasswordEncoder, NoOpPasswordEncoder, Pbkdf2PasswordEncoder, SCryptPasswordEncoder, StandardPasswordEncoder. Regardless of the password encoder in use, the password in the database is never decoded. Rather, the password that comes over wire is encoded and compared with the encoded form stored in the database. IF those representations match, authentication is successful. The matching is performed by PasswordEncoder.matches(CharSequence rawPassword, String encodedPassword).