The article is mainly divided into three parts
1. The architecture and core components of spring security are as follows: (1) authentication; (2) authority interception; (3) database management; (4) authority caching; (5) custom decision making; and;
2. To build and use the environment, the current popular spring boot is used to build the environment, and the actual examples in the project are used to do several cases;
3. The advantages and disadvantages of spring security, combined with the implementation of several cases in the second part, summarizes the advantages and disadvantages of spring security.
1. Introduction to spring security
Overall introduction, spring security provides comprehensive security services for enterprise application software developed based on J2EE, especially for enterprise software projects developed with spring. If you are familiar with spring, especially the dependency injection principle of spring, it will help you master spring security more quickly. At present, spring is used There are many reasons for security. Usually, solutions for typical application scenarios cannot be found in J2EE servlet specification and EJB specification. When referring to these specifications, it is particularly important to point out that they cannot be migrated at war or ear level. If you need to change the server environment, you need to do a lot of work in the new target environment to reconfigure the security of your application, Spring security solves these problems and provides you with many useful customizable security features.
Spring security consists of three main components:SecurityContext
、AuthenticationManager
、AccessDecisionManager
.
Figure 1-1 main components of spring security
1.1 certification
Spring security provides many filters, which intercept servlet requests, forward these requests to authentication processing filter and access decision filter for processing, and force security to authenticate user identity and user rights to achieve the purpose of protecting web resources. Spring security security mechanism includes two main operations,authenticationandverification, which can also be called permission control. This is spring The two main directions of security are authentication, which is the process of establishing a claimed principal for the user. This principal generally refers to the user’s equipment or other systems that can perform actions in the system. Authentication refers to whether the user can perform an operation in the application. Before the authorization judgment is reached, the identity principal has been established by the identity authentication process. Below are several common authentication modes. They are not detailed in this paper. The old fellow who needs detailed understanding can check the corresponding information by themselves.
Basic
:HTTP1.0
A challenge / response based authentication mode is proposed, which can be accessed only after user name and password authentication are provided for a specific realm, and the password is transmitted in plaintext. Disadvantages: ① stateless results in authentication information being carried in every communication, even for resources that have been authenticated; ② insufficient transmission security, authentication information is usedBase64
Coding, which is basically plaintext transmission, is easy to intercept messages and steal authentication information.Digest
:HTTP1.1
In order to solve the security problem of basic mode, it is used to replace the original basic authentication mode. Digest authentication also adopts challenge / response authentication mode, and the basic authentication process is similar. Digest mode avoids the password transmission in plaintext on the network and improves the security, but it still has some shortcomings, such as the authentication message is intercepted by the attacker, and the attacker can obtain the resources.X.509
Certification: certificate,X.509
It is a very general certificate format. The certificate includes version number, serial number (unique), signature, issuer, validity period, principal and principal public key.LDAP
: Lightweight Directory Access Protocol.Form
: form based authentication mode.
1.2 authority interception
Figure 1-2 user request
Figure 1-3 filter
Spring security provides many filters, among whichSecurityContextPersistenceFilter
、UsernamePasswordAuthenticationFilter
、FilterSecurityInterceptor
Corresponding separatelySecurityContext
、AuthenticationManager
、AccessDecisionManager
The treatment of.
Figure 1-4 flow chart of spring security filter chain
The functions of each filter are described below.
filter | describe |
---|---|
WebAsyncManagerIntegrationFilter |
set upSecurityContext To an asynchronous thread to obtain user context information |
SecurityContextPersistenceFilter |
Throughout the request processSecurityContext Create and clean up1. Not logged in, SecurityContext Null, create a newThreadLocal OfSecurityContext fillSecurityContextHolder .2. Logged in from SecurityContextRepository AcquiredSecurityContext ObjectBoth requests are cleared after completion SecurityContextHolder , and updateSecurityContextRepository |
HeaderWriterFilter |
Add header information to response object |
CsrfFilter |
Filter to prevent CSRF attack (Cross Site Request Forgery) |
LogoutFilter |
Logout processing |
UsernamePasswordAuthenticationFilter |
Get the user name and password of the form and process the login request based on the form |
DefaultLoginPageGeneratingFilter |
Configure login page |
BasicAuthenticationFilter |
Detect and process HTTP basic authentication and put the results intoSecurityContextHolder |
RequestCacheAwareFilter |
Cache for processing request requests |
SecurityContextHolderAwareRequestFilter |
Wrap request for easy accessSecurityContextHolder |
AnonymousAuthenticationFilter |
When the filter is called, anonymous information does not exist |
SessionManagementFilter |
When the user login authentication is detected, the corresponding session management is performed |
ExceptionTranslationFilter |
handleAccessDeniedException Access exceptions andAuthenticationException Authentication exception |
FilterSecurityInterceptor |
Detects whether the user has access to the resource path |
1.3 database management
Figure 1-5 core processing flow of spring security
The above figure shows the core processing flow of spring security. When a user logs in, he / she will be authenticated first. If he / she fails to pass the authentication, he / she will be required to re authenticate. When the user ID card is passed, the role manager will be called to determine whether he / she can access. Here, if you want to manage users and permissions in database, you need to customize the user login function. Spring security has provided an interfaceUserDetailsService
。
package org.springframework.security.core.userdetails;
public interface UserDetailsService {
/**
* Locates the user based on the username. In the actual implementation, the search
* may possibly be case sensitive, or case insensitive depending on how the
* implementation instance is configured. In this case, the UserDetails
* object that comes back may have a username that is of a different case than what
* was actually requested..
*
* @param username the username identifying the user whose data is required.
*
* @return a fully populated user record (never null)
*
* @throws UsernameNotFoundException if the user could not be found or the user has no
* GrantedAuthority
*/
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
UserDetailService
The interface has only one method. From the method name, we can see that the method obtains the user information through the user name, but the return result isUserDetails
Object,UserDetails
It is also an interface. If any method in the interface returns false, the user’s credentials will be considered invalid.
package org.springframework.security.core.userdetails;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import java.io.Serializable;
import java.util.Collection;
/**
* Provides core user information.
*
*
* Implementations are not used directly by Spring Security for security purposes. They
* simply store user information which is later encapsulated into {@link Authentication}
* objects. This allows non-security related user information (such as email addresses,
* telephone numbers etc) to be stored in a convenient location.
*
* Concrete implementations must take particular care to ensure the non-null contract
* detailed for each method is enforced. See
* {@link org.springframework.security.core.userdetails.User} for a reference
* implementation (which you might like to extend or use in your code).
*
* @see UserDetailsService
* @see UserCache
*
* @author Ben Alex
*/
public interface UserDetails extends Serializable {
// ~ Methods
// ========================================================================================================
/**
* Returns the authorities granted to the user. Cannot return null.
*
* @return the authorities, sorted by natural key (never null)
*/
Collection extends grantedauthority > getauthorities(); // permission collection
/**
* Returns the password used to authenticate the user.
*
* @return the password
*/
String getpassword(); // password
/**
* Returns the username used to authenticate the user. Cannot return null.
*
* @return the username (never null)
*/
String getusername(); // user name
/**
* Indicates whether the user's account has expired. An expired account cannot be
* authenticated.
*
* @return true if the user's account is valid (ie non-expired),
* false if no longer valid (ie expired)
*/
Boolean isaccountnonexpired(); // is the account expired
/**
* Indicates whether the user is locked or unlocked. A locked user cannot be
* authenticated.
*
* @return true if the user is not locked, false otherwise
*/
Boolean isaccountnonlocked(); // is the account locked
/**
* Indicates whether the user's credentials (password) has expired. Expired
* credentials prevent authentication.
*
* @return true if the user's credentials are valid (ie non-expired),
* false if no longer valid (ie expired)
*/
Boolean iscredentialsnonexpired(); // is the certificate expired
/**
* Indicates whether the user is enabled or disabled. A disabled user cannot be
* authenticated.
*
* @return true if the user is enabled, false otherwise
*/
Boolean isenabled(); // is the account valid
}
What needs to be noted here is thatAuthentication
AndUserDetails
Object differentiation,Authentication
Object is the security object used by spring security to access and control user informationAuthentication
The object has two states: UN authenticated and authenticated. When it is passed into the authentication manager as a parameter, it is an authenticated object. It obtains the user’s identity authentication information from the client, such as user name and password. It can be obtained from a login page or from a cookie, and the system automatically generates oneAuthentication
Object, and here’s theUserDetails
It represents a source of user security information. This source can be returned from the database, LDAP server, and Ca center. What spring security needs to do is not authenticateAuthentication
Objects andUserDetails
Object. After successful matching, theUserDetails
The permission information in the object is copied toAuthentication
To form a completeAuthentication
Object to share with other components.
package org.springframework.security.core;
import java.io.Serializable;
import java.security.Principal;
import java.util.Collection;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.context.SecurityContextHolder;
public interface Authentication extends Principal, Serializable {
/**Permission set*/
Collection extends GrantedAuthority> getAuthorities();
/**Get voucher*/
Object getCredentials();
/**Get some additional information about certification*/
Object getDetails();
/**Previously certified entities*/
Object getPrincipal();
/**Is it certified*/
boolean isAuthenticated();
/**
* See {@link #isAuthenticated()} for a full description.
*
* Implementations should always allow this method to be called with a
* false parameter, as this is used by various classes to specify the
* authentication token should not be trusted. If an implementation wishes to reject
* an invocation with a true parameter (which would indicate the
* authentication token is trusted - a potential security risk) the implementation
* should throw an {@link IllegalArgumentException}.
*
* @param isAuthenticated true if the token should be trusted (which may
* result in an exception) or false if the token should not be trusted
*
* @throws IllegalArgumentException if an attempt to make the authentication token
* trusted (by passing true as the argument) is rejected due to the
* implementation being immutable or implementing its own alternative approach to
* {@link #isAuthenticated()}
*/
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
After understanding the above three objects of spring security, we need to implement them manually when we need database management usersUserDetailsService
ObjectloadUserByUsername
Method, we need to prepare the following data tables at the same time, which are user table, role table, permission table, user and role relationship table_ Role), permission and role relationship table (permission)_ role),UserDetails
The user status in the user table is filled with the attributes in the user table,UserDetails
In this way, user authentication and user permission set can be managed in the database.
1.4 permission cache
Spring security’s authority caching and database management are related to user authentication, so they are all related to user authenticationUserDetails
Unlike database management, spring security provides a cacheUserDetailsService
The implementation class of. The name of this class isCachingUserDetailsService
package org.springframework.security.authentication;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.cache.NullUserCache;
import org.springframework.util.Assert;
/**
*
* @author Luke Taylor
* @since 2.0
*/
public class CachingUserDetailsService implements UserDetailsService {
private UserCache userCache = new NullUserCache();
private final UserDetailsService delegate;
public CachingUserDetailsService(UserDetailsService delegate) {
this.delegate = delegate;
}
public UserCache getUserCache() {
return userCache;
}
public void setUserCache(UserCache userCache) {
this.userCache = userCache;
}
public UserDetails loadUserByUsername(String username) {
UserDetails user = userCache.getUserFromCache(username);
//When there is no userdetails in the cache, it is loaded through the userdetailsservice
if (user == null) {
user = delegate.loadUserByUsername(username);
}
Assert.notNull(user, () -> "UserDetailsService " + delegate
+ " returned null for username " + username + ". "
+ "This is an interface contract violation");
//Store the userdetails in the cache and return the userdetails
userCache.putUserInCache(user);
return user;
}
}
CachingUserDetailsService
Class receives aUserDetails
OfUserDetailsService
Implementation class, when you need to loadUserDetails
If there is noUserDetails
If there is, then the heldUserDetailsService
The implementation class is loaded, and then the loaded results are stored in the cache,UserDetails
Interaction with the cache is done throughUserCache
Interface,CachingUserDetailsService
By default, you have oneUserCache
OfNullUserCache()
realization. The cache provided by spring security is based on memory, and it is cachedUserDetails
Object, in practical applications, more cache is generally used, such as redis, and more data such as permission related information will be cached.
2.5 user defined decision
After the user authentication is passed, spring security will call a role manager to determine whether it can continue to access. In the [spring security core processing flow (Figure 1-5)] (ා 1.3 database management)AccessDecisionManager
It is the role manager of spring security, and its corresponding abstract class isAbstractAccessDecisionManager
If you want to customize the decision manager, you usually inherit this abstract class instead of implementing the interface.
package org.springframework.security.access.vote;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.util.Assert;
/**
* Abstract implementation of {@link AccessDecisionManager}.
*
*
* Handles configuration of a bean context defined list of {@link AccessDecisionVoter}s
* and the access control behaviour if all voters abstain from voting (defaults to deny
* access).
*/
public abstract class AbstractAccessDecisionManager implements AccessDecisionManager,
InitializingBean, MessageSourceAware {
protected final Log logger = LogFactory.getLog(getClass());
private List> decisionVoters;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private boolean allowIfAllAbstainDecisions = false;
protected AbstractAccessDecisionManager(
List> decisionVoters) {
Assert.notEmpty(decisionVoters, "A list of AccessDecisionVoters is required");
this.decisionVoters = decisionVoters;
}
public void afterPropertiesSet() {
Assert.notEmpty(this.decisionVoters, "A list of AccessDecisionVoters is required");
Assert.notNull(this.messages, "A message source must be set");
}
protected final void checkAllowIfAllAbstainDecisions() {
if (!this.isAllowIfAllAbstainDecisions()) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
}
public List> getDecisionVoters() {
return this.decisionVoters;
}
public boolean isAllowIfAllAbstainDecisions() {
return allowIfAllAbstainDecisions;
}
public void setAllowIfAllAbstainDecisions(boolean allowIfAllAbstainDecisions) {
this.allowIfAllAbstainDecisions = allowIfAllAbstainDecisions;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
public boolean supports(ConfigAttribute attribute) {
for (AccessDecisionVoter voter : this.decisionVoters) {
if (voter.supports(attribute)) {
return true;
}
}
return false;
}
/**
* Iterates through all AccessDecisionVoters and ensures each can support
* the presented class.
*
* If one or more voters cannot support the presented class, false is
* returned.
*
* @param clazz the type of secured object being presented
* @return true if this type is supported
*/
public boolean supports(Class> clazz) {
for (AccessDecisionVoter voter : this.decisionVoters) {
if (!voter.supports(clazz)) {
return false;
}
}
return true;
}
}
The core method issupports
Method, one of the methods is useddecisionVoters
The type in the collection isAccessDecisionVoter
This is a voter introduced by spring security. The final decision on whether you have access or not is determined by the voter.
package org.springframework.security.access;
import java.util.Collection;
import org.springframework.security.core.Authentication;
public interface AccessDecisionVoter {
int ACCESS_GRANTED = 1;
int ACCESS_ABSTAIN = 0;
int ACCESS_DENIED = -1;
boolean supports(ConfigAttribute attribute);
boolean supports(Class> clazz);
int vote(Authentication authentication, S object,
Collection attributes);
}
There are many voting machines here, the most common one isRoleVoter
Voter,RoleVoter
The prefix “role” is defined_ “The core of a voter is to rely onvote
This election method implements the parameters in the methodauthentication
It is the user and permission information,attributes
It is the permission required to access the resource. The code circulates to determine whether the user has the permission required to access the resource. If so, it will be returnedACCESS_GRANTED
, that is, they have permission.
package org.springframework.security.access.vote;
import java.util.Collection;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
public class RoleVoter implements AccessDecisionVoter {
private String rolePrefix = "ROLE_";
public String getRolePrefix() {
return rolePrefix;
}
public void setRolePrefix(String rolePrefix) {
this.rolePrefix = rolePrefix;
}
public boolean supports(ConfigAttribute attribute) {
if ((attribute.getAttribute() != null)
&& attribute.getAttribute().startsWith(getRolePrefix())) {
return true;
}
else {
return false;
}
}
public boolean supports(Class> clazz) {
return true;
}
/**
*Authentication is user and permission information
*Attributes are the permissions required to access a resource
*/
public int vote(Authentication authentication, Object object,
Collection attributes) {
if (authentication == null) {
return ACCESS_DENIED;
}
int result = ACCESS_ABSTAIN;
Collection extends GrantedAuthority> authorities = extractAuthorities(authentication);
for (ConfigAttribute attribute : attributes) {
if (this.supports(attribute)) {
result = ACCESS_DENIED;
// Attempt to find a matching granted authority
for (GrantedAuthority authority : authorities) {
if (attribute.getAttribute().equals(authority.getAuthority())) {
return ACCESS_GRANTED;
}
}
}
}
return result;
}
Collection extends GrantedAuthority> extractAuthorities(
Authentication authentication) {
return authentication.getAuthorities();
}
}
Spring seucrity provides three voting decisions, which areAffirmativeBased
: you can visit by one vote;ConsensusBased
Access is allowed only when more than half of them pass;UnanimousBased
: access is allowed only when all pass. Custom decisions only need inheritanceAbstractAccessDecisionManager
Abstract class, you can customize your own voter, for example, you need to meet multiple conditions to access at the same time, and so on. You don’t need to use the voter provided by spring security.
2. Environment construction and use
2.1 quickly build spring boot + spring security environment
Open the spring boot website https://start.spring.io/ , select Java language, add spring web and spring security in dependencies, and finally click generate to download.
Unzip the downloaded file and open it with idea. You can see that this is a demo that can be started directly. Because we are a web project, we add an interface here to have a look.
@SpringBootApplication
@RestController
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@RequestMapping("/")
public String home() {
return "hello spring boot";
}
}
When we start, we type in the address bar locahost:8080 It will automatically jump to the / login path, indicating that spring security has been directly involved.
Then we create an inheritanceWebSecurityConfigurerAdapter
At the same time, we add an interface with the path of “/ Hello”. According to the code annotation, we can see that the access to the main path of the project does not need to be verified, while the access to other paths needs to be verified. Start project, visit localhost:8080 You can go directly through, but access localhost:8080 \Hello will automatically jump to localhost:8080/login Path requires login. This shows that the security policy of spring security has taken effect, and the environment construction of spring boot and spring security has been completed.
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
/**
*Interception strategy
*Define which paths need to be intercepted and which do not
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
. antmatchers ("/"). Permitall() // the main path of the project can be released
. anyrequest(). Authenticated() // all other requests need to be verified
. and(). Logout(). Permitall() // allow logout to access
. and(). Formlogin(); // allow form login
http.csrf (). Disable(); // turn off CSRF authentication
}
@Override
public void configure(WebSecurity web) throws Exception {
/**
*Ignore static resource interception
*/
web.ignoring().antMatchers("/js/**", "/css/**");
}
}
2.2 common case implementation
2.2.1 as long as you can log in
As long as you log in, you can access all the resource paths of the project, and you don’t need to write a separate login page. Here, you will use the memory based authentication provided by spring security. staySpringSecurityConfig
Classconfigure(AuthenticationManagerBuilder auth)
This method. After spring security 5.0, a variety of encryption methods have been added and the default password format has been changed. The new password storage format is “{ID}…” . the ID in the front is the encryption method. The ID can be bcrypt, sha256, etc., followed by the encrypted password. That is to say, when the program gets the password, it will first look for the ID included by “{” and “}” to determine how the password is encrypted. If it can not be found, the ID will be considered as null. At this time, the program will report an error: there is no passwordencoder mapped for the ID “null”. In practical application, you can also customize the encryption method, only need to inheritPasswordEncoder
Interface.
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//Create a user with the user name of admin, password of 123456 and role of admin
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin")
.password(new BCryptPasswordEncoder().encode("123456"))
.roles("ADMIN");
//Multiple users can be specified
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("zhangsan")
.password(new BCryptPasswordEncoder().encode("123456"))
.roles("DEMO");
}
}
2.2.2 there are specified roles, and each role has specified permissions
To add a qualified role request, only those with the admin role can access it byRoleVoter
The prefix defined in the previous custom decision. At the same time, it is also important to note that the@PreAuthorize
This annotation must be added to the class@EnableGlobalMethodSecurity(prePostEnabled = true)
annotation@PreAuthorize
Will take effect. In this way, admin users can access / roleauth, but Zhangsan can’t access / roleauth.
@SpringBootApplication
@RestController
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class DemoApplication {
/**Middle code omitted**/
@PreAuthorize("hasRole('ROLE_ADMIN')")
@RequestMapping("/roleAuth")
public String role() {
return "admin auth";
}
}
In the actual scenario, user roles are generally stored in the database. As mentioned above, the database management of spring security needs to be implementedUserDetailsService
Interface, define database related query, returnUserDetails
Object.
package com.mall.demo;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
@Component
public class MyUserService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
return null;
}
}
@Autowired
private MyUserService myUserService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserService);
/**
*Default database validation provided by spring security
*/
auth.jdbcAuthentication()
. usersbyusernamequery (") // query users
. authoritiesbyusernamequery ("); // query permissions
}
Before we go back to security, we can use the spring database configuration@PreAuthorize
This annotation controls whether a method can be called. In fact, spring security provides four such annotations, namely@PreAuthorize
、@PostAuthorize
、@PreFilter
、@PostFilter
,@PreAuthorize
and@PostAuthorize
Is used to check the permissions before and after a method call,@PreFilter
and@PostFilter
Is used to filter the parameters or return values of the collection class.
//The ID parameter passed in is less than 10
//Incoming username = current user name
//The user name of the user object passed in = Zhangsan
@PreAuthorize("#id<10 and principal.username.equals(#username) and #user.username.equals('zhangsan')")
//Verify that the returned result is even
@PostAuthorize("returnObject%2==0")
@RequestMapping("/test1")
public Integer test1(Integer id, String username, User user) {
return id;
}
//Filter incoming parameters, leaving even numbers
@PreFilter("filterObject%2==0")
//Filter returns the number that is divisible by 4
@PostFilter("filterObject%4==0")
@RequestMapping("/test2")
public List test2(List idList) {
return idList;
}