table of Contents
- One, Spring Security principle
- Two, Spring Security certification process (source code tracking)
- Three, Spring Security authorization process (source code tracking)
One, Spring Security principle
-
It is based on
过滤器链
intercepting the request sent by the user; -
The problem solved by Spring Security is
安全访问控制
that the security access control function is actuallyIntercept all requests entering the system and verify whether each request can access the resources it expects. According to the previous knowledge, it can be achieved throughFilter
orAOP
other technologies. Spring Security's protection of web resources is achieved by Filter , so start with this Filter and gradually deepen the principles of Spring Security. -
When initializing Spring Security, it creates a named
SpringSecurityFilterChain
Servlet filter typeorg.springframework.security.web.FilterChainProxy
, it implementsjavax.servlet.Filter
, therefore such external requests through the lower figure is misplaced Spring Security chain structure of FIG.
上图:
UsernamePasswordAuthenticationFilter
—> corresponds to the actual workAuthenenticationManager
FilterSecurityInterceptor
—> corresponds to the actual workAccessDecisionManager
FilterChainProxy is a proxy. What really works is the filters contained in SecurityFilterChain in FilterChainProxy. At the same time, these Filters are managed by Spring as Beans. They are the core of Spring Security and each have their own responsibilities, but they do not directly handle user authentication. It does not directly deal with user authorization, but hands them over 认证管理器(AuthenticationManager
and 决策(授权)管理器 (AccessDecisionManager)
processes them. The following figure is a UML diagram of FilterChainProxy related classes.
1. Spring Security certification process (source code tracking)
Jump to the directory
According to the above sequence diagram, find the two classes in the program, UsernamePasswordAuthenticationFilter
and DaoAuthenticationProvider
analyze the above 时序图
process through breakpoints ;
1, first into the login page, enter the correct account password zhangsan, 123
2, because after enter the account password, will enter into AbstractAuthenticationProcessingFilter
the doFilter
method,
3. The authenticator (AuthenticationManager) is mainly entrusted DaoAuthenticationProvider
to authenticate the user's account and password; find the retrieveUser
method
of UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
this type. In this step, the userDetailsService
method we wrote will be called to retrieve the user's account and password from the database;
4. The account password in the database, proceed 匹配
, will enter DaoAuthenticationProvider
the parent class to AbstractUserDetailsAuthenticationProvider
perform the judgment operation.
Enter the additionalAuthenticationChecks
method to match the account password.
At this time, the login is successful;
The general relationship of certification core components is as follows:
1、AuthenticationProvider
2、UserDetailsService
- Meet UserDetailsService
Now we now know that DaoAuthenticationProvider handles the authentication logic of the web form. After successful authentication, an Authentication (implemented by UsernamePasswordAuthenticationToken) is obtained, which contains the identity information (Principal). This identity information is an Object, and in most cases it can be forced into an UserDetails
object.
DaoAuthenticationProvider contains an UserDetailsService
instance, which is responsible for extracting user information UserDetails (including password) based on the user name, and then DaoAuthenticationProvider will compare whether the user password extracted by UserDetailsService matches the password submitted by the user as the key basis for successful authentication, so it can be passed from The defined UserDetailsService is exposed as a spring bean to define custom authentication.
- Many people confuse the responsibilities of
DaoAuthenticationProvider
andUserDetailsService
, in fact, UserDetailsService is only responsible from a specific place (usually a database)加载用户信息
, nothing more. The DaoAuthenticationProvider has a greater responsibility, it completes the complete,认证流程
and at the same time fills the UserDetails to Authentication.
-
It is very similar to the Authentication interface, for example, they all have username and authorities. Authentication's getCredentials() and UserDetails' getPassword() need to be treated differently. The former is the password credential submitted by the user, and the latter is the password actually stored by the user. Authentication is actually a comparison of the two. The getAuthorities() in Authentication is actually formed by the getAuthorities() of UserDetails. Remember the getDetails() method in the Authentication interface? The UserDetails user details are populated after authentication by the AuthenticationProvider.
-
By implementing UserDetailsService and UserDetails, we can complete the expansion of user information acquisition methods and user information fields.
-
InMemoryUserDetailsManager (memory authentication) provided by Spring Security, JdbcUserDetailsManager (jdbc authentication) is the implementation class of UserDetailsService, the main difference is nothing more than loading users from
内存
or from数据库
.
Test:
Custom UserDetailsService
@Service
public class MyUserDetailService implements UserDetailsService {
// 根据账号查询信息
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 将来连接数据库根据账号来查询用户信息
// 现在先模拟
System.out.println("username = " + username);
UserDetails userDetails = User.withUsername("zhangsan1").password("123").authorities("p1").build();
return userDetails;
}
}
Shield the definition of UserDetailsService in the security configuration class
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// 配置用户信息服务
// @Bean
// public UserDetailsService userDetailsService() {
// InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
// manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
// return manager;
// }
Restart the project and request authentication. The loadUserByUsername method of MyUserDetailsService is called to query user information.
3、PasswordEncoder
Jump to the directory to
know PasswordEncoder
After the DaoAuthenticationProvider authentication processor obtains UserDetails through UserDetailsService, how does it compare with the password in the authentication request?
Here, in order to adapt to a variety of encryption types, Spring Security has made abstractions. DaoAuthenticationProvider matches
compares passwords through the PasswordEncoder interface method, and the specific password comparison details depend on the implementation:
And Spring Security provides many built-in PasswordEncoders, which can be used out of the box. To use a certain PasswordEncoder, you only need to make the following declaration, as follows
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
NoOpPasswordEncoder adopts string matching method and does not encrypt and compare passwords. The password comparison process is as follows:
1. The user enters the password (in plain text)
2. DaoAuthenticationProvider obtains UserDetails (which stores the correct password of the user)
3. DaoAuthenticationProvider uses PasswordEncoder to verify the entered password and the correct password. If the password is consistent, the verification passes, otherwise the verification fails. .
-
The verification rule of NoOpPasswordEncoder compares the entered password with the correct password in UserDetails. If the
字符串
string content is consistent, the verification passes, otherwise the verification fails. -
It is recommended to use in actual projects
BCryptPasswordEncoder
, Pbkdf2PasswordEncoder, SCryptPasswordEncoder, etc. If you are interested, you can take a look at the specific implementation of these PasswordEncoders
Use BCryptPasswordEncoder
2 to test BCrypt
@SpringBootTest
public class TestBCrypt {
@Test
public void testBCrypt() {
// 对密码进行加密
String hashpw = BCrypt.hashpw("123", BCrypt.gensalt());
String hashpw2 = BCrypt.hashpw("456", BCrypt.gensalt());
System.out.println("hashpw = " + hashpw);
System.out.println("hashpw2 = " + hashpw2);
// 校验密码
boolean checkpw1 = BCrypt.checkpw("123", "$2a$10$QfQYXOtc/2oSgiuYi.9x6.8VcFZ4RuQOq7WmzwkkhXoiD.hB5swP.");
boolean checkpw2 = BCrypt.checkpw("123", "$2a$10$ptyf4yyfbc1oL.OJPfSKMOk.hO4eRS1SQj44MBhhHnSZFrphjGHK.");
System.out.println("checkpw1 = " + checkpw1);
System.out.println("checkpw2 = " + checkpw2);
}
}
3. Modify the security configuration class
/**
* Description: 安全配置的内容包括:用户信息、密码编码器、安全拦截机制。
*
* @author zygui
* @date Created on 2020/7/22 15:11
*/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// 配置用户信息服务
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("zhangsan").password("$2a$10$QfQYXOtc/2oSgiuYi.9x6.8VcFZ4RuQOq7WmzwkkhXoiD.hB5swP.").authorities("p1").build());
manager.createUser(User.withUsername("lisi").password("$2a$10$LYa/9GkXYzhc/UjD7S/D5OWE2F7RXHVgANsDHC4XSp8OiEfi1Fk4e").authorities("p2").build());
return manager;
}
// 对密码进行编码, 使用不加密的对比
// @Bean
// public PasswordEncoder passwordEncoder() {
// return NoOpPasswordEncoder.getInstance();
// }
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 配置安全拦截机制
//安全拦截机制(最重要)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/r/r1").hasAuthority("p1")
.antMatchers("/r/r2").hasAuthority("p2")
.antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
.anyRequest().permitAll()//除了/r/**,其它的请求可以访问
.and()
.formLogin()//允许表单登录
.successForwardUrl("/login-success");//自定义登录成功的页面地址
}
}
Three, Spring Security authorization process (source code tracking)
Go to the directory
through the quick start we know, Spring Security through http.authorizeRequests()
to the web request to authorize protection. Spring Security uses the standard Filter to establish the interception of web requests, and finally realizes authorized access to resources.
The authorization process of Spring Security is as follows:
In the Decide()
method in AccessDecisiontManager 拿到当前访问资源所需要的权限信息
and 用户信息中的权限信息
compared, if it meets, the authorization is successful;
The core interface of AccessDecisionManager (Access Decision Manager) is as follows:
public interface AccessDecisionManager {
void decide(Authentication var1, Object var2, Collection<ConfigAttribute> var3) throws AccessDeniedException, InsufficientAuthenticationException;
boolean supports(ConfigAttribute var1);
boolean supports(Class<?> var1);
}
Here we will focus on the parameters of decide:
- authentication: the identity of the visitor who wants to access the resource
- object: the protected resource to be accessed, the web request corresponds to FilterInvocation
- configAttributes: is the access policy of protected resources, obtained through SecurityMetadataSource.
The decide interface is used to identify whether the current user has the permission to access the corresponding protected resource.
1. Authorization decision
Jump to the directory
AccessDecisionManager uses 投票
the method to determine whether the protected resource can be accessed.
注意:
The default is to use AffirmativeBased way
Enter this class and break the point under the decide method