Shiro execution logic
- Application Code: written application
- Subject: subject, which represents the current "user". This user is not necessarily a specific person. Anything interacting with the current application is a subject, such as web crawler, robot, etc; An abstract concept; All subjects are bound to SecurityManager, and all interactions with subjects will be delegated to SecurityManager; You can think of subject as a facade; The SecurityManager is the actual executor;
- SecurityManager: Security Manager; That is, all security related operations will interact with the SecurityManager; And it manages all subjects; It can be seen that it is the core of Shiro. It is responsible for interacting with other components introduced later. If you have studied spring MVC, you can regard it as the front-end controller of dispatcher servlet;
- Realm: in realm, Shiro obtains security data (such as user, role and permission) from realm, which means that if SecurityManager wants to verify user identity, it needs to obtain corresponding users from realm for comparison to determine whether the user identity is legal; You also need to get the corresponding role / authority of the user from realm to verify whether the user can operate; You can think of realm as a DataSource, that is, a secure data source.
The simplest Shiro application
- The application code is authenticated and authorized through the Subject, which is entrusted to the SecurityManager;
- We need to inject real into Shiro's SecurityManager so that the SecurityManager can obtain legal users and their permissions for judgment.
Shiro's organizational structure
As can be seen from the figure, Security Manager is the most complex system
Security Manager internal
- Authenticator: authenticator, which is responsible for principal authentication. This is an extension point. If users think Shiro's default is not good, they can customize the implementation; It needs Authentication Strategy, that is, when the user authentication is passed;
- Authorizer: authorizer or access controller, which is used to determine whether the subject has permission to perform corresponding operations; That is, it controls which functions users can access in the application;
- SessionManager: if you have written servlets, you should know the concept of session. Session needs someone to manage its life cycle. This component is SessionManager; Shiro can be used not only in the Web environment, but also in ordinary JavaSE environment, EJB and other environments; Therefore, Shiro abstracts a session of its own to manage the data interaction between the subject and the application; In this case, for example, when we used it in the Web environment, it was a Web server at the beginning; Then I went to an EJB server; At this time, if you want to put the session data of the two servers in one place, you can realize your own distributed session (such as putting the data on the Memcached server);
- SessionDAO: DAO has been used by everyone. Data access objects and CRUD for sessions. For example, if we want to save sessions to the database, we can implement our own SessionDAO and write to the database through JDBC; For example, if you want to put a Session into Memcached, you can implement your own Memcached SessionDAO; In addition, in SessionDAO, Cache can be used for caching to improve performance;
- CacheManager: cache controller to manage the cache of users, roles, permissions, etc; Because these data are rarely changed, putting them in the cache can improve the performance of access
- Cryptography: password module. Shiro provides some common encryption components, such as password encryption / decryption.
Implementation of domain Realm
There can be one or more realms, which can be considered as the data source of security entity, that is, the data source used to obtain security entity; It can be implemented by JDBC, LDAP or memory; Provided by the user; Note: Shiro doesn't know where your users / permissions are stored and in what format; Therefore, we generally need to implement our own Realm in applications;
Shiro authentication
Authentication, that is, who can prove that he is himself in the application. Generally, some identification information such as their ID is provided to indicate that he is himself, such as ID card and user name / password.
For authentication in shiro, you need to provide principals and credentials
Identity tells shiro what user you are, and the certificate is used to prove your identity.
The most common principals and credentials are user names and passwords
principals = user name / email / mobile number, etc
Credentials = password / verification code / digital credentials, etc
Next, use the springboot environment to demonstrate how Shiro authenticates
demonstration
Prepare environment dependencies
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.2</version> </dependency>
Prepare user identity
Shiro The INI configuration file specifies two subject s, one with a user name of wang and a password of 123, and the other with a password of 123
[users] wang = 123 zhang = 123
Test code
import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class DemoApplicationTests { @Test public void MainTest(){ // Initialize the SecurityManager factory, where the ini file is used as the realm IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini"); // Use the factory to generate a SecurityManager instance SecurityManager securityManager = factory.getInstance(); // Bind the sm instance to the tool class provided by shiro. The following operations will take advantage of this tool class SecurityUtils.setSecurityManager(securityManager); // Obtain the principal, where the principal is two users in the ini file Subject subject = SecurityUtils.getSubject(); // Use the class provided by shiro to create a virtual user to log in UsernamePasswordToken token = new UsernamePasswordToken("wang", "123"); try { // Authenticate identity subject.login(token); } catch (AuthenticationException e) { System.out.println("Wrong user name and password,Error message:"+e.getMessage()); } // Judge whether the user has passed the authentication. Return true if it has passed the authentication and false if it has not boolean authenticated = subject.isAuthenticated(); if(authenticated) System.out.println("Login successful"); else System.out.println("Login failed"); // Log out subject.logout(); } }
Explanation of line 29 error message
If authentication fails, please capture AuthenticationException or its subclasses, such as DisabledAccountException (disabled account), LockedAccountException (locked account), UnknownAccountException (wrong account), ExcessiveAttemptsException (too many login failures), IncorrectCredentialsException (wrong credentials) ExpiredCredentialsException (expired voucher), etc. Please check its inheritance relationship for details; For the error message display of the page, it is best to use "user name / password error" instead of "user name error" / "password error" to prevent some malicious users from illegally scanning the account library;
The steps of authentication can be summarized from the above code:
- Collect user identity / credentials, such as user name / password;
- Call subject Login. If it fails, you will get the corresponding AuthenticationException exception, and the user will be prompted with error information according to the exception; Otherwise, login succeeds;
- Finally, call Subject.. Logout to exit.
Several problems in the above test:
- The user name / password is hard coded in the ini configuration file, which needs to be changed into database storage in the future, and the password needs to be encrypted;
- The user identity Token may not only be the user name / password, but also others. For example, the user name / email / mobile phone number is allowed to log in at the same time.
It can be seen from the code that these operations seem to be completed by the subject, but in fact, all of them are entrusted by the subject to the securitymanager, and the securitymanager will entrust its own sub module Authenticator to complete the authentication operation. The Authenticator will give the obtained token (user information and credentials) to realm. If realm does not throw an exception, Then the authentication is successful.
It can be seen that the Realm is the final station for authentication. Therefore, we can configure multiple realms to verify the token handed in by the Authenticator for many times
Realm configuration
Realm is a secure data source. We just used the ini file as the data source in the demonstration, so the realm we used is in shiro
org.apache.shiro.realm.text.IniRealm
shiro defines the Realm interface, which is the parent interface of all realms. The code is as follows
String getName(); //Returns a unique Realm name boolean supports(AuthenticationToken token); //Judge whether this Realm supports this Token AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException; //Obtain authentication information according to Token
We can customize the Realm, and then specify the Realm implementation of securityManager through the ini configuration file, so as to realize multi Realm verification
Create a Shiro multi Realm INI file. Suppose we have customized two realms
Then you can configure multiple realms through the following configuration file
\#Declare a realm myRealm1 = com.whr.realm.MyRealm1 myRealm2 = com.whr.realm.MyRealm2 \#The $symbol refers to the object defined above \#Specify the realms implementation of securityManager. This line can be deleted. After deletion, it will be loaded in the order declared above securityManager.realms=$myRealm1,$myRealm2
The securityManager authenticates in the order specified by realms. Here, we specify the order of realms by displaying the specified order. If "securityManager.realms=$myRealm1,$myRealm2" is deleted, the securityManager will be used in the order declared by realms (that is, it will be found automatically without setting the realms attribute). When we display the specified realms, other unspecified realms will be ignored, If "securityManager.realms=$myRealm1", myRealm2 will not be automatically set.
Real provided by Shiro
Generally, you can inherit the authorizing realm in the future; It inherits AuthenticatingRealm (i.e. authentication) and indirectly cacheingrealm (with cache implementation).
Shiro authorization
Why authorization is required? Because in the page, we don't want people without permission to see some resources. For example, in a background management system login window page, we will have several roles, such as administrator, user, etc. then the pages that administrators and users can see and the operations they perform must be different. At this time, we need to authorize different roles.
Several key objects to understand in authorization:
-
The Subject is the user accessing the application. In Shiro, the Subject is used to represent the user. Users are only allowed to access the corresponding resources after authorization.
-
Resource s are the URL s that users can access in the application, such as accessing JSP pages, viewing / editing some data, accessing a business method, printing text, etc. Users can only access after authorization.
-
Permission is the atomic authorization unit in the security policy. Through permission, we can indicate whether the user has the right to operate a resource in the application. That is, permission indicates whether a user can access a resource in the application, such as accessing the user list page to view / add / modify / delete user data (i.e. CRUD (add query, modify and delete) permission control) and printing documents. As can be seen from the above, permission represents whether the user has the right to operate a resource, that is, whether the operation reflected on a resource is allowed or not, and who will perform the operation. Therefore, it is necessary to give permissions to users later, that is, to define which user is allowed to do what operations (permissions) on a resource. Shiro will not do this, but will be provided by the implementer.
-
Role. Roles represent a set of operations and can be understood as a set of permissions. Generally, we will give users roles rather than permissions, that is, users can have a set of permissions, which is more convenient when granting permissions. Typical roles include project manager, technical director, CTO and development engineer. Different roles have different permissions.
Authorization method
Shiro supports authorization in three ways:
Programming: complete by writing if/else authorization code block:
Subject subject = SecurityUtils.getSubject(); if(subject.hasRole("admin")) { //Have authority } else { //No permission }
Annotation: This is accomplished by placing corresponding annotations on the executed Java method:
@RequiresRoles("admin") public void hello() { //Have authority }
If there is no permission, the corresponding exception will be thrown;
JSP/GSP tag: it is completed through the corresponding tag on the JSP/GSP page:
<shiro:hasRole name="admin"> <!— Have authority to -> </shiro:hasRole>
demonstration
Role based presentation control
[users] wang = 123 ,admin,user zhang = 123 ,user
Two roles are set for wang user, admin and user. zhang user only has user role
import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class DemoApplicationTests { /* * Use static code blocks to complete the registration of securityManager, but in most cases, you don't need to do so. This is just a test * */ static { SecurityManager securityManager = new IniSecurityManagerFactory("classpath:shiro.ini"). getInstance(); SecurityUtils.setSecurityManager(securityManager); } @Test public void MainTest(){ Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken zhang = new UsernamePasswordToken("zhang", "123"); subject.login(zhang); if(subject.hasRole("admin")){ System.out.println("have permission"); }else{ System.out.println("don't have permission"); } // Log out subject.logout(); } }
The above example only demonstrates testing whether this user has a certain role. Next, give permission to this role
[users] zhang=123,admin,user wang=123,user [roles] admin = admin:create,admin:delete,admin:update user = user:read
@Test public void MainTest(){ Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken zhang = new UsernamePasswordToken("zhang", "123"); subject.login(zhang); if(subject.hasRole("user")){ System.out.println("have role"); // Judge whether this user has delete permission. If not, an error will be reported try{ subject.checkPermission("admin:delete"); }catch(UnauthorizedException e){ System.out.println(e.getMessage()); } }else{ System.out.println("don't have role"); } // Log out subject.logout(); }
//Error reporting information Subject does not have permission [admin:delete]
Springboot integrated Shiro
All page structures before starting
In POM Add the following three dependencies to XML
<!--shiro Core package--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.2</version> </dependency> <!--shiro And spring Integrated jar package--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.7.1</version> </dependency> <!--thymeleaf Dependent package--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
Configure in yml file
spring: thymeleaf: #Configure the template path. The default is templates. You can not configure it prefix: classpath:/templates/ suffix: .html cache: false #Turn off page caching
Customize a MyShiroConfig
import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author * @description */ @Configuration public class MyShiroConfig { // Use the securityManager object to register shiroFilter @Bean(name = "shiroFilter") public ShiroFilterFactoryBean getShiroFilterFactoryBean (@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); // Associate SecurityManager shiroFilter.setSecurityManager(defaultWebSecurityManager); return shiroFilter; } // Register the SecurityManager object, where the underlying realm uses its own implementation @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // Associated realm securityManager.setRealm(userRealm); return securityManager; } // Register the realm, which is user-defined @Bean public UserRealm userRealm(){ return new UserRealm(); } }
Customize a Realm
import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthenticatingRealm; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; /** * @author * @description */ public class UserRealm extends AuthorizingRealm { // Authorize users @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("implement doGetAuthorizationInfo-PrincipalCollection"); return null; } // The token of the authenticated user @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("implement doGetAuthorizationInfo-AuthenticationToken"); return null; } }
The above code customizes the UserRealm, which describes the access object when the SecurityManager needs the realm, and then defines a filter that can intercept the user's request and jump to the page
After getting the data from the front end in the front-end controller, it is encapsulated into a token and delivered to the SecurityManager object. sm then goes to the real to verify the user's identity and authority.
For example, we have the following Controller
@RequestMapping("/userLogin") public String login(String username, String password, Model model){ // Get the currently logged in user Subject subject = SecurityUtils.getSubject(); // Encapsulate user login data UsernamePasswordToken token = new UsernamePasswordToken(username, password); try{ // The login here will deliver the token to the securityManager, sm and then go to UserRealm to authenticate the user subject.login(token); // Login successful jump return "index"; }catch (UnknownAccountException e){ //user name does not exist model.addAttribute("msg","user name does not exist"); return "login"; }catch (IncorrectCredentialsException e){ //Incorrect password model.addAttribute("msg","Password error"); return "login"; } }
When a user initiates a / userLogin request, the front end encapsulates the user name and password into a token, and calls the login method of the subject to give the token to the SM object, which then accesses the UserRealm to verify the user's identity
Intercept user requests
If you want to intercept user requests, you need to modify shiroFilter in MyShiroConfig class for configuration
@Bean(name = "shiroFilter") public ShiroFilterFactoryBean getShiroFilterFactoryBean (@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(defaultWebSecurityManager); // Add shiro's built-in filter to intercept requests /* anno:Access to the default without authentication authc: Authentication is required to access user: I have to remember to visit perms: You must have permission on a resource to access it role: Access is only possible with the permission of a role */ Map<String, String> filterMap = new LinkedHashMap<>(); /* After configuration, shiro will intercept and authenticate the two requests sent by the user filterMap.put("/user/add","authc"); filterMap.put("/user/delete","authc"); */ filterMap.put("/user/*","authc"); shiroFilter.setFilterChainDefinitionMap(filterMap); // If you don't have permission, jump to the login page shiroFilter.setLoginUrl("/login"); return shiroFilter; }
At this time, when the user wants to access these two requests, he will jump to the login page, and the login page will initiate the / userLogin request
Authenticated user
This is written in the second overloaded method in UserRealm
// The token delivered by the authenticated user @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("implement doGetAuthorizationInfo-AuthenticationToken"); // Assumed user name and password. In the actual development, get from the database String username = "root"; String password = "123456"; // Since the UsernamePasswordToken is passed in, it needs to be transformed downward UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; if (!token.getUsername().equals(username)) { // If the user name does not match the assumed user name, null is returned and an UnKnownAccountException is thrown return null; } // The user name is judged by ourselves, while the password judgment needs to be done by shiro, and shiro will automatically call the password in the token return new SimpleAuthenticationInfo("",password,""); }
Password encryption
Before password encryption, you need to test the results of the database or assumed password
shiro has two encryption methods
-
SimpleCredentialsMatcher: SimpleCredentialsMatcher directly performs an equality check on the stored user credentials and the user credentials submitted from the AuthenticationToken.
-
Hashedcredentials matcher: instead of storing credentials in their original form and comparing the original data, a safer way to store end-user credentials (such as passwords) is to hash before storing data.
shiro's encryption algorithm
import org.apache.shiro.crypto.hash.Md5Hash; import org.apache.shiro.crypto.hash.SimpleHash; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class DemoApplicationTests { @Test public void MainTest(){ Md5Hash md5Hash = new Md5Hash("123456"); System.out.println("md5Hash.getAlgorithmName()="+md5Hash.getAlgorithmName());//The default is MD5 System.out.println("md5Hash.getSalt()="+md5Hash.getSalt()); //The default is null System.out.println("md5Hash.getIterations()="+md5Hash.getIterations());//The default is 1 // Encryption results without salt and with hash times of 1 System.out.println(md5Hash); // Add salt. The salt value is generally the user name and the hash number is 1 md5Hash = new Md5Hash("123456","root"); System.out.println(md5Hash); // Generally, the species are developed, salt is added, and the encryption times are 3 times md5Hash = new Md5Hash("123456","root",3); System.out.println(md5Hash); //4. Use SimpleHash to set MD5 (the above three can be set through this. Here is an example of the number of times of adding salt and hashing) //The first parameter is the algorithm name, md5 is specified here, the second is the password to be encrypted, the third parameter is adding salt, and the fourth parameter is the number of hashes SimpleHash hash = new SimpleHash("md5", "123456", "root",3); System.out.println(hash.toString()); } }
to grant authorization
Configure the request address to be authorized by the filter in MyShiroConfig
@Bean(name = "shiroFilter") public ShiroFilterFactoryBean getShiroFilterFactoryBean (@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(defaultWebSecurityManager); // Add shiro's built-in filter to intercept requests /* anno:Access to the default without authentication authc: Authentication is required to access user: I have to remember to visit perms: You must have permission on a resource to access it role: Only with the permission of a role can you access */ Map<String, String> filterMap = new LinkedHashMap<>(); /* After configuration, shiro will intercept and authenticate the two requests sent by the user filterMap.put("/user/add","authc"); filterMap.put("/user/delete","authc"); */ // Configure permission. The configuration permission must take effect before authentication filterMap.put("/user/add","perms[user:add]"); // Set the delete request to be accessible only with permission. At this time, it will jump to realm filterMap.put("/user/delete","perms[user:delete]"); filterMap.put("/user/*","authc"); shiroFilter.setFilterChainDefinitionMap(filterMap); // If you don't have permission, jump to the login page shiroFilter.setLoginUrl("/login"); // If there is no authorization, jump to the unauthorized page shiroFilter.setUnauthorizedUrl("/unauth"); return shiroFilter; }
Modify UserRealm as follows
import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; /** * @author * @description */ public class UserRealm extends AuthorizingRealm { // Authorize the user. Every time the filter intercepts perms[user:add], it will jump here for authorization authentication @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("implement doGetAuthorizationInfo-PrincipalCollection"); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); // If this user has a role, just add role // simpleAuthorizationInfo.addRole("role"); Subject subject = SecurityUtils.getSubject(); // Through the following two methods, you can get the perms from the authenticated user method String perms = (String) subject.getPrincipal(); // String perms = principalCollection.getPrimaryPrincipal(); // Give this permission to this user simpleAuthorizationInfo.addStringPermission(perms); return simpleAuthorizationInfo; } // The token delivered by the authenticated user @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("implement doGetAuthorizationInfo-AuthenticationToken"); // Assumed user name and password. In the actual development, get from the database String username = "root"; String password = "123456"; // It is assumed that the user has only add permission and no delete permission String perms = "user:add"; UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; if (!token.getUsername().equals(username)) { return null; } // The user's permission perms is transferred to the doGetAuthorizationInfo method through this return value, and the object is transferred in the actual development return new SimpleAuthenticationInfo(perms,password,""); } }
At this point, the basic operation of springboot has been demonstrated. In the jsp page, shiro can use the tag to carry out some tests, and in thymeleaf, you can also use the tag, but you need to load a new jar package, which is the jar package integrated by thymeleaf and shiro. I haven't studied it specifically, but I'll talk about it later.