As long as the system dealing with users needs to be privilege management, otherwise what should we do when the operation is improper for you to delete the library? Open source permission management framework includes Spring Security, Shiro, RBAC, ACL and so on. Whether to choose an open source framework or to build a wheel based on permission management model is good, we must investigate and choose a suitable way to realize the company's business. First, we should study and learn a wave of Spring Security.
Through this article, you will learn about it.
- Some core classes in Spring Security
- Role-based privilege checking using Spring Security
- Deficiencies of Spring Security
Spring Security Core Class
Because Spring Security is just a research study, the source code is not introduced here. Privilege management is authorization and authentication. If you want to authenticate, you must first login to get user information. However, you won't talk about login, login and distributed session management. You will only introduce the core classes in the authentication process, and understand that the core classes can quickly integrate the Spring Security framework.
Landing check
- UsernamePassword Authentication Filter => User login validation, but this class does not really do login validation, but through Provider Manager
- ProviderManager => This class has a List < Authentication Provider >, which provides different checking methods, as long as one of them passes. Usually when we don't match anything, we use the username and password, and then the Authentication Provider implementation class is AbstractUser Details Authentication Provider.
- AbstractUserDetails Authentication Provider => Its login check is done by the additionalAuthentication Checks method of the subclass
- DaoAuthentication Provider => AbstractUserDetails Authentication Provider's unique subclass, if we set the password (which can be memory-based or database-based) and the password passed in is not as good as the login verification failure
- UserDetails Service => UserByUsername returns user information such as username and password (encapsulated as the implementation class of UserDetails) that we have set through this interface's unique method load UserByUsername.
Summary: Login verification is to get the client's username password and the user name password we set (that is, the implementation class of UserDetails) for comparison, get the user name password we set through UserDetails Service. Load UserByUsername (String userName) to achieve [emphasis, a moment to test], framework implementation of UserDet The ailsService can not meet the project requirements generally, so we need to implement it manually. At the same time, if the implementation class of UserDetails that comes with the framework can not meet the requirements, we can also implement UserDetails ourselves.
Permission check
- FilterSecurity Interceptor => Role-based Privilege Check Interceptor, calling the beforeInvocation method of the parent AbstractSecurity Interceptor for authentication
- AbstractSecurity Interceptor => Calls the Decision method of the AccessDecision Manager implementation class to authenticate, so how to customize the authentication method can write a class and then implement AccessDecision Manager
- AffirmativeBased=> The default AccessDecision Manager implementation class is used. Calling the vote method of the AccessDecision Voter implementation class to authenticate, returning 1 privilege check to pass. In fact, it is better to trace to the end than to vote on the string. The method name is easy to be misunderstood.
- Web ExpressionVoter => AccessDecisionVoter implementation class used by default, calling Authentication method for authentication
- SecurityExpressionOperations => Get Authentication Object Interface
- SecurityExpressionRoot => SecurityExpressionOperations implementation class
Summary: Normally, we don't need to manage these authentication methods. Although many layers are invoked layer by layer, the essence is to determine whether the list of permissions contained by the current user contains the permissions needed to access the specified url.
SpringBoot integrates Spring Security
rely on
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>1.4.1.RELEASE</version> </dependency>
UserDetails implementation class: UserDTO.java
public class UserDTO implements UserDetails { /** * User name * */ private String username; /** * Password * */ private String password; /** * Role list * */ private List<String> roleList; public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public List<String> getRoleList() { return roleList; } public void setRoleList(List<String> roleList) { this.roleList = roleList; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { List<GrantedAuthority> authorityList = new ArrayList<>(); for (String role : roleList) { authorityList.add(new SimpleGrantedAuthority(role)); } return authorityList; } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
Implementing classes need to implement interfaces. Here we encapsulate the roleList string queried into SimpleGranted Authority, which is an implementation class of Granted Authority. If the default implementation fails to meet the requirements, it can be re-implemented by itself. UserDetail is a bridge between Spring Security and applications. No matter how your database is built, Spring Security can verify its privileges according to its own mechanism as long as you finally encapsulate the relationship between user information and privileges as UserDetails.
UserDetailsService implementation class: UserDetailsServiceImpl.java
public class UserDetailsServiceImpl implements UserDetailsService { @Resource private UsersService usersService; /** * Obtain user information based on user name */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Users users = new Users(); users.setUsername(username); List<Users> usersList = usersService.selectList(users); return buildUserDTO(usersList); } /** * Encapsulating UserDTO objects * * @param usersList * @return * */ private UserDTO buildUserDTO(List<Users> usersList) { UserDTO userDTO = new UserDTO(); userDTO.setUsername(usersList.get(0).getUsername()); userDTO.setPassword(usersList.get(0).getPassword()); List<String> roleList = new ArrayList<>(); for (Users users : usersList) { roleList.add(String.format("ROLE_%s", users.getRole())); } userDTO.setRoleList(roleList); return userDTO; } }
The purpose of this class is to encapsulate user information and privilege information from the database to a UserDetails return.
Permission configuration class: WebSecurity Config. Java
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) // Open method-level security verification public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsServiceImpl userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() //Any request can be accessed after login .and().formLogin().permitAll(); //Arbitrary access by login page users // Turn off CSRF cross-domain http.csrf().disable(); } @Override public void configure(WebSecurity web) throws Exception { // Setting up interception ignore folder allows static resources to be released web.ignoring().antMatchers("/css/**", "/js/**", "/templates/**"); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //1. Setting up custom userDetailService //2. Specify the cipher decoding method during verification auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder()); } }
This class is equipped with login pages and some simple permission settings, such as any request for login can be accessed, login page can be accessed by everyone. Because method level validation is enabled through the @EnableGlobalMethodSecurity annotation, there is no permission to configure method level in this method. At the same time, by specifying the User Details Service and the password decoding method, if you do not specify the password decoding method, you will be wrong in the latest version of Spring Security.
Control Layer Annotation Usage
@RestController @RequestMapping("/api/user") public class UsersController { @GetMapping("/guest") @PreAuthorize("hasAnyRole('guest')") public Object guest() { return "hello guest"; } @PreAuthorize("hasAnyRole('admin')") @GetMapping("/admin") public Object admin() { return "hello admin"; } }
Privilege control is done through @PreAuthorize, and authorization (roles) to access the api are written in hasAnyRole. In addition to the @PreAuthorize annotation, you can also use @Secured,@PostAuthorize annotations.
Deficiencies of Spring Security Framework
- Intrusion into project code
- Not universal enough, all systems requiring permission validation need to integrate Spring Security framework. Different application systems have different database designs. UserDetail usually needs to be implemented by itself. [Just for example, there may be more classes rewritten in the actual development process]
- Roles should be dynamic, but the roles configured through Spring Security are static, and the code must be modified when new roles are added to the database, otherwise they cannot be used.
- It is not a design model of RBAC, but an ACL model. The partition of roles and permissions is not very clear. Permissions can also be roles. If you want to check permissions based on RBAC, you must rewrite the method of permission checking by yourself.
- Privilege management granularity is not fine enough, for example, can not support to the method level of privilege checking, to support more fine-grained privileges must write their own privilege checking.
- The three ways of caching user information are NullUserCache, EhCacheBasedUserCache and Spring CacheBasedUserCache. The first one is always return null, which is equivalent to not using cache, and the latter two are memory cache. In distributed deployment, there will be low cache hit rate and inconsistent cache, so we need to implement cache by ourselves.
Just some of my own opinions. If there are any mistakes, please correct them.
Last: Project source code